From 7d046a0a5efb7aa484f4c565415fe77d8299cdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=98BOY?= <115208948+N0BOY@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:46:26 -0700 Subject: [PATCH 1/3] Delete ft8cn/app/src/main/java/com/bg7yoz/ft8cn directory --- .../ft8cn/database/AfterInsertQSLData.java | 5 - .../com/bg7yoz/ft8cn/html/ImportTaskList.java | 134 ------------ .../bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java | 201 ------------------ 3 files changed, 340 deletions(-) delete mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/AfterInsertQSLData.java delete mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java delete mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/AfterInsertQSLData.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/AfterInsertQSLData.java deleted file mode 100644 index 869930f..0000000 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/AfterInsertQSLData.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.bg7yoz.ft8cn.database; - -public interface AfterInsertQSLData { - void doAfterInsert(boolean isInvalid,boolean isNewQSL); -} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java deleted file mode 100644 index 409b5df..0000000 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.bg7yoz.ft8cn.html; - -import android.annotation.SuppressLint; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.util.HashMap; - -public class ImportTaskList extends HashMap { - - /** - * 获取上传的任务,以session为key - * - * @param session session - * @return 任务的HTML - */ - public String getTaskHTML(int session) { - ImportTask task = this.get(session); - if (task == null) { - return GeneralVariables.getStringFromResource(R.string.null_task_html); - } - return task.getHtml(); - } - public void cancelTask(int session){ - ImportTask task = this.get(session); - if (task != null) { - task.setStatus(ImportState.CANCELED); - } - } - - /** - * 检查任务是不是在运行。 - * - * @param session 任务ID - * @return false 没有任务或任务结束 - */ - public boolean checkTaskIsRunning(int session) { - ImportTask task = this.get(session); - if (task == null) { - return false; - } else { - return task.status == ImportState.STARTING || task.status == ImportState.IMPORTING; - } - } - - /** - * 添加任务到列表,要确保线程安全 - * - * @param session session - * @param task 任务 - */ - public synchronized ImportTask addTask(int session, ImportTask task) { - this.put(session, task); - return task; - } - - public ImportTask addTask(int session) { - return addTask(session, new ImportTask(session)); - } - - - enum ImportState { - STARTING, IMPORTING, FINISHED, CANCELED - } - - public static class ImportTask { - - - int session;//session,用于记录上传会话,是一个hash - public int count = 0;//解析出总的数据量 - public int importedCount = 0;//导入的数量 - public int readErrorCount = 0;//读取数据错误数量 - public int processCount = 0; - public int updateCount = 0;//更新的数量 - public int invalidCount = 0;//无效的QSL - public int newCount = 0;//新导入的数量 - public ImportState status = ImportState.STARTING;//状态:0:开始,1:运行,2:结束,3:取消 - String message = "";//任务消息描述 - String errorMsg = ""; - - @SuppressLint("DefaultLocale") - public String getHtml() { - String htmlHeader = "\n"; - String htmlEnder = "
\n"; - String progress = String.format("%s %.1f%%(%d/%d)\n", GeneralVariables.getStringFromResource(R.string.import_progress_html) - , count == 0 ? 0 : processCount * 100f / count, processCount, count); - String cell = "%s\n"; - String errorHtml = status == ImportState.FINISHED || status == ImportState.CANCELED ? errorMsg : ""; - String doCancelButton = status == ImportState.FINISHED || status == ImportState.CANCELED ? "" - : String.format("

" - , session,GeneralVariables.getStringFromResource(R.string.import_cancel_button)); - return htmlHeader - + String.format(cell, progress) - + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_read_error_count_html), readErrorCount)) - + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_new_count_html), newCount)) - + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_update_count_html), updateCount)) - + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_invalid_count_html), invalidCount)) - + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_readed_html), importedCount)) - + String.format(cell, message) - + String.format(cell, errorHtml) - + htmlEnder - + doCancelButton; - } - - public ImportTask(int session) { - this.session = session; - } - - public void setStatus(ImportState status) { - this.status = status; - setStateMSG(status); - } - - private void setStateMSG(ImportState state) { - switch (state) { - case IMPORTING: - this.message = String.format("%s" - ,GeneralVariables.getStringFromResource(R.string.log_importing_html)); - break; - case FINISHED: - this.message = String.format("%s" - ,GeneralVariables.getStringFromResource(R.string.log_import_finished_html)); - break; - case CANCELED: - this.message = String.format("%s" - ,GeneralVariables.getStringFromResource(R.string.import_canceled_html)); - break; - } - } - - - } -} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java deleted file mode 100644 index 924967a..0000000 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * wolf 的cat指令集兼容yaesu 450d,但是有的ham在实际测试中发现,450d默认是dig-u,此模式在wolf上测试无法满功率发射, - * 而采用usb模式就可以满功率发射,故增加一个usb模式的选项 - * 在创建rig时,用布尔参数是否时USB模式 - */ -public class Wolf_sdr_450Rig extends BaseRig { - private static final String TAG = "Wolf_sdr_450Rig"; - private final StringBuilder buffer = new StringBuilder(); - private int swr = 0; - private int alc = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - private boolean isUsbMode=true; - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); - } - } - - private void showAlert() { - if (swr >= Yaesu3RigConstant.swr_39_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu3RigConstant.setPTT_TX_On(on));//针对YAESU 450指令 - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - //getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); - //getConnector().sendData(Yaesu3RigConstant.setOperationUSB_Data_Mode()); - if (isUsbMode) {//usb模式 - getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); - }else {//dig-u模式 - getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); - } - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - //ToastMessage.showDebug("39 YAESU 读数据:"+new String(Yaesu3RigConstant.setReadOperationFreq())); - - if (!s.contains(";")) { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - //return;//说明数据还没接收完。 - } else { - if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0, s.indexOf(";"))); - } - - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";") + 1)); - - if (yaesu3Command == null) { - return; - } - //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - //} - - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER - if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { - swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - if (Yaesu3Command.isALCMeter38(yaesu3Command)) { - alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - showAlert(); - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "WOLF SDR"; - } - - public Wolf_sdr_450Rig(boolean usbMode) { - isUsbMode=usbMode; - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} From ded02df37d4e5c96a7c177320b06688c84f500dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=98BOY?= <115208948+N0BOY@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:47:59 -0700 Subject: [PATCH 2/3] Delete ft8CN/app/src/main/java/com/bg7yoz/ft8cn directory --- .../java/com/bg7yoz/ft8cn/FAQActivity.java | 46 - .../main/java/com/bg7yoz/ft8cn/FT8Common.java | 25 - .../java/com/bg7yoz/ft8cn/Ft8Message.java | 501 ----- .../com/bg7yoz/ft8cn/GeneralVariables.java | 587 ----- .../java/com/bg7yoz/ft8cn/MainActivity.java | 660 ------ .../java/com/bg7yoz/ft8cn/MainViewModel.java | 921 -------- .../java/com/bg7yoz/ft8cn/MessageHashMap.java | 55 - .../ft8cn/bluetooth/BluetoothConstants.java | 63 - .../bluetooth/BluetoothSerialListener.java | 13 - .../bluetooth/BluetoothSerialService.java | 213 -- .../bluetooth/BluetoothSerialSocket.java | 127 -- .../BluetoothStateBroadcastReceive.java | 109 - .../ft8cn/callsign/CallsignDatabase.java | 277 --- .../ft8cn/callsign/CallsignFileOperation.java | 140 -- .../bg7yoz/ft8cn/callsign/CallsignInfo.java | 79 - .../OnAfterQueryCallsignLocation.java | 12 - .../ft8cn/connector/BaseRigConnector.java | 103 - .../connector/BluetoothRigConnector.java | 184 -- .../ft8cn/connector/CableConnector.java | 75 - .../ft8cn/connector/CableSerialPort.java | 374 ---- .../bg7yoz/ft8cn/connector/ConnectMode.java | 24 - .../bg7yoz/ft8cn/connector/FlexConnector.java | 325 --- .../ft8cn/connector/IComWifiConnector.java | 112 - .../connector/OnConnectorStateChanged.java | 12 - .../com/bg7yoz/ft8cn/count/CountDbOpr.java | 555 ----- .../com/bg7yoz/ft8cn/count/CountFragment.java | 93 - .../bg7yoz/ft8cn/count/CountInfoAdapter.java | 260 --- .../bg7yoz/ft8cn/database/ControlMode.java | 33 - .../bg7yoz/ft8cn/database/DatabaseOpr.java | 1912 ---------------- .../com/bg7yoz/ft8cn/database/DxccObject.java | 49 - .../ft8cn/database/OnAfterQueryConfig.java | 11 - .../database/OnAfterQueryFollowCallsigns.java | 12 - .../ft8cn/database/OnAfterWriteConfig.java | 10 - .../bg7yoz/ft8cn/database/OnGetCallsign.java | 10 - .../bg7yoz/ft8cn/database/OperationBand.java | 158 -- .../bg7yoz/ft8cn/database/RigNameList.java | 151 -- .../com/bg7yoz/ft8cn/flex/FlexCommand.java | 66 - .../com/bg7yoz/ft8cn/flex/FlexMeterInfos.java | 149 -- .../com/bg7yoz/ft8cn/flex/FlexMeterList.java | 112 - .../com/bg7yoz/ft8cn/flex/FlexMeterType.java | 10 - .../java/com/bg7yoz/ft8cn/flex/FlexRadio.java | 1412 ------------ .../bg7yoz/ft8cn/flex/FlexRadioFactory.java | 185 -- .../bg7yoz/ft8cn/flex/FlexResponseStyle.java | 16 - .../com/bg7yoz/ft8cn/flex/RadioTcpClient.java | 224 -- .../com/bg7yoz/ft8cn/flex/RadioUdpClient.java | 174 -- .../main/java/com/bg7yoz/ft8cn/flex/VITA.java | 437 ---- .../com/bg7yoz/ft8cn/floatview/FloatView.java | 381 ---- .../ft8cn/floatview/FloatViewButton.java | 39 - .../com/bg7yoz/ft8cn/ft8listener/A91List.java | 31 - .../ft8cn/ft8listener/FT8SignalListener.java | 369 --- .../bg7yoz/ft8cn/ft8listener/OnFt8Listen.java | 27 - .../ft8cn/ft8listener/ReBuildSignal.java | 28 - .../bg7yoz/ft8cn/ft8signal/FT8Package.java | 378 ---- .../ft8cn/ft8transmit/FT8TransmitSignal.java | 1106 --------- .../ft8cn/ft8transmit/FunctionOfTransmit.java | 74 - .../bg7yoz/ft8cn/ft8transmit/GenerateFT8.java | 259 --- .../ft8cn/ft8transmit/OnDoTransmitted.java | 14 - .../ft8cn/ft8transmit/OnTransmitSuccess.java | 12 - .../bg7yoz/ft8cn/ft8transmit/QSLRecord.java | 100 - .../ft8cn/ft8transmit/QslRecordList.java | 100 - .../ft8cn/ft8transmit/TransmitCallsign.java | 59 - .../ft8cn/grid_tracker/GridInfoWindow.java | 118 - .../grid_tracker/GridMarkerInfoWindow.java | 158 -- .../ft8cn/grid_tracker/GridOsmMapView.java | 951 -------- .../grid_tracker/GridRecordInfoWindow.java | 89 - .../grid_tracker/GridTrackerMainActivity.java | 895 -------- .../com/bg7yoz/ft8cn/html/HtmlContext.java | 219 -- .../com/bg7yoz/ft8cn/html/LogHttpServer.java | 1976 ----------------- .../bg7yoz/ft8cn/icom/IComPacketTypes.java | 1033 --------- .../com/bg7yoz/ft8cn/icom/IComWifiRig.java | 152 -- .../com/bg7yoz/ft8cn/icom/IcomAudioUdp.java | 103 - .../com/bg7yoz/ft8cn/icom/IcomCivUdp.java | 110 - .../com/bg7yoz/ft8cn/icom/IcomControlUdp.java | 244 -- .../com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java | 78 - .../com/bg7yoz/ft8cn/icom/IcomUdpBase.java | 430 ---- .../com/bg7yoz/ft8cn/icom/IcomUdpClient.java | 226 -- .../java/com/bg7yoz/ft8cn/log/HashTable.java | 117 - .../bg7yoz/ft8cn/log/LogCallsignAdapter.java | 199 -- .../com/bg7yoz/ft8cn/log/LogFileImport.java | 117 - .../com/bg7yoz/ft8cn/log/LogQSLAdapter.java | 241 -- .../bg7yoz/ft8cn/log/OnQueryQSLCallsign.java | 12 - .../ft8cn/log/OnQueryQSLRecordCallsign.java | 12 - .../bg7yoz/ft8cn/log/QSLCallsignRecord.java | 80 - .../java/com/bg7yoz/ft8cn/log/QSLRecord.java | 386 ---- .../com/bg7yoz/ft8cn/log/QSLRecordStr.java | 180 -- .../java/com/bg7yoz/ft8cn/log/SWLQsoList.java | 159 -- .../ft8cn/maidenhead/MaidenheadGrid.java | 406 ---- .../java/com/bg7yoz/ft8cn/rigs/BaseRig.java | 133 -- .../bg7yoz/ft8cn/rigs/BaseRigOperation.java | 138 -- .../java/com/bg7yoz/ft8cn/rigs/CRC16.java | 93 - .../bg7yoz/ft8cn/rigs/ElecraftCommand.java | 78 - .../com/bg7yoz/ft8cn/rigs/ElecraftRig.java | 174 -- .../ft8cn/rigs/ElecraftRigConstant.java | 86 - .../bg7yoz/ft8cn/rigs/Flex6000Command.java | 72 - .../com/bg7yoz/ft8cn/rigs/Flex6000Rig.java | 144 -- .../ft8cn/rigs/Flex6000RigConstant.java | 94 - .../com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java | 113 - .../com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java | 188 -- .../bg7yoz/ft8cn/rigs/GuoHeRigConstant.java | 112 - .../com/bg7yoz/ft8cn/rigs/IcomCommand.java | 189 -- .../java/com/bg7yoz/ft8cn/rigs/IcomRig.java | 275 --- .../bg7yoz/ft8cn/rigs/IcomRigConstant.java | 280 --- .../com/bg7yoz/ft8cn/rigs/InstructionSet.java | 22 - .../com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java | 144 -- .../ft8cn/rigs/KenwoodTK90RigConstant.java | 135 -- .../bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java | 194 -- .../bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java | 193 -- .../ft8cn/rigs/OnConnectReceiveData.java | 10 - .../bg7yoz/ft8cn/rigs/OnRigStateChanged.java | 14 - .../com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java | 242 -- .../java/com/bg7yoz/ft8cn/rigs/XieGuRig.java | 240 -- .../com/bg7yoz/ft8cn/rigs/Yaesu2Command.java | 22 - .../java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java | 156 -- .../bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java | 84 - .../com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java | 190 -- .../com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java | 190 -- .../com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java | 184 -- .../com/bg7yoz/ft8cn/rigs/Yaesu3Command.java | 136 -- .../bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java | 132 -- .../com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java | 191 -- .../ft8cn/serialport/CdcAcmSerialDriver.java | 353 --- .../ft8cn/serialport/Ch34xSerialDriver.java | 388 ---- .../ft8cn/serialport/CommonUsbSerialPort.java | 318 --- .../ft8cn/serialport/Cp21xxSerialDriver.java | 335 --- .../ft8cn/serialport/FtdiSerialDriver.java | 432 ---- .../bg7yoz/ft8cn/serialport/ProbeTable.java | 87 - .../serialport/ProlificSerialDriver.java | 582 ----- .../serialport/SerialTimeoutException.java | 15 - .../com/bg7yoz/ft8cn/serialport/UsbId.java | 95 - .../ft8cn/serialport/UsbSerialDriver.java | 33 - .../ft8cn/serialport/UsbSerialPort.java | 261 --- .../ft8cn/serialport/UsbSerialProber.java | 92 - .../ft8cn/serialport/util/MonotonicClock.java | 14 - .../util/SerialInputOutputManager.java | 257 --- .../ft8cn/spectrum/SpectrumListener.java | 41 - .../com/bg7yoz/ft8cn/timer/OnUtcTimer.java | 14 - .../java/com/bg7yoz/ft8cn/timer/UtcTimer.java | 301 --- .../bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java | 54 - .../ft8cn/ui/BauRateSpinnerAdapter.java | 63 - .../bg7yoz/ft8cn/ui/CallingListAdapter.java | 426 ---- .../bg7yoz/ft8cn/ui/CallingListFragment.java | 416 ---- .../bg7yoz/ft8cn/ui/ClearCacheDataDialog.java | 234 -- .../com/bg7yoz/ft8cn/ui/ColumnarView.java | 164 -- .../com/bg7yoz/ft8cn/ui/ConfigFragment.java | 1235 ----------- .../com/bg7yoz/ft8cn/ui/FilterDialog.java | 95 - .../bg7yoz/ft8cn/ui/FlexMeterRulerView.java | 193 -- .../ft8cn/ui/FlexRadioInfoFragment.java | 203 -- .../java/com/bg7yoz/ft8cn/ui/FreqDialog.java | 150 -- .../ft8cn/ui/FunctionOrderSpinnerAdapter.java | 78 - .../java/com/bg7yoz/ft8cn/ui/HelpDialog.java | 176 -- .../ui/LaunchSupervisionSpinnerAdapter.java | 81 - .../java/com/bg7yoz/ft8cn/ui/LogFragment.java | 545 ----- .../bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java | 210 -- .../bg7yoz/ft8cn/ui/MyCallingFragment.java | 502 ----- .../ft8cn/ui/NoReplyLimitSpinnerAdapter.java | 64 - .../ft8cn/ui/PttDelaySpinnerAdapter.java | 60 - .../com/bg7yoz/ft8cn/ui/QRZ_Fragment.java | 68 - .../ft8cn/ui/RigNameSpinnerAdapter.java | 65 - .../bg7yoz/ft8cn/ui/RulerFrequencyView.java | 123 - .../ft8cn/ui/SelectBluetoothDialog.java | 220 -- .../ft8cn/ui/SelectFlexRadioDialog.java | 244 -- .../com/bg7yoz/ft8cn/ui/SetVolumeDialog.java | 113 - .../com/bg7yoz/ft8cn/ui/SpectrumFragment.java | 219 -- .../com/bg7yoz/ft8cn/ui/SpectrumView.java | 207 -- .../com/bg7yoz/ft8cn/ui/ToastMessage.java | 82 - .../ft8cn/ui/UtcOffsetSpinnerAdapter.java | 60 - .../com/bg7yoz/ft8cn/ui/VolumeProgress.java | 162 -- .../com/bg7yoz/ft8cn/ui/WaterfallView.java | 281 --- .../com/bg7yoz/ft8cn/wave/HamRecorder.java | 306 --- .../com/bg7yoz/ft8cn/wave/MicRecorder.java | 102 - .../bg7yoz/ft8cn/wave/OnAudioRecorded.java | 26 - .../bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java | 10 - .../com/bg7yoz/ft8cn/wave/OnHamRecord.java | 10 - .../ft8cn/wave/OnVoiceMonitorChanged.java | 5 - .../com/bg7yoz/ft8cn/wave/WaveAccess.java | 11 - .../com/bg7yoz/ft8cn/wave/WaveConstants.java | 28 - .../com/bg7yoz/ft8cn/wave/WaveFileReader.java | 202 -- .../com/bg7yoz/ft8cn/wave/WaveFileWriter.java | 144 -- .../com/bg7yoz/ft8cn/wave/WriteWavHeader.java | 128 -- 179 files changed, 38775 deletions(-) delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/OnConnectorStateChanged.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountDbOpr.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/ControlMode.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryConfig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryFollowCallsigns.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexCommand.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterInfos.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexResponseStyle.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/UtcOffsetSpinnerAdapter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java delete mode 100644 ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java deleted file mode 100644 index 442a387..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * 问题收集的WebView。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.appcompat.app.AppCompatActivity; - -public class FAQActivity extends AppCompatActivity { - - @SuppressLint("SetJavaScriptEnabled") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_faqactivity); - - - WebView webView = (WebView) findViewById(R.id.faqWebView); - webView.getSettings().setJavaScriptEnabled(true); - webView.getSettings().setDomStorageEnabled(true); // 这个要加上 - - /* 获得 webview url,请注意url单词是product而不是products,products是旧版本的参数,用错地址将不能成功提交 */ - //String url = "https://www.qrz.com/db/BG7YOZ"; - String url = "https://support.qq.com/product/415890"; - - /* WebView 内嵌 Client 可以在APP内打开网页而不是跳出到浏览器 */ - WebViewClient webViewClient = new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - super.shouldOverrideUrlLoading(view, url); - view.loadUrl(url); - return true; - } - }; - webView.setWebViewClient(webViewClient); - - webView.loadUrl(url); - } - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java deleted file mode 100644 index 67e2312..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.bg7yoz.ft8cn; - -/** - * FT8有关的常量。 - * @author BGY70Z - * @date 2023-03-20 - */ -public final class FT8Common { - public static final int FT8_MODE=0; - public static final int FT4_MODE=1; - public static final int SAMPLE_RATE=12000; - public static final int FT8_SLOT_TIME=15; - public static final int FT8_SLOT_TIME_MILLISECOND=15000;//一个周期的毫秒数 - public static final int FT4_SLOT_TIME_MILLISECOND=7500; - public static final int FT8_5_SYMBOLS_MILLISECOND=800;//5个符号所需的 - - - public static final float FT4_SLOT_TIME=7.5f; - public static final int FT8_SLOT_TIME_M=150;//15秒 - public static final int FT8_5_SYMBOLS_TIME_M =8;//5个符号的时间长度0.8秒 - public static final int FT4_SLOT_TIME_M=75;//7.5秒 - public static final int FT8_TRANSMIT_DELAY=500;//默认发射延迟时长,毫秒 - public static final long DEEP_DECODE_TIMEOUT=7*1000;//深度解码的最长时间范围 - public static final int DECODE_MAX_ITERATIONS=1;//迭代次数 -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java deleted file mode 100644 index fe6519a..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java +++ /dev/null @@ -1,501 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * Ft8Message类是用于展现FT8信号的解析结果。 - * 包括UTC时间、信噪比、时间偏移、频率、得分、消息的文本、消息的哈希值 - * ----2022.5.6----- - * time_sec可能是时间偏移,目前还不能完全确定,待后续解决。 - * 1.为方便在列表中显示,各要素通过Get方法,返回String类型的结果。 - * -----2022.5.13--- - * 2.增加i3,n3消息类型内容 - * @author BG7YOZ - * @date 2022.5.6 - */ - -import android.annotation.SuppressLint; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.ft8signal.FT8Package; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; -import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.google.android.gms.maps.model.LatLng; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; - -public class Ft8Message { - private static String TAG = "Ft8Message"; - public int i3 = 0; - public int n3 = 0; - public int signalFormat = FT8Common.FT8_MODE;//是不是FT8格式的消息 - public long utcTime;//UTC时间 - public boolean isValid;//是否是有效信息 - public int snr = 0;//信噪比 - public float time_sec = 0;//时间偏移(秒) - public float freq_hz = 0;//频率 - public int score = 0;//得分 - public int messageHash;//消息的哈希 - - public String callsignFrom = null;//发起呼叫的呼号 - public String callsignTo = null;//接收呼叫的呼号 - - public String modifier = null;//目标呼号的修饰符 如CQ POTA BG7YOZ OL50中的POTA - - public String extraInfo = null; - public String maidenGrid = null; - public int report = -100;//当-100时,意味着没有信号报告 - public long callFromHash10 = 0;//12位长度的哈希码 - public long callFromHash12 = 0;//12位长度的哈希码 - public long callFromHash22 = 0;//12位长度的哈希码 - public long callToHash10 = 0;//12位长度的哈希码 - public long callToHash12 = 0;//12位长度的哈希码 - public long callToHash22 = 0;//12位长度的哈希码 - //private boolean isCallMe = false;//是不是CALL我的消息 - public long band;//载波频率 - - public String fromWhere = null;//用于显示地址 - public String toWhere = null;//用于显示地址 - - public boolean isQSL_Callsign = false;//是不是通联过的呼号 - - public static MessageHashMap hashList = new MessageHashMap(); - - - public boolean fromDxcc = false; - public boolean fromItu = false; - public boolean fromCq = false; - public boolean toDxcc = false; - public boolean toItu = false; - public boolean toCq = false; - - public LatLng fromLatLng = null; - public LatLng toLatLng = null; - - public boolean isWeakSignal=false; - - - - - @NonNull - @SuppressLint({"SimpleDateFormat", "DefaultLocale"}) - @Override - public String toString() { - return String.format("%s %d %+4.2f %4.0f ~ %s Hash : %#06X", - new SimpleDateFormat("HHmmss").format(utcTime), - snr, time_sec, freq_hz, getMessageText(), messageHash); - } - - /** - * 创建一个解码消息对象,要确定信号的格式。 - * - * @param signalFormat - */ - public Ft8Message(int signalFormat) { - this.signalFormat = signalFormat; - } - - public Ft8Message(String callTo, String callFrom, String extraInfo) { - //如果是自由文本,callTo=CQ,callFrom=MyCall,extraInfo=freeText - this.callsignTo = callTo.toUpperCase(); - this.callsignFrom = callFrom.toUpperCase(); - this.extraInfo = extraInfo.toUpperCase(); - } - - public Ft8Message(int i3, int n3, String callTo, String callFrom, String extraInfo) { - this.callsignTo = callTo; - this.callsignFrom = callFrom; - this.extraInfo = extraInfo; - this.i3 = i3; - this.n3 = n3; - this.utcTime = UtcTimer.getSystemTime();//用于显示TX - } - - /** - * 创建一个解码消息对象 - * - * @param message 如果message不为null,则创建一个与message内容一样的解码消息对象 - */ - public Ft8Message(Ft8Message message) { - if (message != null) { - - signalFormat = message.signalFormat; - utcTime = message.utcTime; - isValid = message.isValid; - snr = message.snr; - time_sec = message.time_sec; - freq_hz = message.freq_hz; - score = message.score; - band = message.band; - - messageHash = message.messageHash; - - if (message.callsignFrom.equals("<...>")) {//到哈希列表中查一下 - callsignFrom = hashList.getCallsign(new long[]{message.callFromHash10, message.callFromHash12, message.callFromHash22}); - } else { - callsignFrom = message.callsignFrom; - } - - if (message.callsignTo.equals("<...>")) {//到哈希列表中查一下 - callsignTo = hashList.getCallsign(new long[]{message.callToHash10, message.callToHash12, message.callToHash22}); - } else { - callsignTo = message.callsignTo; - } - if (message.i3 == 4) { - hashList.addHash(FT8Package.getHash22(message.callsignFrom), message.callsignFrom); - hashList.addHash(FT8Package.getHash12(message.callsignFrom), message.callsignFrom); - hashList.addHash(FT8Package.getHash10(message.callsignFrom), message.callsignFrom); - } - - extraInfo = message.extraInfo; - maidenGrid = message.maidenGrid; - report = message.report; - callToHash10 = message.callToHash10; - callToHash12 = message.callToHash12; - callToHash22 = message.callToHash22; - callFromHash10 = message.callFromHash10; - callFromHash12 = message.callFromHash12; - callFromHash22 = message.callFromHash22; - - - i3 = message.i3; - n3 = message.n3; - - //把哈希和呼号对应关系保存到列表里 - hashList.addHash(callToHash10, callsignTo); - hashList.addHash(callToHash12, callsignTo); - hashList.addHash(callToHash22, callsignTo); - hashList.addHash(callFromHash10, callsignFrom); - hashList.addHash(callFromHash12, callsignFrom); - hashList.addHash(callFromHash22, callsignFrom); - - - //Log.d(TAG, String.format("i3:%d,n3:%d,From:%s,To:%s", i3, n3, getCallsignFrom(), getCallsignTo())); - } - } - - /** - * 返回解码消息的所使用的频率 - * - * @return String 为方便显示,返回值是字符串 - */ - @SuppressLint("DefaultLocale") - public String getFreq_hz() { - return String.format("%04.0f", freq_hz); - } - - public String getMessageText(boolean showWeekSignal){ - if (isWeakSignal && showWeekSignal){ - return "*"+getMessageText(); - }else { - return getMessageText(); - } - } - - /** - * 返回解码消息的文本内容 - * - * @return String - */ - public String getMessageText() { - - if (i3 == 0 && n3 == 0) {//说明是自由文本 - if (extraInfo.length() < 13) { - return String.format("%-13s", extraInfo.toUpperCase()); - } else { - return extraInfo.toUpperCase().substring(0, 13); - } - } - if (modifier != null && checkIsCQ()) {//修饰符 - if (modifier.matches("[0-9]{3}|[A-Z]{1,4}")) { - return String.format("%s %s %s %s", callsignTo, modifier, callsignFrom, extraInfo).trim(); - } - } - return String.format("%s %s %s", callsignTo, callsignFrom, extraInfo).trim(); - } - - /** - * 返回解码消息带信噪比的内容 - * - * @return 内容 - */ - @SuppressLint("DefaultLocale") - public String getMessageTextWithDb() { - return String.format("%d %s %s %s", snr, callsignTo, callsignFrom, extraInfo).trim(); - } - - /** - * 返回消息的延迟时间。可能不一定对,待研究清楚解码算法后在确定 - * - * @return String 为方便显示,返回值是字符串。 - */ - @SuppressLint("DefaultLocale") - public String getDt() { - return String.format("%.1f", time_sec); - } - - /** - * 返回解码消息的信噪比dB值,该计算方法还为搞定,暂时用000代替 - * - * @return String 为方便显示,返回值是字符串 - */ - public String getdB() { - return String.valueOf(snr); - } - - /** - * 检查消息处于奇数还是偶数序列。 - * - * @return boolean 处于偶数序列true,第0,30秒为true - */ - public boolean isEvenSequence() { - if (signalFormat == FT8Common.FT8_MODE) { - return (utcTime / 1000) % 15 == 0; - } else { - return (utcTime / 100) % 75 == 0; - } - } - - /** - * 显示当前消息处于哪一个时间序列的。 - * - * @return String 以时间周期取模为结果。 - */ - @SuppressLint("DefaultLocale") - public int getSequence() { - if (signalFormat == FT8Common.FT8_MODE) { - return (int) ((((utcTime + 750) / 1000) / 15) % 2); - } else { - return (int) (((utcTime + 370) / 100) / 75) % 2; - } - } - - @SuppressLint("DefaultLocale") - public int getSequence4() { - if (signalFormat == FT8Common.FT8_MODE) { - return (int) ((((utcTime + 750) / 1000) / 15) % 4); - } else { - return (int) (((utcTime + 370) / 100) / 75) % 4; - } - } - - /** - * 消息中含有mycall呼号的 - * - * @return boolean - */ - public boolean inMyCall() { - if (GeneralVariables.myCallsign.length() == 0) return false; - return this.callsignFrom.contains(GeneralVariables.myCallsign) - || this.callsignTo.contains(GeneralVariables.myCallsign); - //return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals("")); - } -/* -i3.n3类型 基本目的 消息范例 位字段标签 -0.0 自由文本(Free Text) TNX BOB 73 GL f71 -0.1 远征(DXpedition) K1ABC RR73; W9XYZ -08 c28 c28 h10 r5 -0.3 野外日(Field Day) K1ABC W9XYZ 6A WI c28 c28 R1 n4 k3 S7 -0.4 野外日(Field Day) W9XYZ K1ABC R 17B EMA c28 c28 R1 n4 k3 S7 -0.5 遥测(Telemetry) 123456789ABCDEF012 t71 -1. 标准消息(Std Msg) K1ABC/R W9XYZ/R R EN37 c28 r1 c28 r1 R1 g15 -2. 欧盟甚高频(EU VHF) G4ABC/P PA9XYZ JO22 c28 p1 c28 p1 R1 g15 -3. 电传(RTTY RU) K1ABC W9XYZ 579 WI t1 c28 c28 R1 r3 s13 -4. 非标准呼叫(NonStd Call) PJ4/K1ABC RRR h12 c58 h1 r2 c1 -5. 欧盟甚高频(EU VHF) R 570007 JO22DB h12 h22 R1 r3 s11 g25 -*/ -/* -标签 传达的信息 -c1 第一个呼号是CQ;h12被忽略 -c28 标准呼号、CQ、DE、QRZ或22位哈希 -c58 非标准呼号,最多11个字符 -f71 自由文本,最多13个字符 -g15 4字符网格、报告、RRR、RR73、73或空白 -g25 6字符网格 -h1 哈希呼号是第二个呼号 -h10 哈希呼号,10位 -h12 哈希呼号,12位 -h22 哈希呼号,22位 -k3 野外日级别(Class):A、B、…F -n4 发射器数量:1-16、17-32 -p1 呼号后缀 /P -r1 呼号后缀/R -r2 RRR、RR73、73、或空白 -r3 报告:2-9,显示为529-599或52-59 -R1 R -r5 报告:-30到+30,仅偶数 -s11 序列号(0-2047) -s13 序列号(0-7999)或州/省 -S7 ARRL/RAC部分 -t1 TU; -t71 遥感数据,最多18位十六进制数字 - -*/ - - - /** - * 获取发送者的呼号,fromTo的最终解决办法要在decode.c中解决---TO DO---- - * 可获取发送者呼号的消息类型为i1,i2,i3,i4,i5,i0.1,i0.3,i0.4 - * - * @return String 返回呼号 - */ - public String getCallsignFrom() { - if (callsignFrom == null) { - return ""; - } - return callsignFrom.replace("<", "").replace(">", ""); - } - - /** - * 获取通联信息中的接收呼号 - * - * @return - */ - public String getCallsignTo() { - if (callsignTo == null) { - return ""; - } - if (callsignTo.length() < 2) { - return ""; - } - if (callsignTo.substring(0, 2).equals("CQ") || callsignTo.substring(0, 2).equals("DE") - || callsignTo.substring(0, 3).equals("QRZ")) { - return ""; - } - return callsignTo.replace("<", "").replace(">", ""); - } - - /** - * 从消息中获取梅登海德网格信息 - * - * @return String,梅登海德网格,如果没有返回""。 - */ - public String getMaidenheadGrid(DatabaseOpr db) { - if (i3 != 1 && i3 != 2) {//一般只有i3=1或i3=2,标准消息,甚高频消息才有网格 - return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 - } else { - String[] msg = getMessageText().split(" "); - if (msg.length < 1) { - return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 - } - String s = msg[msg.length - 1]; - if (MaidenheadGrid.checkMaidenhead(s)) { - return s; - } else {//不是网格信息,就可能是信号报告 - return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 - } - } - } - - public String getToMaidenheadGrid(DatabaseOpr db) { - if (checkIsCQ()) return ""; - return GeneralVariables.getGridByCallsign(callsignTo, db); - } - - /** - * 查看消息是不是CQ - * - * @return boolean 是CQ返回true - */ - public boolean checkIsCQ() { - String s = callsignTo.trim().split(" ")[0]; - if (s == null) { - return false; - } else { - return (s.equals("CQ") || s.equals("DE") || s.equals("QRZ")); - } - } - - /** - * 查消息的类型。i3.n3。 - * - * @return 消息类型 - */ - - public String getCommandInfo() { - return getCommandInfoByI3N3(i3, n3); - } - - /** - * 查消息的类型。i3.n3。 - * - * @param i i3 - * @param n n3 - * @return 消息类型 - */ - @SuppressLint("DefaultLocale") - public static String getCommandInfoByI3N3(int i, int n) { - String format = "%d.%d:%s"; - switch (i) { - case 1: - case 2: - return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.std_msg)); - case 5: - case 3: - case 4: - return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg)); - case 0: - switch (n) { - case 0: - return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.free_text)); - case 1: - return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.dXpedition)); - case 3: - case 4: - return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.field_day)); - case 5: - return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.telemetry)); - } - } - return ""; - } - - //获取发送者的传输对象 - public TransmitCallsign getFromCallTransmitCallsign() { - return new TransmitCallsign(this.i3, this.n3, this.callsignFrom, freq_hz - , this.getSequence() - , snr); - } - - //获取发送者的传输对象,注意!!!与发送者的时序是相反的!!! - public TransmitCallsign getToCallTransmitCallsign() { - if (report == -100) {//如果消息中没有信号报告,就用发送方的SNR代替 - return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, snr); - } else { - return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, report); - } - } - - @SuppressLint("DefaultLocale") - public String toHtml() { - StringBuilder result = new StringBuilder(); - - result.append(""); - result.append(UtcTimer.getDatetimeStr(utcTime)); - result.append("\n"); - - result.append(""); - result.append(getdB()); - result.append("\n"); - - result.append(""); - result.append(String.format("%.1f", time_sec)); - result.append("\n"); - - result.append(""); - result.append(String.format("%.0f", freq_hz)); - result.append("\n"); - - result.append(""); - result.append(getMessageText()); - result.append("\n"); - - result.append(""); - result.append(BaseRigOperation.getFrequencyStr(band)); - result.append("\n"); - - return result.toString(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java deleted file mode 100644 index d1fd408..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java +++ /dev/null @@ -1,587 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * 常用变量。关于mainContext有内存泄漏的风险,以后解决。 - * mainContext - */ - -import android.annotation.SuppressLint; -import android.content.Context; - -import androidx.lifecycle.MutableLiveData; - -import com.bg7yoz.ft8cn.callsign.CallsignDatabase; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.ft8transmit.QslRecordList; -import com.bg7yoz.ft8cn.html.HtmlContext; -import com.bg7yoz.ft8cn.icom.IcomAudioUdp; -import com.bg7yoz.ft8cn.log.QSLRecord; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - - -public class GeneralVariables { - private static final String TAG = "GeneralVariables"; - public static String VERSION = BuildConfig.VERSION_NAME;//版本号"0.62(Beta 4)"; - public static String BUILD_DATE = BuildConfig.apkBuildTime;//编译的时间 - public static int MESSAGE_COUNT = 3000;//消息的最大缓存数量 - public static boolean saveSWLMessage=false;//保存解码消息开关 - public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关 - - public static boolean deepDecodeMode=false;//是否开启深度解码 - - public static boolean audioOutput32Bit =true;//音频输出类型true=float,false=int16 - public static int audioSampleRate=12000;//发射音频的采样率 - - public static MutableLiveData mutableVolumePercent = new MutableLiveData<>(); - public static float volumePercent = 0.5f;//播放音频的音量,是百分比 - - public static int flexMaxRfPower=10;//flex电台的最大发射功率 - public static int flexMaxTunePower=10;//flex电台的最大调谐功率 - - private Context mainContext; - public static CallsignDatabase callsignDatabase = null; - - public void setMainContext(Context context) { - mainContext = context; - } - - public static boolean isChina = true;//语言是不是中国 - public static boolean isTraditionalChinese = true;//语言是不是繁体中文 - //public static double maxDist = 0;//最远距离 - - //各已经通联的分区列表 - public static final Map dxccMap = new HashMap<>(); - public static final Map cqMap = new HashMap<>(); - public static final Map ituMap = new HashMap<>(); - - private static final Map excludedCallsigns = new HashMap<>(); - - /** - * 添加排除的字头 - * - * @param callsigns 呼号 - */ - public static synchronized void addExcludedCallsigns(String callsigns) { - excludedCallsigns.clear(); - String[] s = callsigns.toUpperCase().replace(" ", ",") - .replace("|", ",") - .replace(",", ",").split(","); - for (int i = 0; i < s.length; i++) { - if (s[i].length() > 0) { - excludedCallsigns.put(s[i], 0); - } - } - } - - /** - * 查找是否含有排除的字头 - * - * @param callsign 呼号 - * @return 是否 - */ - public static synchronized boolean checkIsExcludeCallsign(String callsign) { - Iterator iterator = excludedCallsigns.keySet().iterator(); - while (iterator.hasNext()) { - String key = iterator.next(); - if (callsign.toUpperCase().indexOf(key) == 0) { - return true; - } - } - return false; - } - - /** - * 获取排除呼号前缀的列表 - * - * @return 列表 - */ - public static synchronized String getExcludeCallsigns() { - StringBuilder calls = new StringBuilder(); - Iterator iterator = excludedCallsigns.keySet().iterator(); - int i = 0; - while (iterator.hasNext()) { - String key = iterator.next(); - if (i == 0) { - calls.append(key); - } else { - calls.append(",").append(key); - } - i++; - } - return calls.toString(); - } - - - //通联记录列表,包括成功与不成功的 - public static QslRecordList qslRecordList = new QslRecordList(); - - //此处有内存泄露警告,但Application Context不应该会内存泄露,所以注释掉 - @SuppressLint("StaticFieldLeak") - private static GeneralVariables generalVariables = null; - - public static GeneralVariables getInstance() { - if (generalVariables == null) { - generalVariables = new GeneralVariables(); - } - return generalVariables; - } - - public static Context getMainContext() { - return GeneralVariables.getInstance().mainContext; - } - - - public static MutableLiveData mutableDebugMessage = new MutableLiveData<>(); - public static int QUERY_FREQ_TIMEOUT = 2000;//轮询频率变化的时间间隔。2秒 - public static int START_QUERY_FREQ_DELAY = 2000;//开始轮询频率的时间延迟 - - public static final int DEFAULT_LAUNCH_SUPERVISION = 10 * 60 * 1000;//发射监管默认值,10分钟 - private static String myMaidenheadGrid = ""; - public static MutableLiveData mutableMyMaidenheadGrid = new MutableLiveData<>(); - - public static int connectMode = ConnectMode.USB_CABLE;//连接方式USB==0,BLUE_TOOTH==1 - - //public static String bluetoothDeviceAddress=null;//可以用于连接的蓝牙设备地址 - - - //用于记录呼号于网格的对应关系 todo---应当把此处列表也放到后台追踪信息里 - //public static ArrayList callsignMaidenheadGrids=new ArrayList<>(); - public static final Map callsignAndGrids = new ConcurrentHashMap<>(); - //private static final Map callsignAndGrids=new HashMap<>(); - - public static String myCallsign = "";//我的呼号 - public static String toModifier = "";//呼叫的修饰符 - private static float baseFrequency = 1000;//声音频率 - public static MutableLiveData mutableBaseFrequency = new MutableLiveData<>(); - - public static boolean synFrequency = false;//同频发射 - public static int transmitDelay = 500;//发射延迟时间,这个时间也是给上一个周期的解码时间 - public static int pttDelay = 100;//PTT的响应时间,在给电台PTT指令后,一般电台会有一个响应时间,此处默认是100毫秒 - public static int civAddress = 0xa4;//civ地址 - public static int baudRate = 19200;//波特率 - public static long band = 14074000;//载波频段 - public static int instructionSet = 0;//指令集,0:icom,1:yaesu 2 代,2:yaesu 3代。 - public static int bandListIndex = -1;//电台波段的索引值 - public static MutableLiveData mutableBandChange = new MutableLiveData<>();//波段索引值变化 - public static int controlMode = ControlMode.VOX; - public static int modelNo = 0; - public static int launchSupervision = DEFAULT_LAUNCH_SUPERVISION;//发射监管 - public static long launchSupervisionStart = UtcTimer.getSystemTime();//自动发射的起始时间 - public static int noReplyLimit = 0;//呼叫无回应次数0==忽略 - - public static int noReplyCount = 0;//没有回应的次数 - - //下面4个参数是ICOM网络方式连接的参数 - public static String icomIp = "255.255.255.255"; - public static int icomUdpPort = 50001; - public static String icomUserName = "ic705"; - public static String icomPassword = ""; - - - public static boolean autoFollowCQ = true;//自动关注CQ - public static boolean autoCallFollow = true;//自动呼叫关注的呼号 - public static ArrayList QSL_Callsign_list = new ArrayList<>();//QSL成功的呼号 - public static ArrayList QSL_Callsign_list_other_band = new ArrayList<>();//在其它波段QSL成功的呼号 - - - public static final ArrayList followCallsign = new ArrayList<>();//关注的呼号 - - public static ArrayList transmitMessages = new ArrayList<>();//放在呼叫界面,关注的列表 - - public static void setMyMaidenheadGrid(String grid) { - myMaidenheadGrid = grid; - mutableMyMaidenheadGrid.postValue(grid); - } - - public static String getMyMaidenheadGrid() { - return myMaidenheadGrid; - } - - public static float getBaseFrequency() { - return baseFrequency; - } - - public static void setBaseFrequency(float baseFrequency) { - mutableBaseFrequency.postValue(baseFrequency); - GeneralVariables.baseFrequency = baseFrequency; - } - - @SuppressLint("DefaultLocale") - public static String getBaseFrequencyStr() { - return String.format("%.0f", baseFrequency); - } - - public static String getCivAddressStr() { - return String.format("%2X", civAddress); - } - - public static String getTransmitDelayStr() { - return String.valueOf(transmitDelay); - } - - public static String getBandString() { - return BaseRigOperation.getFrequencyAllInfo(band); - } - - /** - * 查有没有通联成功的呼号 - * - * @param callsign 呼号 - * @return 是否存在 - */ - public static boolean checkQSLCallsign(String callsign) { - return QSL_Callsign_list.contains(callsign); - } - - /** - * 查别的波段有没有通联成功的呼号 - * - * @param callsign 呼号 - * @return 是否存在 - */ - public static boolean checkQSLCallsign_OtherBand(String callsign) { - return QSL_Callsign_list_other_band.contains(callsign); - } - - - /** - * 查该呼号是不是在关注的呼号列表中 - * - * @param callsign 呼号 - * @return 是否存在 - */ - public static boolean callsignInFollow(String callsign) { - return followCallsign.contains(callsign); - } - - /** - * 向通联成功的呼号列表添加 - * - * @param callsign 呼号 - */ - public static void addQSLCallsign(String callsign) { - if (!checkQSLCallsign(callsign)) { - QSL_Callsign_list.add(callsign); - } - } - - public static String getMyMaidenhead4Grid() { - if (myMaidenheadGrid.length() > 4) { - return myMaidenheadGrid.substring(0, 4); - } - return myMaidenheadGrid; - } - - /** - * 自动程序运行起始时间 - */ - public static void resetLaunchSupervision() { - launchSupervisionStart = UtcTimer.getSystemTime(); - } - - /** - * 或取自动程序的运行时长 - * - * @return 毫秒 - */ - public static int launchSupervisionCount() { - return (int) (UtcTimer.getSystemTime() - launchSupervisionStart); - } - - public static boolean isLaunchSupervisionTimeout() { - if (launchSupervision == 0) return false;//0是不监管 - return launchSupervisionCount() > launchSupervision; - } - - /** - * 从extraInfo中查消息顺序 - * - * @param extraInfo 消息中的扩展内容 - * @return 返回消息序号 - */ - public static int checkFunOrderByExtraInfo(String extraInfo) { - if (checkFun5(extraInfo)) return 5; - if (checkFun4(extraInfo)) return 4; - if (checkFun3(extraInfo)) return 3; - if (checkFun2(extraInfo)) return 2; - if (checkFun1(extraInfo)) return 1; - return -1; - } - - /** - * 检查消息的序号,如果解析不出来,就-1 - * - * @param message 消息 - * @return 消息序号 - */ - public static int checkFunOrder(Ft8Message message) { - if (message.checkIsCQ()) return 6; - return checkFunOrderByExtraInfo(message.extraInfo); - - } - - - //是不是网格报告 - public static boolean checkFun1(String extraInfo) { - //网格报告必须是4位,或没有网格 - return (extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]") && !extraInfo.equals("RR73")) - || (extraInfo.trim().length() == 0); - - } - - //是不是信号报告,如-10 - public static boolean checkFun2(String extraInfo) { - if (extraInfo.trim().length() < 2) { - return false; - }//信号报告必须至少2位 - try { - return Integer.parseInt(extraInfo.trim()) != 73;//如果是73,说明是消息6,不是消息2 - //return true; - } catch (Exception e) { - return false; - } - } - - //是不是带R的信号报告,如R-10 - public static boolean checkFun3(String extraInfo) { - if (extraInfo.trim().length() < 3) { - return false; - }//带R信号报告必须至少3位 - //第一位如果不是R,或者第二位是R,说明不是消息3 - if ((extraInfo.trim().charAt(0) != 'R') || (extraInfo.trim().charAt(1) == 'R')) { - return false; - } - - try { - Integer.parseInt(extraInfo.trim().substring(1)); - return true; - } catch (Exception e) { - return false; - } - } - - //是不是RRR或RR73值 - public static boolean checkFun4(String extraInfo) { - return extraInfo.trim().equals("RR73") || extraInfo.trim().equals("RRR"); - } - - //是不是73值 - public static boolean checkFun5(String extraInfo) { - return extraInfo.trim().equals("73"); - } - - - /** - * 判断是不是信号报告,如果是,把值赋给 report - * @param extraInfo 消息扩展 - * @return 信号报告值,没找到是-100 - */ - public static int checkFun2_3(String extraInfo){ - if (extraInfo.equals("73")) return -100; - if (extraInfo.matches("[R]?[+-]?[0-9]{1,2}")){ - try { - return Integer.parseInt(extraInfo.replace("R","")); - } catch (Exception e) { - return -100; - } - } - return -100; - } - - /** - * 判断是不是网格报告,如果是,把值赋给 report - * @param extraInfo 消息扩展 - * @return 信号报告 - */ - public static boolean checkFun1_6(String extraInfo){ - return extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]") - && !extraInfo.trim().equals("RR73"); - } - /** - * 检查是否是通联结束:RRR、RR73、73 - * @param extraInfo 消息后缀 - * @return 是否 - */ - public static boolean checkFun4_5(String extraInfo){ - return extraInfo.trim().equals("RR73") - || extraInfo.trim().equals("RRR") - ||extraInfo.trim().equals("73"); - } - - /** - * 从String.xml中提取字符串 - * - * @param id id - * @return 字符串 - */ - public static String getStringFromResource(int id) { - if (getMainContext() != null) { - return getMainContext().getString(id); - } else { - return ""; - } - } - - - /** - * 把已经通联的DXCC分区添加到集合中 - * - * @param dxccPrefix DXCC前缀 - */ - public static void addDxcc(String dxccPrefix) { - dxccMap.put(dxccPrefix, dxccPrefix); - } - - /** - * 查看是不是已经通联的DXCC分区 - * - * @param dxccPrefix DXCC前缀 - * @return 是否 - */ - public static boolean getDxccByPrefix(String dxccPrefix) { - return dxccMap.containsKey(dxccPrefix); - } - - /** - * 把CQ分区加到列表里 - * - * @param cqZone cq分区编号 - */ - public static void addCqZone(int cqZone) { - cqMap.put(cqZone, cqZone); - } - - /** - * 查是否存在已经通联的CQ分区 - * - * @param cq cq分区编号 - * @return 是否存在 - */ - public static boolean getCqZoneById(int cq) { - return cqMap.containsKey(cq); - } - - /** - * 把itu分区添加到已通联的ITU列表中 - * - * @param itu itu编号 - */ - public static void addItuZone(int itu) { - ituMap.put(itu, itu); - } - - /** - * 查Itu分区在不在已通联的列表中 - * - * @param itu itu编号 - * @return 是否存在 - */ - public static boolean getItuZoneById(int itu) { - return ituMap.containsKey(itu); - } - - //用于触发新的网格 - public static MutableLiveData mutableNewGrid = new MutableLiveData<>(); - - /** - * 把呼号与网格的对应关系添加到呼号--网格对应表, - * - * @param callsign 呼号 - * @param grid 网格 - */ - public static void addCallsignAndGrid(String callsign, String grid) { - if (grid.length() >= 4) { - callsignAndGrids.put(callsign, grid); - mutableNewGrid.postValue(grid); - } - } - - /** - * 呼号--网格对应表。以呼号查网格 - * 如果内存中没有,应当到数据库中查一下。 - * - * @param callsign 呼号 - * @return 是否有对应的网格 - */ - public static boolean getCallsignHasGrid(String callsign) { - return callsignAndGrids.containsKey(callsign); - } - - /** - * 呼号--网格对应表。以呼号查网格,条件是呼号和网格都对应的上。 - * 此函数的目的是,为了更新对应表的数据库 - * - * @param callsign 呼号 - * @param grid 网格 - * @return 是否有对应的网格 - */ - public static boolean getCallsignHasGrid(String callsign, String grid) { - if (!callsignAndGrids.containsKey(callsign)) return false;//说明根本没有这个呼号 - String s = callsignAndGrids.get(callsign); - if (s == null) return false; - return s.equals(grid); - } - - public static String getGridByCallsign(String callsign, DatabaseOpr db) { - String s = callsign.replace("<", "").replace(">", ""); - if (getCallsignHasGrid(s)) { - return callsignAndGrids.get(s); - } else { - db.getCallsignQTH(callsign); - return ""; - } - } - - /** - * 遍历呼号--网格对应表,生成HTML - * - * @return HTML - */ - public static String getCallsignAndGridToHTML() { - StringBuilder result = new StringBuilder(); - int order = 0; - for (String key : callsignAndGrids.keySet()) { - order++; - HtmlContext.tableKeyRow(result,order % 2 != 0,key,callsignAndGrids.get(key)); - } - return result.toString(); - } - - public static synchronized void deleteArrayListMore(ArrayList list) { - if (list.size() > GeneralVariables.MESSAGE_COUNT) { - while (list.size() > GeneralVariables.MESSAGE_COUNT) { - list.remove(0); - } - } - } - - /** - * 判断是否为整数 - * - * @param str 传入的字符串 - * @return 是整数返回true, 否则返回false - */ - - public static boolean isInteger(String str) { - if (str != null && !"".equals(str.trim())) - return str.matches("^[0-9]*$"); - else - return false; - } - - /** - * 输出音频的数据类型,网络模式不可用 - */ - public enum AudioOutputBitMode{ - Float32, - Int16 - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java deleted file mode 100644 index 5a0d7f8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java +++ /dev/null @@ -1,660 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * FT8CN程序的主Activity。本APP采用Fragment框架实现,每个Fragment实现不同的功能。 - * ----2022.5.6----- - * 主要完成以下功能: - * 1.生成MainViewModel实例。MainViewModel是用于整个生存周期,用于录音、解析等功能。 - * 2.录音、存储的权限申请。 - * 3.实现Fragment的导航管理。 - * 4.USB串口连接后的提示 - * @author BG7YOZ - * @date 2022.5.6 - */ - - -import android.Manifest; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.media.AudioManager; -import android.os.Build; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.WindowManager; -import android.view.animation.AnimationUtils; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.lifecycle.Observer; -import androidx.navigation.NavController; -import androidx.navigation.fragment.NavHostFragment; -import androidx.navigation.ui.NavigationUI; - -import com.bg7yoz.ft8cn.bluetooth.BluetoothStateBroadcastReceive; -import com.bg7yoz.ft8cn.callsign.CallsignDatabase; -import com.bg7yoz.ft8cn.connector.CableSerialPort; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.database.OnAfterQueryConfig; -import com.bg7yoz.ft8cn.database.OperationBand; -import com.bg7yoz.ft8cn.databinding.MainActivityBinding; -import com.bg7yoz.ft8cn.floatview.FloatView; -import com.bg7yoz.ft8cn.floatview.FloatViewButton; -import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.bg7yoz.ft8cn.ui.FreqDialog; -import com.bg7yoz.ft8cn.ui.SetVolumeDialog; -import com.bg7yoz.ft8cn.ui.ToastMessage; -import com.google.android.material.bottomnavigation.BottomNavigationView; - -import java.io.File; -import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - - -public class MainActivity extends AppCompatActivity { - private BluetoothStateBroadcastReceive mReceive; - private static final String TAG = "MainActivity"; - private MainViewModel mainViewModel; - private NavController navController; - private static boolean animatorRunned = false; - //private boolean animationEnd = false; - - private MainActivityBinding binding; - private FloatView floatView; - - - String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO - , Manifest.permission.ACCESS_COARSE_LOCATION - , Manifest.permission.ACCESS_WIFI_STATE - , Manifest.permission.BLUETOOTH - , Manifest.permission.BLUETOOTH_ADMIN - , Manifest.permission.MODIFY_AUDIO_SETTINGS - , Manifest.permission.WAKE_LOCK - , Manifest.permission.ACCESS_FINE_LOCATION}; - List mPermissionList = new ArrayList<>(); - - private static final int PERMISSION_REQUEST = 1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - permissions = new String[]{Manifest.permission.RECORD_AUDIO - , Manifest.permission.ACCESS_COARSE_LOCATION - , Manifest.permission.ACCESS_WIFI_STATE - , Manifest.permission.BLUETOOTH - , Manifest.permission.BLUETOOTH_ADMIN - , Manifest.permission.BLUETOOTH_CONNECT - , Manifest.permission.MODIFY_AUDIO_SETTINGS - , Manifest.permission.WAKE_LOCK - , Manifest.permission.ACCESS_FINE_LOCATION}; - } - - checkPermission(); - //全屏 - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN - , WindowManager.LayoutParams.FLAG_FULLSCREEN); - - //禁止休眠 - getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - , WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - super.onCreate(savedInstanceState); - GeneralVariables.getInstance().setMainContext(getApplicationContext()); - - //判断是不是简体中文 - GeneralVariables.isTraditionalChinese = - getResources().getConfiguration().locale.getDisplayCountry().equals("中國"); - - //确定是不是中国、香港、澳门、台湾 - GeneralVariables.isChina = (getResources().getConfiguration().locale - .getLanguage().toUpperCase().startsWith("ZH")); - - mainViewModel = MainViewModel.getInstance(this); - binding = MainActivityBinding.inflate(getLayoutInflater()); - binding.initDataLayout.setVisibility(View.VISIBLE);//显示LOG页面 - setContentView(binding.getRoot()); - - - ToastMessage.getInstance(); - registerBluetoothReceiver();//注册蓝牙动作改变的广播 - if (mainViewModel.isBTConnected()) { - mainViewModel.setBlueToothOn(); - } - - - //观察DEBUG信息 - GeneralVariables.mutableDebugMessage.observe(this, new Observer() { - @Override - public void onChanged(String s) { - if (s.length() > 1) { - binding.debugLayout.setVisibility(View.VISIBLE); - } else { - binding.debugLayout.setVisibility(View.GONE); - } - binding.debugMessageTextView.setText(s); - } - }); - binding.debugLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - binding.debugLayout.setVisibility(View.GONE); - } - }); - - - mainViewModel.mutableIsRecording.observe(this, new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - binding.utcProgressBar.setVisibility(View.VISIBLE); - } else { - binding.utcProgressBar.setVisibility(View.GONE); - } - } - }); - //观察时钟的变化,显示进度条 - mainViewModel.timerSec.observe(this, new Observer() { - @Override - public void onChanged(Long aLong) { - if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential() - && mainViewModel.ft8TransmitSignal.isActivated()) { - binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color)); - } else { - binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color)); - } - binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15)); - } - }); - - //添加点击发射消息提示窗口点击关闭动作 - binding.transmittingLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - binding.transmittingLayout.setVisibility(View.GONE); - } - }); - //清空缓存中的文件 - //deleteFolderFile(this.getCacheDir().getPath()); - - //Log.e(TAG, this.getCacheDir().getPath()); - - //用于Fragment的导航。 - NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); - assert navHostFragment != null;//断言不为空 - navController = navHostFragment.getNavController(); - - NavigationUI.setupWithNavController(binding.navView, navController); - //此处增加回调是因为当APP主动navigation后,无法回到解码的界面 - binding.navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - //Log.e(TAG, "onNavigationItemSelected: "+item.toString() ); - navController.navigate(item.getItemId()); - //binding.navView.setLabelFor(item.getItemId()); - return true; - } - }); - - //FT8CN Ver %s\nBG7YOZ\n%s - binding.welcomTextView.setText(String.format(getString(R.string.version_info) - , GeneralVariables.VERSION, GeneralVariables.BUILD_DATE)); - - floatView = new FloatView(this, 32); - if (!animatorRunned) { - animationImage(); - animatorRunned = true; - } else { - binding.initDataLayout.setVisibility(View.GONE); - - InitFloatView(); - } - //初始化数据 - InitData(); - - - //观察是不是flex radio - - mainViewModel.mutableIsFlexRadio.observe(this, new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - //if (floatView==null) return; - if (aBoolean) { - //添加flex配置按钮 - floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon - , new View.OnClickListener() { - @Override - public void onClick(View view) { - - navController.navigate(R.id.flexRadioInfoFragment); - -// if (mainViewModel.baseRig != null) { -// if (mainViewModel.baseRig.isConnected()) { -// ToastMessage.show("flex connected"); -// }else { -// ToastMessage.show("flex disconnected"); -// } -// }else { -// ToastMessage.show("rig is null"); -// } - - } - }); - } else {//删除flex配置按钮 - floatView.deleteButtonByName("flex_radio"); - } - } - }); - - - //关闭串口设备列表按钮 - binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - binding.selectSerialPortLayout.setVisibility(View.GONE); - } - }); - - //观察串口设备列表的变化 - mainViewModel.mutableSerialPorts.observe(this, new Observer>() { - @Override - public void onChanged(ArrayList serialPorts) { - setSelectUsbDevice(); - } - }); - - //列USB设备列表 - mainViewModel.getUsbDevice(); - - - //设置发射消息框的动画 - binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this - , R.anim.view_blink)); - //观察发射的状态 - mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, - new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - binding.transmittingLayout.setVisibility(View.VISIBLE); - } else { - binding.transmittingLayout.setVisibility(View.GONE); - } - } - }); - - //观察发射内容的变化 - mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this, - new Observer() { - @Override - public void onChanged(String s) { - binding.transmittingMessageTextView.setText(s); - } - }); - } - - - /** - * 添加浮动按钮 - */ - - private void InitFloatView() { - //floatView = new FloatView(this, 32); - - binding.container.addView(floatView); - floatView.setButtonMargin(0); - floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT); - - floatView.setButtonBackgroundResourceId(R.drawable.float_button_style); - //动态添加按钮,建议使用静态的ID,静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置 - floatView.addButton(R.id.float_nav, "float_nav", R.drawable.ic_baseline_fullscreen_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - FloatViewButton button = floatView.getButtonByName("float_nav"); - if (binding.navView.getVisibility() == View.VISIBLE) { - binding.navView.setVisibility(View.GONE); - if (button != null) { - button.setImageResource(R.drawable.ic_baseline_fullscreen_exit_24); - } - } else { - binding.navView.setVisibility(View.VISIBLE); - if (button != null) { - button.setImageResource(R.drawable.ic_baseline_fullscreen_24); - } - } - } - }); - floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - new FreqDialog(binding.container.getContext(), mainViewModel).show(); - } - }); - - floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - new SetVolumeDialog(binding.container.getContext(), mainViewModel).show(); - } - }); - //打开网格追踪 - floatView.addButton(R.id.grid_tracker, "grid_tracker", R.drawable.ic_baseline_grid_tracker_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(getApplicationContext(), GridTrackerMainActivity.class); - startActivity(intent); - } - }); - - -// floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon -// , new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// navController.navigate(R.id.flexRadioInfoFragment); -// } -// }); - - floatView.initLocation(); - } - - /** - * 初始化一些数据 - */ - private void InitData() { - if (mainViewModel.configIsLoaded) return;//如果数据已经读取一遍了,就不用再读取了。 - - //读取波段数据 - if (mainViewModel.operationBand == null) { - mainViewModel.operationBand = OperationBand.getInstance(getBaseContext()); - } - - mainViewModel.databaseOpr.getQslDxccToMap(); - - //获取所有的配置参数 - mainViewModel.databaseOpr.getAllConfigParameter(new OnAfterQueryConfig() { - @Override - public void doOnBeforeQueryConfig(String KeyName) { - - } - - @Override - public void doOnAfterQueryConfig(String KeyName, String Value) { - mainViewModel.configIsLoaded = true; - //此处梅登海德已经通过数据库得到了,但是如果GPS能获取到,还是用GPS的 - String grid = MaidenheadGrid.getMyMaidenheadGrid(getApplicationContext()); - if (!grid.equals("")) {//说明获取到了GPS数据 - GeneralVariables.setMyMaidenheadGrid(grid); - //写到数据库中 - mainViewModel.databaseOpr.writeConfig("grid", grid, null); - } - - mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay); - //如果呼号、网格为空,就进入设置界面 - if (GeneralVariables.getMyMaidenheadGrid().equals("") - || GeneralVariables.myCallsign.equals("")) { - runOnUiThread(new Runnable() { - @Override - public void run() {//导航到设置页面 - navController.navigate(R.id.menu_nav_config); - } - }); - } - } - }); - - //把历史中通联成功的呼号与网格的对应关系 - new DatabaseOpr.GetCallsignMapGrid(mainViewModel.databaseOpr.getDb()).execute(); - - mainViewModel.getFollowCallsignsFromDataBase(); - //打开呼号位置信息的数据库,目前是以内存数据库方式。 - if (GeneralVariables.callsignDatabase == null) { - GeneralVariables.callsignDatabase = CallsignDatabase.getInstance(getBaseContext(), null, 1); - } - } - - - /** - * 检查权限 - */ - private void checkPermission() { - mPermissionList.clear(); - - //判断哪些权限未授予 - for (String permission : permissions) { - if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { - mPermissionList.add(permission); - } - } - - //判断是否为空 - if (!mPermissionList.isEmpty()) {//请求权限方法 - String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);//将List转为数组 - ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST); - } - } - - - /** - * 响应授权 - * 这里不管用户是否拒绝,都进入首页,不再重复申请权限 - */ - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode != PERMISSION_REQUEST) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - } - - - /** - * 显示串口设备列表 - */ - public void setSelectUsbDevice() { - ArrayList ports = mainViewModel.mutableSerialPorts.getValue(); - binding.selectSerialPortLinearLayout.removeAllViews(); - for (int i = 0; i < ports.size(); i++) {//动态添加串口设备列表 - View layout = LayoutInflater.from(getApplicationContext()) - .inflate(R.layout.select_serial_port_list_view_item, null); - layout.setId(i); - TextView textView = layout.findViewById(R.id.selectSerialPortListViewItemTextView); - textView.setText(ports.get(i).information()); - binding.selectSerialPortLinearLayout.addView(layout); - layout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - //连接电台并做电台的频率设置等操作 - mainViewModel.connectCableRig(getApplicationContext(), ports.get(view.getId())); - binding.selectSerialPortLayout.setVisibility(View.GONE); - } - }); - } - - //选择串口设备弹框 - if ((ports.size() >= 1) && (!mainViewModel.isRigConnected())) { - binding.selectSerialPortLayout.setVisibility(View.VISIBLE); - } else {//说明没有可以识别的驱动,不显示设备弹框 - binding.selectSerialPortLayout.setVisibility(View.GONE); - } - } - - /** - * 删除指定文件夹中的所有文件 - * - * @param filePath 指定的文件夹 - */ - public static void deleteFolderFile(String filePath) { - try { - File file = new File(filePath);//获取SD卡指定路径 - File[] files = file.listFiles();//获取SD卡指定路径下的文件或者文件夹 - for (int i = 0; i < files.length; i++) { - if (files[i].isFile()) {//如果是文件直接删除 - File tempFile = new File(files[i].getPath()); - tempFile.delete(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void animationImage() { - - ObjectAnimator navigationAnimator = ObjectAnimator.ofFloat(binding.navView, "translationY", 200); - navigationAnimator.setDuration(3000); - navigationAnimator.setFloatValues(200, 200, 200, 0); - - - ObjectAnimator hideLogoAnimator = ObjectAnimator.ofFloat(binding.initDataLayout, "alpha", 1f, 1f, 1f, 0); - hideLogoAnimator.setDuration(3000); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(navigationAnimator, hideLogoAnimator); - //animatorSet.playTogether(initPositionStrAnimator, logoAnimator, navigationAnimator, hideLogoAnimator); - animatorSet.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - - } - - @Override - public void onAnimationEnd(Animator animator) { - //animationEnd = true; - binding.initDataLayout.setVisibility(View.GONE); - binding.utcProgressBar.setVisibility(View.VISIBLE); - InitFloatView();//显示浮窗 - //binding.floatView.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - - animatorSet.start(); - } - - - //此方法只有在android:launchMode="singleTask"模式下起作用 - @Override - protected void onNewIntent(Intent intent) { - if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) { - mainViewModel.getUsbDevice(); - } - super.onNewIntent(intent); - } - - - @Override - public void onBackPressed() { - if (navController.getGraph().getStartDestination() == navController.getCurrentDestination().getId()) {//说明是到最后一个页面了 - AlertDialog.Builder builder = new AlertDialog.Builder(this) - .setMessage(getString(R.string.exit_confirmation)) - .setPositiveButton(getString(R.string.exit) - , new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(false); - } - closeThisApp();//退出APP - } - }).setNegativeButton(getString(R.string.cancel) - , new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); - builder.create().show(); - - } else {//退出activity堆栈 - navController.navigateUp(); - //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); - } - } - - private void closeThisApp() { - mainViewModel.ft8TransmitSignal.setActivated(false); - if (mainViewModel.baseRig != null) { - if (mainViewModel.baseRig.getConnector() != null) { - mainViewModel.baseRig.getConnector().disconnect(); - } - } - - mainViewModel.ft8SignalListener.stopListen(); - mainViewModel = null; - System.exit(0); - } - - - /** - * 注册蓝牙动作广播 - */ - private void registerBluetoothReceiver() { - if (mReceive == null) { - mReceive = new BluetoothStateBroadcastReceive(getApplicationContext(), mainViewModel); - } - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); - intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); - intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE); - intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_STATE); - intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); - intentFilter.addAction(BluetoothAdapter.EXTRA_CONNECTION_STATE); - intentFilter.addAction(BluetoothAdapter.EXTRA_STATE); - intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF"); - intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON"); - registerReceiver(mReceive, intentFilter); - } - - /** - * 注销蓝牙动作广播 - */ - private void unregisterBluetoothReceiver() { - if (mReceive != null) { - unregisterReceiver(mReceive); - mReceive = null; - } - } - - @Override - protected void onDestroy() { - unregisterBluetoothReceiver(); - super.onDestroy(); - } - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java deleted file mode 100644 index 2872f40..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java +++ /dev/null @@ -1,921 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * -----2022.5.6----- - * MainViewModel类,用于解码FT8信号以及保存与解码有关的变量数据。生存于APP的整个生命周期。 - * 1.解码的总条数。decoded_counter和mutable_Decoded_Counter。 - * 2.解码消息的列表。消息以Ft8Message展示,列表用ArrayList泛型实现。ft8Messages,mutableFt8MessageList。 - * 3.解码和录音都需要时间同步,也就是以UTC时间的每15秒为一个周期。同步事件的触发由UtcTimer类来实现。 - * 4.当前的UTC时间。timerSec,更新频率(心跳频率)由UtcTimer确定,暂定100毫秒。 - * 5.通过类方法getInstance获取当前的MainViewModel的实例,确保有唯一的实例。 - * 6.用HamAudioRecorder类实现录音,目前只实现录音成文件,然后读取文件的数据给解码模块,后面要改成直接给数组的方式----TO DO--- - * 7.解码采用JNI接口调用原生C语言。调用接口名时ft8cn,由cpp文件夹下的CMakeLists.txt维护。各函数的调用接口在decode_ft8.cpp中。 - * -----2022.5.9----- - * 如果系统没有发射信号,触发器会在每一个周期触发录音动作,因录音开始和结束要浪费一些时间,如果不干预上一个录音的动作,将出现 - * 连续的周期内录音动作重叠,造成第二个录音动作失败。所以,第二个周期的录音开始前,要停止前一个周期的录音,造成的结果就是每一次录音 - * 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右 - *

- * - * @author BG7YOZ - * @date 2022.5.6 - */ - -import static com.bg7yoz.ft8cn.GeneralVariables.getStringFromResource; - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.Context; -import android.media.AudioManager; -import android.os.Handler; -import android.util.Log; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelStoreOwner; - -import com.bg7yoz.ft8cn.callsign.CallsignDatabase; -import com.bg7yoz.ft8cn.callsign.CallsignInfo; -import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; -import com.bg7yoz.ft8cn.connector.BluetoothRigConnector; -import com.bg7yoz.ft8cn.connector.CableConnector; -import com.bg7yoz.ft8cn.connector.CableSerialPort; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.connector.FlexConnector; -import com.bg7yoz.ft8cn.connector.IComWifiConnector; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns; -import com.bg7yoz.ft8cn.database.OperationBand; -import com.bg7yoz.ft8cn.flex.FlexRadio; -import com.bg7yoz.ft8cn.ft8listener.FT8SignalListener; -import com.bg7yoz.ft8cn.ft8listener.OnFt8Listen; -import com.bg7yoz.ft8cn.ft8transmit.FT8TransmitSignal; -import com.bg7yoz.ft8cn.ft8transmit.OnDoTransmitted; -import com.bg7yoz.ft8cn.ft8transmit.OnTransmitSuccess; -import com.bg7yoz.ft8cn.html.LogHttpServer; -import com.bg7yoz.ft8cn.icom.IComWifiRig; -import com.bg7yoz.ft8cn.log.QSLCallsignRecord; -import com.bg7yoz.ft8cn.log.QSLRecord; -import com.bg7yoz.ft8cn.log.SWLQsoList; -import com.bg7yoz.ft8cn.rigs.BaseRig; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.rigs.ElecraftRig; -import com.bg7yoz.ft8cn.rigs.Flex6000Rig; -import com.bg7yoz.ft8cn.rigs.FlexNetworkRig; -import com.bg7yoz.ft8cn.rigs.GuoHeQ900Rig; -import com.bg7yoz.ft8cn.rigs.IcomRig; -import com.bg7yoz.ft8cn.rigs.InstructionSet; -import com.bg7yoz.ft8cn.rigs.KenwoodKT90Rig; -import com.bg7yoz.ft8cn.rigs.KenwoodTS2000Rig; -import com.bg7yoz.ft8cn.rigs.KenwoodTS590Rig; -import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; -import com.bg7yoz.ft8cn.rigs.Wolf_sdr_450Rig; -import com.bg7yoz.ft8cn.rigs.XieGu6100Rig; -import com.bg7yoz.ft8cn.rigs.XieGuRig; -import com.bg7yoz.ft8cn.rigs.Yaesu2Rig; -import com.bg7yoz.ft8cn.rigs.Yaesu38Rig; -import com.bg7yoz.ft8cn.rigs.Yaesu38_450Rig; -import com.bg7yoz.ft8cn.rigs.Yaesu39Rig; -import com.bg7yoz.ft8cn.rigs.YaesuDX10Rig; -import com.bg7yoz.ft8cn.spectrum.SpectrumListener; -import com.bg7yoz.ft8cn.timer.OnUtcTimer; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.bg7yoz.ft8cn.ui.ToastMessage; -import com.bg7yoz.ft8cn.wave.HamRecorder; -import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - - -public class MainViewModel extends ViewModel { - String TAG = "ft8cn MainViewModel"; - public boolean configIsLoaded = false; - - private static MainViewModel viewModel = null;//当前存在的实例。 - //public static Application application; - - - //public int decoded_counter = 0;//解码的总条数 - public final ArrayList ft8Messages = new ArrayList<>();//消息列表 - public UtcTimer utcTimer;//同步触发动作的计时器。 - - //public boolean showTrackerInfo=true; - - //public CallsignDatabase callsignDatabase = null;//呼号信息的数据库 - public DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库 - - - public MutableLiveData mutable_Decoded_Counter = new MutableLiveData<>();//解码的总条数 - public int currentDecodeCount = 0;//本次解码的条数 - public MutableLiveData> mutableFt8MessageList = new MutableLiveData<>();//消息列表 - public MutableLiveData timerSec = new MutableLiveData<>();//当前UTC时间。更新频率由UtcTimer确定,未触发时约100毫秒。 - public MutableLiveData mutableIsRecording = new MutableLiveData<>();//是否处于录音状态 - public MutableLiveData mutableHamRecordIsRunning = new MutableLiveData<>();//HamRecord是否运转 - public MutableLiveData mutableTimerOffset = new MutableLiveData<>();//本周期的时间延迟 - public MutableLiveData mutableIsDecoding = new MutableLiveData<>();//会触发频谱图中的标记动作 - public ArrayList currentMessages = null;//本周期解码的消息(用于画到频谱上) - - public MutableLiveData mutableIsFlexRadio = new MutableLiveData<>();//是不是flex电台 - - private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool(); - private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool(); - private final GetQTHRunnable getQTHRunnable = new GetQTHRunnable(this); - private final SendWaveDataRunnable sendWaveDataRunnable = new SendWaveDataRunnable(); - - - public HamRecorder hamRecorder;//用于录音的对象 - public FT8SignalListener ft8SignalListener;//用于监听FT8信号并解码的对象 - public FT8TransmitSignal ft8TransmitSignal;//用于发射信号用的对象 - public SpectrumListener spectrumListener;//用于画频谱的对象 - public boolean markMessage = true;//是否标记消息开关 - - //控制电台的方式 - public OperationBand operationBand = null; - - private SWLQsoList swlQsoList = new SWLQsoList();//用于记录SWL的QSO对象,对SWL QSO做判断,防止重复。 - - - public MutableLiveData> mutableSerialPorts = new MutableLiveData<>(); - private ArrayList serialPorts;//串口列表 - public BaseRig baseRig;//电台 - private final OnRigStateChanged onRigStateChanged = new OnRigStateChanged() { - @Override - public void onDisconnected() { - //与电台连接中断 - ToastMessage.show(getStringFromResource(R.string.disconnect_rig)); - } - - @Override - public void onConnected() { - //与电台建立连接 - ToastMessage.show(getStringFromResource(R.string.connected_rig)); - } - - @Override - public void onPttChanged(boolean isOn) { - - } - - @Override - public void onFreqChanged(long freq) { - //当前频率:%s - ToastMessage.show(String.format(getStringFromResource(R.string.current_frequency) - , BaseRigOperation.getFrequencyAllInfo(freq))); - //把频率的变化写回到全局变量中 - GeneralVariables.band = freq; - GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(freq); - GeneralVariables.mutableBandChange.postValue(GeneralVariables.bandListIndex); - - databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 - - } - - @Override - public void onRunError(String message) { - //与电台通讯出现错误, - ToastMessage.show(String.format(getStringFromResource(R.string.radio_communication_error) - , message)); - } - }; - - //发射信号用的消息列表 - //public ArrayList transmitMessages = new ArrayList<>(); - //public MutableLiveData> mutableTransmitMessages = new MutableLiveData<>(); - public MutableLiveData mutableTransmitMessagesCount = new MutableLiveData<>(); - - - public boolean deNoise = false;//在频谱中抑制噪声 - - //*********日志查询需要的变量******************** - public boolean logListShowCallsign = false;//在日志查询列表的表现形式 - public String queryKey = "";//查询的关键字 - public int queryFilter = 0;//过滤,0全部,1,确认,2,未确认 - public MutableLiveData mutableQueryFilter = new MutableLiveData<>(); - public ArrayList callsignRecords = new ArrayList<>(); - //public ArrayList qslRecords=new ArrayList<>(); - //******************************************** - //关注呼号的列表 - //public ArrayList followCallsign = new ArrayList<>(); - - - //日志管理HTTP SERVER - private final LogHttpServer httpServer; - - /** - * 获取MainViewModel的实例,确保存在唯一的MainViewModel实例,该实例在APP的全部生存周期中。 - * - * @param owner ViewModelStoreOwner 所有者,一般为Activity或Fragment。 - * @return MainViewModel 返回一个MainViewModel实例 - */ - public static MainViewModel getInstance(ViewModelStoreOwner owner) { - if (viewModel == null) { - viewModel = new ViewModelProvider(owner).get(MainViewModel.class); - } - return viewModel; - } - - /** - * 获取消息列表中指定的消息 - * - * @param position 在Mutable类型的列表中的位置 - * @return 返回一个Ft8Message类型的解码后的信息 - */ - public Ft8Message getFt8Message(int position) { - return Objects.requireNonNull(ft8Messages.get(position)); - } - - /** - * MainViewModel的构造函数主要完成一下事情: - * 1.创建与UTC同步的时钟,时钟是UtcTimer类,内核是用Timer和TimerTask实现的。回调函数是多线程的,要考虑线程安全的问题。 - * 2.创建Mutable型的解码消息列表。 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - public MainViewModel() { - - //获取配置信息。 - databaseOpr = DatabaseOpr.getInstance(GeneralVariables.getMainContext() - , "data.db"); - mutableIsDecoding.postValue(false);//解码状态 - //创录音对象 - hamRecorder = new HamRecorder(null); - hamRecorder.startRecord(); - - mutableIsFlexRadio.setValue(false); - - //创建用于显示时间的计时器 - utcTimer = new UtcTimer(10, false, new OnUtcTimer() { - @Override - public void doHeartBeatTimer(long utc) {//不触发时的时钟信息 - - } - - @Override - public void doOnSecTimer(long utc) {//当指定间隔时触发时 - timerSec.postValue(utc);//发送当前UTC时间 - mutableIsRecording.postValue(hamRecorder.isRunning()); - mutableHamRecordIsRunning.postValue(hamRecorder.isRunning());//发送当前计时器状态 - } - }); - utcTimer.start();//启动计时器 - - //同步一下时间。microsoft的NTP服务器 - UtcTimer.syncTime(null); - - mutableFt8MessageList.setValue(ft8Messages); - - //创建监听对象,回调中的动作用于处理解码、发射、关注的呼号列表添加等操作 - ft8SignalListener = new FT8SignalListener(databaseOpr, new OnFt8Listen() { - @Override - public void beforeListen(long utc) { - mutableIsDecoding.postValue(true); - } - - @Override - public void afterDecode(long utc, float time_sec, int sequential - , ArrayList messages, boolean isDeep) { - if (messages.size() == 0) return;//没有解码出消息,不触发动作 - - synchronized (ft8Messages) { - ft8Messages.addAll(messages);//添加消息到列表 - } - GeneralVariables.deleteArrayListMore(ft8Messages);//删除多余的消息,FT8CN限定的可展示消息的总数量 - - mutableFt8MessageList.postValue(ft8Messages);//触发添加消息的动作,让界面能观察到 - mutableTimerOffset.postValue(time_sec);//本次时间偏移量 - - - findIncludedCallsigns(messages);//查找符合条件的消息,放到呼叫列表中 - - //检查发射程序。从消息列表中解析发射的程序 - //超出周期2秒钟,就不应该解析了 - if (!ft8TransmitSignal.isTransmitting() - && (ft8SignalListener.timeSec - + GeneralVariables.pttDelay - + GeneralVariables.transmitDelay <= 2000)) {//考虑网络模式,发射时长是13秒 - ft8TransmitSignal.parseMessageToFunction(messages);//解析消息,并处理 - } - - currentMessages = messages; - - if (isDeep) { - currentDecodeCount += messages.size(); - } else { - currentDecodeCount = messages.size(); - } - - mutableIsDecoding.postValue(false);//解码的状态,会触发频谱图中的标记动作 - - - getQTHRunnable.messages = messages; - getQTHThreadPool.execute(getQTHRunnable);//用线程池的方式查询归属地 - - //此变量也是告诉消息列表变化的 - mutable_Decoded_Counter.postValue( - currentDecodeCount);//告知界面消息的总数量 - - if (GeneralVariables.saveSWLMessage) { - databaseOpr.writeMessage(messages);//把SWL消息写到数据库 - } - //检查QSO of SWL,并保存到SWLQSOTable中的通联列表qsoList中 - if (GeneralVariables.saveSWL_QSO) { - swlQsoList.findSwlQso(messages, ft8Messages, new SWLQsoList.OnFoundSwlQso() { - @Override - public void doFound(QSLRecord record) { - databaseOpr.addSWL_QSO(record);//把SWL QSO保存到数据库 - ToastMessage.show(record.swlQSOInfo()); - } - }); - } - //从列表中查找呼号和网格对应关系,并添加到表中 - getCallsignAndGrid(messages); - } - }); - - ft8SignalListener.setOnWaveDataListener(new FT8SignalListener.OnWaveDataListener() { - @Override - public void getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone) { - hamRecorder.getVoiceData(duration, afterDoneRemove, getVoiceDataDone); - } - }); - - - ft8SignalListener.startListen(); - - //频谱监听对象 - spectrumListener = new SpectrumListener(hamRecorder); - - - //创建发射对象,回调:发射前,发射后、QSL成功后。 - ft8TransmitSignal = new FT8TransmitSignal(databaseOpr, new OnDoTransmitted() { - @Override - public void onBeforeTransmit(Ft8Message message, int functionOder) { - if (GeneralVariables.controlMode == ControlMode.CAT - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - if (baseRig != null) { - if (GeneralVariables.connectMode != ConnectMode.NETWORK) stopSco(); - baseRig.setPTT(true); - } - } - if (ft8TransmitSignal.isActivated()) { - GeneralVariables.transmitMessages.add(message); - //mutableTransmitMessages.postValue(GeneralVariables.transmitMessages); - mutableTransmitMessagesCount.postValue(1); - } - } - - @Override - public void onAfterTransmit(Ft8Message message, int functionOder) { - if (GeneralVariables.controlMode == ControlMode.CAT - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - if (baseRig != null) { - baseRig.setPTT(false); - if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco(); - } - } - } - - @Override - public void onTransmitByWifi(Ft8Message msg) { - if (GeneralVariables.connectMode == ConnectMode.NETWORK) { - if (baseRig != null) { - if (baseRig.isConnected()) { - sendWaveDataRunnable.baseRig = baseRig; - sendWaveDataRunnable.message = msg; - //以线程池的方式执行网络数据包发送 - sendWaveDataThreadPool.execute(sendWaveDataRunnable); - } - } - } - } - }, new OnTransmitSuccess() {//当通联成功时 - @Override - public void doAfterTransmit(QSLRecord qslRecord) { - databaseOpr.addQSL_Callsign(qslRecord);//两个操作,把呼号和QSL记录下来 - if (qslRecord.getToCallsign() != null) {//把通联成功的分区加入到分区列表 - GeneralVariables.callsignDatabase.getCallsignInformation(qslRecord.getToCallsign() - , new OnAfterQueryCallsignLocation() { - @Override - public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { - GeneralVariables.addDxcc(callsignInfo.DXCC); - GeneralVariables.addItuZone(callsignInfo.ITUZone); - GeneralVariables.addCqZone(callsignInfo.CQZone); - } - }); - } - } - }); - - - //打开HTTP SERVER - httpServer = new LogHttpServer(this, LogHttpServer.DEFAULT_PORT); - try { - httpServer.start(); - } catch (IOException e) { - Log.e(TAG, "http server error:" + e.getMessage()); - } - } - - public void setTransmitIsFreeText(boolean isFreeText) { - if (ft8TransmitSignal != null) { - ft8TransmitSignal.setTransmitFreeText(isFreeText); - } - } - - public boolean getTransitIsFreeText() { - if (ft8TransmitSignal != null) { - return ft8TransmitSignal.isTransmitFreeText(); - } - return false; - } - - - /** - * 查找符合条件的消息,放到呼叫列表中 - * - * @param messages 消息 - */ - private synchronized void findIncludedCallsigns(ArrayList messages) { - Log.d(TAG, "findIncludedCallsigns: 查找关注的呼号"); - if (ft8TransmitSignal.isActivated() && ft8TransmitSignal.sequential != UtcTimer.getNowSequential()) { - return; - } - int count = 0; - for (Ft8Message msg : messages) { - //与我的呼号有关,与关注的呼号有关 - if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign) - || msg.getCallsignTo().equals(GeneralVariables.myCallsign) - || GeneralVariables.callsignInFollow(msg.getCallsignFrom()) - || (GeneralVariables.callsignInFollow(msg.getCallsignTo()) && (msg.getCallsignTo() != null)) - || (GeneralVariables.autoFollowCQ && msg.checkIsCQ())) {//是CQ,并且允许关注CQ - //看不是通联成功的呼号的消息 - msg.isQSL_Callsign = GeneralVariables.checkQSLCallsign(msg.getCallsignFrom()); - if (!GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//不在排除呼号前缀的,才加入列表 - count++; - GeneralVariables.transmitMessages.add(msg); - } - } - } - GeneralVariables.deleteArrayListMore(GeneralVariables.transmitMessages);//删除多余的消息 - //mutableTransmitMessages.postValue(GeneralVariables.transmitMessages); - mutableTransmitMessagesCount.postValue(count); - } - - /** - * 清除传输消息列表 - */ - public void clearTransmittingMessage() { - GeneralVariables.transmitMessages.clear(); - mutableTransmitMessagesCount.postValue(0); - } - - - /** - * 从消息列表中查找呼号和网格的对应关系 - * - * @param messages 消息列表 - */ - private void getCallsignAndGrid(ArrayList messages) { - for (Ft8Message msg : messages) { - if (GeneralVariables.checkFun1(msg.extraInfo)) {//检查是不是网格 - //如果内存表中没有,或不一致,就写入数据库中 - if (!GeneralVariables.getCallsignHasGrid(msg.getCallsignFrom(), msg.maidenGrid)) { - databaseOpr.addCallsignQTH(msg.getCallsignFrom(), msg.maidenGrid);//写数据库 - } - GeneralVariables.addCallsignAndGrid(msg.getCallsignFrom(), msg.maidenGrid); - } - } - } - - /** - * 清除消息列表 - */ - public void clearFt8MessageList() { - ft8Messages.clear(); - mutable_Decoded_Counter.postValue(ft8Messages.size()); - mutableFt8MessageList.postValue(ft8Messages); - } - - - /** - * 删除单个文件 - * - * @param fileName 要删除的文件的文件名 - */ - public static void deleteFile(String fileName) { - File file = new File(fileName); - // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除 - if (file.exists() && file.isFile()) { - file.delete(); - } - } - - /** - * 向关注的呼号列表添加呼号 - * - * @param callsign 呼号 - */ - public void addFollowCallsign(String callsign) { - if (!GeneralVariables.followCallsign.contains(callsign)) { - GeneralVariables.followCallsign.add(callsign); - databaseOpr.addFollowCallsign(callsign); - } - } - - - /** - * 从数据库中获取关注的呼号列表 - */ - public void getFollowCallsignsFromDataBase() { - databaseOpr.getFollowCallsigns(new OnAfterQueryFollowCallsigns() { - @Override - public void doOnAfterQueryFollowCallsigns(ArrayList callsigns) { - for (String s : callsigns) { - if (!GeneralVariables.followCallsign.contains(s)) { - GeneralVariables.followCallsign.add(s); - } - } - } - }); - } - - - /** - * 设置操作载波频率。如果电台没有连接,就有操作 - */ - public void setOperationBand() { - if (!isRigConnected()) { - return; - } - - //先设置上边带,再设置频率 - baseRig.setUsbModeToRig();//设置上边带 - - //此处延迟1秒发送第二个指令,是防止协谷X6100断开连接的问题 - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - baseRig.setFreq(GeneralVariables.band);//设置频率 - baseRig.setFreqToRig(); - } - }, 800); - } - - public void setCivAddress() { - if (baseRig != null) { - baseRig.setCivAddress(GeneralVariables.civAddress); - } - } - - public void setControlMode() { - if (baseRig != null) { - baseRig.setControlMode(GeneralVariables.controlMode); - } - } - - - /** - * 通过USB连接电台 - * - * @param context context - * @param port 串口 - */ - public void connectCableRig(Context context, CableSerialPort.SerialPort port) { - if (GeneralVariables.controlMode == ControlMode.VOX) {//如果当前是VOX,就改成CAT模式 - GeneralVariables.controlMode = ControlMode.CAT; - } - connectRig(); - - if (baseRig == null) { - return; - } - baseRig.setControlMode(GeneralVariables.controlMode); - CableConnector connector = new CableConnector(context, port, GeneralVariables.baudRate - , GeneralVariables.controlMode); - baseRig.setOnRigStateChanged(onRigStateChanged); - baseRig.setConnector(connector); - connector.connect(); - - //晚1秒钟设置模式,防止有的电台反应不过来 - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - setOperationBand();//设置载波频率 - } - }, 1000); - - } - - public void connectBluetoothRig(Context context, BluetoothDevice device) { - GeneralVariables.controlMode = ControlMode.CAT;//蓝牙控制模式,只能是CAT控制 - connectRig(); - if (baseRig == null) { - return; - } - baseRig.setControlMode(GeneralVariables.controlMode); - BluetoothRigConnector connector = BluetoothRigConnector.getInstance(context, device.getAddress() - , GeneralVariables.controlMode); - baseRig.setOnRigStateChanged(onRigStateChanged); - baseRig.setConnector(connector); - - new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的,等2秒再设置频率 - @Override - public void run() { - setOperationBand();//设置载波频率 - } - }, 5000); - } - - public void connectIComWifiRig(Context context, IComWifiRig iComWifiRig) { - if (GeneralVariables.connectMode == ConnectMode.NETWORK) { - if (baseRig != null) { - if (baseRig.getConnector() != null) { - baseRig.getConnector().disconnect(); - } - } - } - - GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式 - IComWifiConnector iComWifiConnector = new IComWifiConnector(GeneralVariables.controlMode - , iComWifiRig); - iComWifiConnector.setOnWifiDataReceived(new IComWifiConnector.OnWifiDataReceived() { - @Override - public void OnWaveReceived(int bufferLen, float[] buffer) { - hamRecorder.doOnWaveDataReceived(bufferLen, buffer); - } - - @Override - public void OnCivReceived(byte[] data) { - - } - }); - iComWifiConnector.connect(); - connectRig(); - - baseRig.setControlMode(GeneralVariables.controlMode); - baseRig.setOnRigStateChanged(onRigStateChanged); - baseRig.setConnector(iComWifiConnector); -// - new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的,等2秒再设置频率 - @Override - public void run() { - setOperationBand();//设置载波频率 - } - }, 1000); - } - - /** - * 连接到flexRadio - * - * @param context context - * @param flexRadio flexRadio对象 - */ - public void connectFlexRadioRig(Context context, FlexRadio flexRadio) { - if (GeneralVariables.connectMode == ConnectMode.NETWORK) { - if (baseRig != null) { - if (baseRig.getConnector() != null) { - baseRig.getConnector().disconnect(); - } - } - } - GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式 - FlexConnector flexConnector = new FlexConnector(context, flexRadio, GeneralVariables.controlMode); - flexConnector.setOnWaveDataReceived(new FlexConnector.OnWaveDataReceived() { - @Override - public void OnDataReceived(int bufferLen, float[] buffer) { - hamRecorder.doOnWaveDataReceived(bufferLen, buffer); - } - }); - flexConnector.connect(); - connectRig(); - - baseRig.setOnRigStateChanged(onRigStateChanged); - baseRig.setConnector(flexConnector); -// - new Handler().postDelayed(new Runnable() {//连接是需要时间的,等2秒再设置频率 - @Override - public void run() { - setOperationBand();//设置载波频率 - } - }, 3000); - } - - - /** - * 根据指令集创建不同型号的电台 - */ - private void connectRig() { - if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK) - || (GeneralVariables.instructionSet == InstructionSet.ICOM - && GeneralVariables.connectMode == ConnectMode.NETWORK)) { - hamRecorder.setDataFromLan(); - } else { - hamRecorder.setDataFromMic(); - } - baseRig = null; - //此处判断是用什么类型的电台,ICOM,YAESU 2,YAESU 3 - switch (GeneralVariables.instructionSet) { - case InstructionSet.ICOM: - baseRig = new IcomRig(GeneralVariables.civAddress); - break; - case InstructionSet.YAESU_2: - baseRig = new Yaesu2Rig(); - break; - case InstructionSet.YAESU_3_9: - baseRig = new Yaesu39Rig();//yaesu3代指令,9位频率 - break; - case InstructionSet.YAESU_3_8: - baseRig = new Yaesu38Rig();//yaesu3代指令,8位频率 - break; - case InstructionSet.YAESU_3_450: - baseRig = new Yaesu38_450Rig();//yaesu3代指令,8位频率 - break; - case InstructionSet.KENWOOD_TK90: - baseRig = new KenwoodKT90Rig();//建伍TK90 - break; - case InstructionSet.YAESU_DX10: - baseRig = new YaesuDX10Rig();//YAESU DX10 DX101 - break; - case InstructionSet.KENWOOD_TS590: - baseRig = new KenwoodTS590Rig();//KENWOOD TS590 - break; - case InstructionSet.GUOHE_Q900: - baseRig = new GuoHeQ900Rig();//国赫Q900 - break; - case InstructionSet.XIEGUG90S://协谷,USB模式 - baseRig = new XieGuRig(GeneralVariables.civAddress);//协谷G90S - break; - case InstructionSet.ELECRAFT: - baseRig = new ElecraftRig();//ELECRAFT - break; - case InstructionSet.FLEX_CABLE: - baseRig = new Flex6000Rig();//FLEX6000 - break; - case InstructionSet.FLEX_NETWORK: - baseRig = new FlexNetworkRig(); - break; - case InstructionSet.XIEGU_6100: - baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100 - break; - case InstructionSet.KENWOOD_TS2000: - baseRig = new KenwoodTS2000Rig();//建伍TS2000 - break; - case InstructionSet.WOLF_SDR_DIGU: - baseRig = new Wolf_sdr_450Rig(false); - break; - case InstructionSet.WOLF_SDR_USB: - baseRig = new Wolf_sdr_450Rig(true); - break; - } - - mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK); - - } - - - /** - * 检察电台是否处于连接状态,两种情况:rigBaseClass没建立,串口没连接成功 - * - * @return 是否连接 - */ - public boolean isRigConnected() { - if (baseRig == null) { - return false; - } else { - return baseRig.isConnected(); - } - } - - /** - * 获取串口设备列表 - */ - public void getUsbDevice() { - serialPorts = - CableSerialPort.listSerialPorts(GeneralVariables.getMainContext()); - mutableSerialPorts.postValue(serialPorts); - } - - - public void startSco() { - AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() - .getSystemService(Context.AUDIO_SERVICE); - if (audioManager == null) return; - if (!audioManager.isBluetoothScoAvailableOffCall()) { - //蓝牙设备不支持录音 - ToastMessage.show(getStringFromResource(R.string.does_not_support_recording)); - return; - } - audioManager.setBluetoothScoOn(true); - audioManager.startBluetoothSco();//71毫秒 - audioManager.setSpeakerphoneOn(false);//进入耳机模式 - } - - public void stopSco() { - AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() - .getSystemService(Context.AUDIO_SERVICE); - if (audioManager == null) return; - if (audioManager.isBluetoothScoOn()) { - audioManager.setBluetoothScoOn(false); - audioManager.stopBluetoothSco(); - audioManager.setSpeakerphoneOn(true);//退出耳机模式 - } - - } - - - public void setBlueToothOn() { - AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() - .getSystemService(Context.AUDIO_SERVICE); - if (audioManager == null) return; - if (!audioManager.isBluetoothScoAvailableOffCall()) { - //蓝牙设备不支持录音 - ToastMessage.show(getStringFromResource(R.string.does_not_support_recording)); - } - - /* - 播放音乐的对应的就是MODE_NORMAL, 如果使用外放播则调用audioManager.setSpeakerphoneOn(true)即可. - 若使用耳机和听筒,则需要先设置模式为MODE_IN_CALL(3.0以前)或MODE_IN_COMMUNICATION(3.0以后). - */ - audioManager.setMode(AudioManager.MODE_NORMAL);//178毫秒 - audioManager.setBluetoothScoOn(true); - audioManager.stopBluetoothSco(); - audioManager.startBluetoothSco();//71毫秒 - audioManager.setSpeakerphoneOn(false);//进入耳机模式 - - //进入到蓝牙耳机模式 - ToastMessage.show(getStringFromResource(R.string.bluetooth_headset_mode)); - - } - - public void setBlueToothOff() { - - AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() - .getSystemService(Context.AUDIO_SERVICE); - if (audioManager == null) return; - if (audioManager.isBluetoothScoOn()) { - audioManager.setMode(AudioManager.MODE_NORMAL); - audioManager.setBluetoothScoOn(false); - audioManager.stopBluetoothSco(); - audioManager.setSpeakerphoneOn(true);//退出耳机模式 - } - //离开蓝牙耳机模式 - ToastMessage.show(getStringFromResource(R.string.bluetooth_Headset_mode_cancelled)); - - } - - - /** - * 查询蓝牙是否连接 - * - * @return 是否 - */ - @SuppressLint("MissingPermission") - public boolean isBTConnected() { - BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter(); - if (blueAdapter == null) return false; - - //蓝牙头戴式耳机,支持语音输入输出 - int headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); - int a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP); - return headset == BluetoothAdapter.STATE_CONNECTED || a2dp == BluetoothAdapter.STATE_CONNECTED; - } - - private static class GetQTHRunnable implements Runnable { - MainViewModel mainViewModel; - ArrayList messages; - - public GetQTHRunnable(MainViewModel mainViewModel) { - this.mainViewModel = mainViewModel; - } - - - @Override - public void run() { - CallsignDatabase.getMessagesLocation( - GeneralVariables.callsignDatabase.getDb(), messages); - mainViewModel.mutableFt8MessageList.postValue(mainViewModel.ft8Messages); - } - } - - private static class SendWaveDataRunnable implements Runnable { - BaseRig baseRig; - //float[] data; - Ft8Message message; - - @Override - public void run() { - if (baseRig != null && message != null) { - baseRig.sendWaveData(message);//实际生成的数据是12.64+0.04,0.04是生成的0数据 - } - } - } - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java deleted file mode 100644 index e556f62..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.bg7yoz.ft8cn; -/** - * 呼号的哈希码列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import java.util.HashMap; - -public class MessageHashMap extends HashMap { - private static final String TAG = "MessageHashMap"; - - /** - * 添加呼号和哈希码到列表 - * - * @param hashCode 哈希码 - * @param callsign 呼号 - * @return false说明已经存在了 - */ - public synchronized void addHash(long hashCode, String callsign) { - //if (callsign.length()<2){return;} - //if (){return;} - if (callsign.equals("CQ")||callsign.equals("QRZ")||callsign.equals("DE")){ - return; - } - if (hashCode == 0 || checkHash(hashCode)|| callsign.charAt(0) == '<') { - return; - } - Log.d(TAG, String.format("addHash: callsign:%s ,hash:%x",callsign,hashCode )); - put(hashCode,callsign); - } - - //检查是否存在这个hash码 - public boolean checkHash(long hashCode) { - return get(hashCode)!=null; -// for (HashStruct hash : this) { -// if (hash.hashCode == hashCode) { -// return true; -// } -// } -// return false; - } - - //通过哈希码查呼号 - public synchronized String getCallsign(long[] hashCode) { - for (long l : hashCode) { - if (checkHash(l)) { - return String.format("<%s>", get(l)); - } - } - return "<...>"; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java deleted file mode 100644 index bfc10bd..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.bg7yoz.ft8cn.bluetooth; - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.ParcelUuid; - -import com.bg7yoz.ft8cn.BuildConfig; - -/** - * 与蓝牙有关的常量 - */ - -public class BluetoothConstants { - - // values have to be globally unique - static final String INTENT_ACTION_DISCONNECT = BuildConfig.APPLICATION_ID + ".Disconnect"; - static final String NOTIFICATION_CHANNEL = BuildConfig.APPLICATION_ID + ".Channel"; - static final String INTENT_CLASS_MAIN_ACTIVITY = BuildConfig.APPLICATION_ID + ".MainActivity"; - - // values have to be unique within each app - static final int NOTIFY_MANAGER_START_FOREGROUND_SERVICE = 1001; - - - public static boolean checkBluetoothIsOpen(){ - BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter(); - if (adapter==null){ - return false; - }else { - return adapter.isEnabled(); - } - } - - public static boolean checkIsSpp(BluetoothDevice device) { - @SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids(); - - if (parcelUuids != null) { - for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的 - if (parcelUuids[i].getUuid().toString().toUpperCase().equals("00001101-0000-1000-8000-00805F9B34FB")) { - return true; - } - } - } - return false; - } - - public static boolean checkIsHeadSet(BluetoothDevice device){ - @SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids(); - boolean audioSinkService=false; - boolean handsFreeService=false; - if (parcelUuids != null) { - for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的 - if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000111e-0000-1000-8000-00805f9b34fb")) { - handsFreeService=true; - } - if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000110b-0000-1000-8000-00805f9b34fb")) { - audioSinkService=true; - } - } - } - return audioSinkService&&handsFreeService; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java deleted file mode 100644 index 55ad4c2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.bg7yoz.ft8cn.bluetooth; - -/** - * 蓝牙串口的回调接口 - * BG7YOZ - * 2023-03 - */ -public interface BluetoothSerialListener { - void onSerialConnect (); - void onSerialConnectError (Exception e); - void onSerialRead (byte[] data); - void onSerialIoError (Exception e); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java deleted file mode 100644 index 6d70f42..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.bg7yoz.ft8cn.bluetooth; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; - -import androidx.annotation.Nullable; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.Queue; - -/** - * 蓝牙串口有关的服务 - * BG7YOZ - * 2023-03 - */ -public class BluetoothSerialService extends Service implements BluetoothSerialListener { - - public class SerialBinder extends Binder { - public BluetoothSerialService getService() { return BluetoothSerialService.this; } - } - - private enum QueueType {Connect, ConnectError, Read, IoError} - - private static class QueueItem { - QueueType type; - byte[] data; - Exception e; - - QueueItem(QueueType type, byte[] data, Exception e) { this.type=type; this.data=data; this.e=e; } - } - - private final Handler mainLooper; - private final IBinder binder; - private final Queue queue1, queue2; - - private BluetoothSerialSocket socket; - private BluetoothSerialListener listener; - private boolean connected; - - /** - * Lifecylce - */ - public BluetoothSerialService() { - mainLooper = new Handler(Looper.getMainLooper()); - binder = new SerialBinder(); - queue1 = new LinkedList<>(); - queue2 = new LinkedList<>(); - } - - @Override - public void onDestroy() { - //cancelNotification(); - disconnect(); - super.onDestroy(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - /** - * Api - */ - public void connect(BluetoothSerialSocket socket) throws IOException { - socket.connect(this); - this.socket = socket; - connected = true; - } - - public void disconnect() { - connected = false; // ignore data,errors while disconnecting - //cancelNotification(); - if(socket != null) { - socket.disconnect(); - socket = null; - } - } - - public void write(byte[] data) throws IOException { - if(!connected) - throw new IOException("not connected"); - socket.write(data); - } - - public void attach(BluetoothSerialListener listener) { - if(Looper.getMainLooper().getThread() != Thread.currentThread()) - throw new IllegalArgumentException("not in main thread"); - //cancelNotification(); - // use synchronized() to prevent new items in queue2 - // new items will not be added to queue1 because mainLooper.post and attach() run in main thread - synchronized (this) { - this.listener = listener; - } - for(QueueItem item : queue1) { - switch(item.type) { - case Connect: listener.onSerialConnect (); break; - case ConnectError: listener.onSerialConnectError (item.e); break; - case Read: listener.onSerialRead (item.data); break; - case IoError: listener.onSerialIoError (item.e); break; - } - } - for(QueueItem item : queue2) { - switch(item.type) { - case Connect: listener.onSerialConnect (); break; - case ConnectError: listener.onSerialConnectError (item.e); break; - case Read: listener.onSerialRead (item.data); break; - case IoError: listener.onSerialIoError (item.e); break; - } - } - queue1.clear(); - queue2.clear(); - } - - public void detach() { - if (connected){ - disconnect(); - } - listener = null; - } - - - - /** - * SerialListener - */ - public void onSerialConnect() { - if(connected) { - synchronized (this) { - if (listener != null) { - mainLooper.post(() -> { - if (listener != null) { - listener.onSerialConnect(); - } else { - queue1.add(new QueueItem(QueueType.Connect, null, null)); - } - }); - } else { - queue2.add(new QueueItem(QueueType.Connect, null, null)); - } - } - } - } - - public void onSerialConnectError(Exception e) { - if(connected) { - synchronized (this) { - if (listener != null) { - mainLooper.post(() -> { - if (listener != null) { - listener.onSerialConnectError(e); - } else { - queue1.add(new QueueItem(QueueType.ConnectError, null, e)); - //cancelNotification(); - disconnect(); - } - }); - } else { - queue2.add(new QueueItem(QueueType.ConnectError, null, e)); - //cancelNotification(); - disconnect(); - } - } - } - } - - public void onSerialRead(byte[] data) { - if(connected) { - synchronized (this) { - if (listener != null) { - mainLooper.post(() -> { - if (listener != null) { - listener.onSerialRead(data); - } else { - queue1.add(new QueueItem(QueueType.Read, data, null)); - } - }); - } else { - queue2.add(new QueueItem(QueueType.Read, data, null)); - } - } - } - } - - public void onSerialIoError(Exception e) { - if(connected) { - synchronized (this) { - if (listener != null) { - mainLooper.post(() -> { - if (listener != null) { - listener.onSerialIoError(e); - } else { - queue1.add(new QueueItem(QueueType.IoError, null, e)); - //cancelNotification(); - disconnect(); - } - }); - } else { - queue2.add(new QueueItem(QueueType.IoError, null, e)); - //cancelNotification(); - disconnect(); - } - } - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java deleted file mode 100644 index 626db39..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.bg7yoz.ft8cn.bluetooth; -/** - * 蓝牙串口的SOCKET - * BG7YOZ - * 2023-03 - */ - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -import java.io.IOException; -import java.security.InvalidParameterException; -import java.util.Arrays; -import java.util.UUID; -import java.util.concurrent.Executors; - -public class BluetoothSerialSocket implements Runnable { - - private static final UUID BLUETOOTH_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); - - private final BroadcastReceiver disconnectBroadcastReceiver; - - private final Context context; - private BluetoothSerialListener listener; - private final BluetoothDevice device; - private BluetoothSocket socket; - private boolean connected; - - public BluetoothSerialSocket(Context context, BluetoothDevice device) { - if(context instanceof Activity) - throw new InvalidParameterException("expected non UI context"); - this.context = context; - this.device = device; - disconnectBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if(listener != null) - listener.onSerialIoError(new IOException("background disconnect")); - disconnect(); // disconnect now, else would be queued until UI re-attached - } - }; - } - - @SuppressLint("MissingPermission") - String getName() { - return device.getName() != null ? device.getName() : device.getAddress(); - } - - /** - * connect-success and most connect-errors are returned asynchronously to listener - */ - void connect(BluetoothSerialListener listener) throws IOException { - this.listener = listener; - context.registerReceiver(disconnectBroadcastReceiver, new IntentFilter(BluetoothConstants.INTENT_ACTION_DISCONNECT)); - Executors.newCachedThreadPool().submit(this); - } - - void disconnect() { - listener = null; // ignore remaining data and errors - // connected = false; // run loop will reset connected - if(socket != null) { - try { - socket.close(); - } catch (Exception ignored) { - } - socket = null; - } - try { - context.unregisterReceiver(disconnectBroadcastReceiver); - } catch (Exception ignored) { - } - } - - void write(byte[] data) throws IOException { - if (!connected) - throw new IOException("not connected"); - socket.getOutputStream().write(data); - } - - @SuppressLint("MissingPermission") - @Override - public void run() { // connect & read - try { - socket = device.createRfcommSocketToServiceRecord(BLUETOOTH_SPP); - socket.connect(); - if(listener != null) - listener.onSerialConnect(); - } catch (Exception e) { - if(listener != null) - listener.onSerialConnectError(e); - try { - socket.close(); - } catch (Exception ignored) { - } - socket = null; - return; - } - connected = true; - try { - byte[] buffer = new byte[1024]; - int len; - //noinspection InfiniteLoopStatement - while (true) { - len = socket.getInputStream().read(buffer); - byte[] data = Arrays.copyOf(buffer, len); - if(listener != null) - listener.onSerialRead(data); - } - } catch (Exception e) { - connected = false; - if (listener != null) - listener.onSerialIoError(e); - try { - socket.close(); - } catch (Exception ignored) { - } - socket = null; - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java deleted file mode 100644 index 9618712..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.bg7yoz.ft8cn.bluetooth; -/** - * 蓝牙状态广播类。连接、断开、变化 - * @writer bg7yoz - * @date 2022-07-22 - */ - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -public class BluetoothStateBroadcastReceive extends BroadcastReceiver { - private static final String TAG="BluetoothStateBroadcastReceive"; - private Context context; - private MainViewModel mainViewModel; - - public BluetoothStateBroadcastReceive(Context context, MainViewModel mainViewModel) { - this.context = context; - this.mainViewModel = mainViewModel; - } - - @SuppressLint("MissingPermission") - @Override - public void onReceive(Context context, Intent intent) { - this.context=context; - String action = intent.getAction(); - - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter(); - int headset=-1; - int a2dp=-1; - if (blueAdapter!=null) { - headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); - a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP); - } - switch (action) { - case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED: - case BluetoothAdapter.EXTRA_CONNECTION_STATE: - case BluetoothAdapter.EXTRA_STATE: - if(headset == BluetoothProfile.STATE_CONNECTED ||a2dp==BluetoothProfile.STATE_CONNECTED){ - //if(headset == BluetoothProfile.STATE_CONNECTED){ - //if(a2dp==BluetoothProfile.STATE_CONNECTED){ - mainViewModel.setBlueToothOn(); - }else { - mainViewModel.setBlueToothOff(); - } - break; - - case BluetoothDevice.ACTION_ACL_CONNECTED: - if (device!=null) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.bluetooth_is_connected) - ,device.getName())); - } - break; - - case BluetoothDevice.ACTION_ACL_DISCONNECTED: - if (device!=null) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.bluetooth_is_diconnected) - ,device.getName())); - } - break; - - case AudioManager.ACTION_AUDIO_BECOMING_NOISY: - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.sound_source_switched)); - break; - - - case BluetoothAdapter.ACTION_STATE_CHANGED: - int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); - switch (blueState) { - case BluetoothAdapter.STATE_OFF: - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_off)); - break; - case BluetoothAdapter.STATE_ON: - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_on)); - break; - } - break; - - } - } - -// static final int PROFILE_HEADSET = 0; -// static final int PROFILE_A2DP = 1; -// static final int PROFILE_OPP = 2; -// static final int PROFILE_HID = 3; -// static final int PROFILE_PANU = 4; -// static final int PROFILE_NAP = 5; -// static final int PROFILE_A2DP_SINK = 6; -// -// private boolean checkBluetoothClass(BluetoothClass bluetoothClass,int proFile){ -// if (proFile==PROFILE_A2DP){ -// bluetoothClass.hasService(BluetoothClass.Service.RENDER); -// return true; -// } -// } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java deleted file mode 100644 index dde5f67..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.bg7yoz.ft8cn.callsign; -/** - * 用于呼号归属地查询的数据库操作,数据库采用内存方式。来源是CTY.DAT - * @author BG7YOZ - * 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.os.AsyncTask; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.google.android.gms.maps.model.LatLng; - -import java.util.ArrayList; -import java.util.Set; - -public class CallsignDatabase extends SQLiteOpenHelper { - private static final String TAG = "CallsignDatabase"; - @SuppressLint("StaticFieldLeak") - private static CallsignDatabase instance; - private final Context context; - private SQLiteDatabase db; - - public static CallsignDatabase getInstance(@Nullable Context context, @Nullable String databaseName, int version) { - if (instance == null) { - instance = new CallsignDatabase(context, databaseName, null, version); - } - return instance; - } - - - public CallsignDatabase(@Nullable Context context, @Nullable String name - , @Nullable SQLiteDatabase.CursorFactory factory, int version) { - super(context, name, factory, version); - this.context = context; - - //链接数据库,如果实体库不存在,就会调用onCreate方法,在onCreate方法中初始化数据库 - db = this.getWritableDatabase(); - } - - public SQLiteDatabase getDb() { - return db; - } - - /** - * 当实体数据库不存在时,会调用该方法。可在这个地方创建数据,并添加文件 - * - * @param sqLiteDatabase 需要连接的数据库 - */ - @Override - public void onCreate(SQLiteDatabase sqLiteDatabase) { - Log.d(TAG, "Create database."); - db = sqLiteDatabase;//把数据库链接保存下来 - createTables();//创建数据表 - new InitDatabase(context, db).execute();//导入数据 - } - - @Override - public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { - - } - - - private void createTables() { - try { - db.execSQL("CREATE TABLE countries (\n" + - "id INTEGER NOT NULL PRIMARY KEY,\n" + - "CountryNameEn TEXT,\n" + - "CountryNameCN TEXT,\n" + - "CQZone INTEGER,\n" + - "ITUZone INTEGER,\n" + - "Continent TEXT,\n" + - "Latitude REAL,\n" + - "Longitude REAL,\n" + - "GMT_offset REAL,\n" + - "DXCC TEXT)"); - db.execSQL("CREATE INDEX countries_id_IDX ON countries (id)"); - db.execSQL("CREATE TABLE callsigns (countryId INTEGER NOT NULL,callsign TEXT)"); - db.execSQL("CREATE INDEX callsigns_callsign_IDX ON callsigns (callsign)"); - - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - - } - } - - //查呼号的归属地 - public void getCallsignInformation(String callsign, OnAfterQueryCallsignLocation afterQueryCallsignLocation) { - new QueryCallsignInformation(db, callsign, afterQueryCallsignLocation).execute(); - } - - public CallsignInfo getCallInfo(String callsign) { - return getCallsignInfo(db, callsign); - } - - /** - * 更新消息中的位置及经纬度信息 - * - * @param ft8Messages 消息列表 - */ - public static synchronized void getMessagesLocation(SQLiteDatabase db, ArrayList ft8Messages ) { - if (ft8Messages==null) return; - ArrayList messages = new ArrayList<>(ft8Messages);//防止线程访问冲突 - - for (Ft8Message msg : messages) { - if (msg.i3==0&&msg.n3==0) continue;//如果是自由文本,就不查了 - CallsignInfo fromCallsignInfo = getCallsignInfo(db, - msg.callsignFrom.replace("<","").replace(">","")); - if (fromCallsignInfo != null) { - msg.fromDxcc = !GeneralVariables.getDxccByPrefix(fromCallsignInfo.DXCC); - msg.fromItu = !GeneralVariables.getItuZoneById(fromCallsignInfo.ITUZone); - msg.fromCq = !GeneralVariables.getCqZoneById(fromCallsignInfo.CQZone); - if (GeneralVariables.isChina) { - msg.fromWhere = fromCallsignInfo.CountryNameCN; - } else { - msg.fromWhere = fromCallsignInfo.CountryNameEn; - } - msg.fromLatLng = new LatLng(fromCallsignInfo.Latitude, fromCallsignInfo.Longitude * -1); - } - - if (msg.checkIsCQ() || msg.getCallsignTo().contains("...")) {//CQ就不查了 - continue; - } - - CallsignInfo toCallsignInfo = getCallsignInfo(db, - msg.callsignTo.replace("<","").replace(">","")); - if (toCallsignInfo != null) { - msg.toDxcc = !GeneralVariables.getDxccByPrefix(toCallsignInfo.DXCC); - msg.toItu = !GeneralVariables.getItuZoneById(toCallsignInfo.ITUZone); - msg.toCq = !GeneralVariables.getCqZoneById(toCallsignInfo.CQZone); - - if (GeneralVariables.isChina) { - msg.toWhere = toCallsignInfo.CountryNameCN; - } else { - msg.toWhere = toCallsignInfo.CountryNameEn; - } - msg.toLatLng = new LatLng(toCallsignInfo.Latitude, toCallsignInfo.Longitude*-1); - } - } - } - - @SuppressLint("Range") - private static CallsignInfo getCallsignInfo(SQLiteDatabase db, String callsign) { - CallsignInfo callsignInfo = null; - - String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" + - "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" + - "order by LENGTH(callsign) desc\n" + - "LIMIT 1"; - - Cursor cursor = db.rawQuery(querySQL, new String[]{callsign.toUpperCase(), callsign.toUpperCase()}); - if (cursor.moveToFirst()) { - callsignInfo = new CallsignInfo(callsign.toUpperCase() - , cursor.getString(cursor.getColumnIndex("CountryNameEn")) - , cursor.getString(cursor.getColumnIndex("CountryNameCN")) - , cursor.getInt(cursor.getColumnIndex("CQZone")) - , cursor.getInt(cursor.getColumnIndex("ITUZone")) - , cursor.getString(cursor.getColumnIndex("Continent")) - , cursor.getFloat(cursor.getColumnIndex("Latitude")) - , cursor.getFloat(cursor.getColumnIndex("Longitude")) - , cursor.getFloat(cursor.getColumnIndex("GMT_offset")) - , cursor.getString(cursor.getColumnIndex("DXCC"))); - } - cursor.close(); - return callsignInfo; - } - - - static class QueryCallsignInformation extends AsyncTask { - private final SQLiteDatabase db; - private final String sqlParameter; - private final OnAfterQueryCallsignLocation afterQueryCallsignLocation; - - public QueryCallsignInformation(SQLiteDatabase db, String sqlParameter, OnAfterQueryCallsignLocation afterQueryCallsignLocation) { - this.db = db; - this.sqlParameter = sqlParameter; - this.afterQueryCallsignLocation = afterQueryCallsignLocation; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { -// String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" + -// "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" + -// "order by LENGTH(callsign) desc\n" + -// "LIMIT 1"; -// -// Cursor cursor = db.rawQuery(querySQL, new String[]{sqlParameter.toUpperCase(), sqlParameter.toUpperCase()}); -// if (cursor.moveToFirst()) { -// CallsignInfo callsignInfo = new CallsignInfo(); -// callsignInfo.CallSign = sqlParameter.toUpperCase(); -// callsignInfo.CountryNameEn = cursor.getString(cursor.getColumnIndex("CountryNameEn")); -// callsignInfo.CountryNameCN = cursor.getString(cursor.getColumnIndex("CountryNameCN")); -// callsignInfo.CQZone = cursor.getInt(cursor.getColumnIndex("CQZone")); -// callsignInfo.ITUZone = cursor.getInt(cursor.getColumnIndex("ITUZone")); -// callsignInfo.Continent = cursor.getString(cursor.getColumnIndex("Continent")); -// callsignInfo.Latitude = cursor.getFloat(cursor.getColumnIndex("Latitude")); -// callsignInfo.Longitude = cursor.getFloat(cursor.getColumnIndex("Longitude")); -// callsignInfo.GMT_offset = cursor.getFloat(cursor.getColumnIndex("GMT_offset")); -// callsignInfo.DXCC = cursor.getString(cursor.getColumnIndex("DXCC")); -// if (afterQueryCallsignLocation!=null){ -// afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo); -// } -// } -// cursor.close(); - CallsignInfo callsignInfo = getCallsignInfo(db, sqlParameter); - if (callsignInfo != null && afterQueryCallsignLocation != null) { - afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo); - } - return null; - } - } - - - static class InitDatabase extends AsyncTask { - @SuppressLint("StaticFieldLeak") - private final Context context; - private final SQLiteDatabase db; - - public InitDatabase(Context context, SQLiteDatabase db) { - this.context = context; - this.db = db; - } - - @Override - protected Void doInBackground(Void... voids) { - Log.d(TAG, "开始导入呼号位置数据..."); - String insertCountriesSQL = "INSERT INTO countries (id,CountryNameEn,CountryNameCN,CQZone" + - ",ITUZone,Continent,Latitude,Longitude,GMT_offset,DXCC)\n" + - "VALUES(?,?,?,?,?,?,?,?,?,?)"; - - ArrayList callsignInfos = - CallsignFileOperation.getCallSingInfoFromFile(context); - ContentValues values = new ContentValues(); - for (int i = 0; i < callsignInfos.size(); i++) { - try { - //把国家和地区数据写进表中,id用于关联呼号 - db.execSQL(insertCountriesSQL, new Object[]{ - i,//id号 - callsignInfos.get(i).CountryNameEn, - callsignInfos.get(i).CountryNameCN, - callsignInfos.get(i).CQZone, - callsignInfos.get(i).ITUZone, - callsignInfos.get(i).Continent, - callsignInfos.get(i).Latitude, - callsignInfos.get(i).Longitude, - callsignInfos.get(i).GMT_offset, - callsignInfos.get(i).DXCC}); - Set calls = CallsignFileOperation.getCallsigns(callsignInfos.get(i).CallSign); - - for (String s : calls - ) { - values.put("countryId", i); - values.put("callsign", s); - db.insert("callsigns", null, values); - values.clear(); - } - - } catch (Exception e) { - Log.e(TAG, "错误:" + e.getMessage()); - } - } - Log.d(TAG, "呼号位置数据导入完毕!"); - return null; - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java deleted file mode 100644 index 461f219..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.bg7yoz.ft8cn.callsign; -/** - * 预处理呼号数据库的文件操作,呼号的来源是CTY.DAT - * @author BG7YOZ - * @date 2023-03-20 - */ - -import android.content.Context; -import android.content.res.AssetManager; - -import com.bg7yoz.ft8cn.GeneralVariables; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -public class CallsignFileOperation { - public static String TAG="CallsignFileOperation"; - public static String[][] countries; - - /** - * 从assets目录中的cty.dat中读出呼号分配国家和地区的列表。呼号字符串中包括多个字符串,以逗号分割, - * @param context 用于调用getAssets()方法。 - * @return ArrayList 返回CallsignInfo数组列表 - */ - public static ArrayList getCallSingInfoFromFile(Context context){ - ArrayList callsignInfos=new ArrayList<>(); - - //读出国家和地区的中英文对应翻译表。保存到countries二维数组中。 - countries=getCountryNameToCN(context); - - AssetManager assetManager = context.getAssets(); - try { - InputStream inputStream= assetManager.open("cty.dat"); - String[] st=getLinesFromInputStream(inputStream,";"); - for (int i = 0; i getCallsigns(String s){ - String[] ls=s.replace("\n","").split(","); - Set callsigns=new HashSet<>(); - for (int i = 0; i < ls.length ; i++) { - if (ls[i].contains(")")) { - //Log.d(TAG,ls[i]); - ls[i] = ls[i].substring(0, ls[i].indexOf("(")); - //Log.d(TAG,ls[i]+" ((("); - } - if (ls[i].contains("[")) { - //Log.d(TAG,ls[i]); - ls[i] = ls[i].substring(0, ls[i].indexOf("[")); - //Log.d(TAG,ls[i]+" 【【【"); - } - callsigns.add(ls[i].trim()); - } - - return callsigns; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java deleted file mode 100644 index bd5dce8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.bg7yoz.ft8cn.callsign; -/** - * 呼号信息类,用于归属地查询 - * - * @author BG7YOZ - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -public class CallsignInfo { - public static String TAG="CallsignInfo"; - public String CallSign;//呼号 - public String CountryNameEn;//国家 - public String CountryNameCN;//国家中文名 - public int CQZone;//CQ分区 - public int ITUZone;//ITU分区 - public String Continent;//大陆缩写 - public float Latitude;//以度为单位的纬度,+ 表示北 - public float Longitude;//以度为单位的经度,+ 表示西 - public float GMT_offset;//与 GMT 的本地时间偏移 - public String DXCC;//DXCC前缀 - - @SuppressLint("DefaultLocale") - @NonNull - @Override - public String toString() { - String country; - if (GeneralVariables.isChina) { - country=CountryNameCN; - }else { - country=CountryNameEn; - } - //return String.format("呼号:%s\n位置:%s\nCQ分区:%d\nITU分区:%d\n大陆:%s\n经纬度:%.2f,%.2f\n时区:%.0f\nDXCC前缀:%s" - return String.format(GeneralVariables.getStringFromResource(R.string.callsign_info) - , CallSign, country, CQZone, ITUZone, Continent, Longitude, Latitude, GMT_offset, DXCC); - } - - - public CallsignInfo(String callSign, String countryNameEn, - String countryNameCN, int CQZone, int ITUZone, - String continent, float latitude, float longitude, - float GMT_offset, String DXCC) { - CallSign = callSign; - CountryNameEn = countryNameEn; - CountryNameCN = countryNameCN; - this.CQZone = CQZone; - this.ITUZone = ITUZone; - Continent = continent; - Latitude = latitude; - Longitude = longitude; - this.GMT_offset = GMT_offset; - this.DXCC = DXCC; - } - - public CallsignInfo(String s) { - String[] info = s.split(":"); - if (info.length<9){ - Log.e(TAG,"呼号数据格式错误!"+s); - return; - } - CountryNameEn = info[0].replace("\n", "").trim(); - CQZone = Integer.parseInt(info[1].replace("\n", "").replace(" ", "")); - ITUZone = Integer.parseInt(info[2].replace("\n", "").replace(" ", "")); - Continent = info[3].replace("\n", "").replace(" ", ""); - Latitude = Float.parseFloat(info[4].replace("\n", "").replace(" ", "")); - Longitude = Float.parseFloat(info[5].replace("\n", "").replace(" ", "")); - GMT_offset = Float.parseFloat(info[6].replace("\n", "").replace(" ", "")); - DXCC = info[7].replace("\n", "").replace(" ", ""); - CallSign= info[8].replace("\n", "").replace(" ", ""); - } -} - diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java deleted file mode 100644 index 1693ff4..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.bg7yoz.ft8cn.callsign; - -/** - * 用于查询呼号归属地的回调接口,因为数据库操作采用异步方式 - * - * @author BG7YOZ - * @date 2023-03-20 - * - */ -public interface OnAfterQueryCallsignLocation { - void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java deleted file mode 100644 index 84a0422..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.bg7yoz.ft8cn.connector; -/** - * 用于连接电台的基础类,蓝牙、USB线、FLEX网络、ICOM网络都是继承于此 - * - * @author BG7YOZ - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.rigs.OnConnectReceiveData; -import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; - - -public class BaseRigConnector { - private boolean connected;//是否处于连接状态 - private OnConnectReceiveData onConnectReceiveData;//当接收到数据后的动作 - private int controlMode;//控制模式 - private OnRigStateChanged onRigStateChanged; - private OnConnectorStateChanged onConnectorStateChanged=new OnConnectorStateChanged() { - @Override - public void onDisconnected() { - if (onRigStateChanged!=null){ - onRigStateChanged.onDisconnected(); - } - connected=false; - } - - @Override - public void onConnected() { - if (onRigStateChanged!=null){ - onRigStateChanged.onConnected(); - } - connected=true; - } - - @Override - public void onRunError(String message) { - if (onRigStateChanged!=null){ - onRigStateChanged.onRunError(message); - } - connected=false; - } - }; - public BaseRigConnector(int controlMode) { - this.controlMode=controlMode; - } - - /** - * 发送数据 - * @param data 数据 - */ - public synchronized void sendData(byte[] data){}; - - /** - * 设置PTT状态,ON OFF,如果是RTS和DTR,这个是在有线方式才有的,在CableConnector中会重载此方法 - * @param on 是否ON - */ - public void setPttOn(boolean on){}; - - /** - * 使用发送数据的方式设置PTT状态 - * @param command 指令数据 - */ - public void setPttOn(byte[] command){}; - - public void setControlMode(int mode){ - controlMode=mode; - } - - public int getControlMode() { - return controlMode; - } - - public void setOnConnectReceiveData(OnConnectReceiveData receiveData){ - onConnectReceiveData=receiveData; - } - - public void sendWaveData(float[] data){ - //留给网络方式发送音频流 - } - - public OnConnectReceiveData getOnConnectReceiveData() { - return onConnectReceiveData; - } - public void connect(){ - } - public void disconnect(){ - } - - public OnRigStateChanged getOnRigStateChanged() { - return onRigStateChanged; - } - - public void setOnRigStateChanged(OnRigStateChanged onRigStateChanged) { - this.onRigStateChanged = onRigStateChanged; - } - - public OnConnectorStateChanged getOnConnectorStateChanged() { - return onConnectorStateChanged; - } - public boolean isConnected(){ - return connected; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java deleted file mode 100644 index ab3a907..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.bg7yoz.ft8cn.connector; -/** - * 用于蓝牙连接的Connector,继承于BaseRigConnector - * - * @author BG7YOZ - * @date 2023-03-20 - */ - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialListener; -import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialService; -import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialSocket; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.io.IOException; - -public class BluetoothRigConnector extends BaseRigConnector implements ServiceConnection, BluetoothSerialListener { - private enum Connected {False, Pending, True} - private static BluetoothRigConnector connector=null; - - public static BluetoothRigConnector getInstance(Context context, String address, int controlMode){ - if (connector!=null){ - if (!connector.getDeviceAddress().equals(address)) { - if (connector.connected== Connected.True) { - connector.socketDisconnect(); - } - connector.setDeviceAddress(address); - connector.socketConnect(); - } - - return connector; - }else { - return new BluetoothRigConnector(context,address,controlMode); - } - } - - private static final String TAG = "BluetoothRigConnector"; - //private static ServiceConnection connection; - private boolean initialStart = true; - private BluetoothSerialService service = null; - private Connected connected = Connected.False; - private String deviceAddress; - private Context context; - - - public BluetoothRigConnector(Context context, String address, int controlMode) { - super(controlMode); - connector=this; - deviceAddress = address; - this.context = context; - - context.stopService(new Intent(context, BluetoothSerialService.class)); - context.bindService(new Intent(context, BluetoothSerialService.class), this, Context.BIND_AUTO_CREATE); - - } - - public String getDeviceAddress() { - return deviceAddress; - } - - public void setDeviceAddress(String deviceAddress) { - this.deviceAddress = deviceAddress; - } - - @Override - public void onServiceConnected(ComponentName componentName, IBinder iBinder) { - service = ((BluetoothSerialService.SerialBinder) iBinder).getService(); - service.attach(this); - if (initialStart) { - initialStart = false; - //getActivity().runOnUiThread(this::connect); - socketConnect(); - } - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - socketDisconnect(); - service = null; - } - - @Override - public void onSerialConnect() { - Log.d(TAG, "onSerialConnect: connected"); - connected = Connected.True; - getOnConnectorStateChanged().onConnected(); - } - - @Override - public void onSerialConnectError(Exception e) { - Log.e(TAG, "onSerialConnectError: " + e.getMessage()); - getOnConnectorStateChanged().onRunError(e.getMessage()); - socketDisconnect(); - } - - - @Override - public void onSerialRead(byte[] data) { - if (data.length > 0) { - //Log.d(TAG, "onSerialRead: " + BaseRig.byteToStr(data)); - if (getOnConnectReceiveData()!=null){ - getOnConnectReceiveData().onData(data); - } - } - } - - @Override - public void onSerialIoError(Exception e) { - Log.e(TAG, "onSerialIoError: " + e.getMessage()); - getOnConnectorStateChanged().onRunError(e.getMessage()); - socketDisconnect(); - } - - public void socketDisconnect() { - connected = Connected.False; - getOnConnectorStateChanged().onDisconnected(); - service.disconnect(); - } - - /* - * Serial + UI - */ - public void socketConnect() { - try { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.connect_bluetooth_spp) - ,deviceAddress)); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress); - Log.d(TAG, "connecting..."); - connected = Connected.Pending; - BluetoothSerialSocket socket = new BluetoothSerialSocket(context, device); - service.connect(socket); - } catch (Exception e) { - onSerialConnectError(e); - } - } - - public void sendCommand(byte[] data) { - //Log.d(TAG, "sendCommand: "+BaseRig.byteToStr(data) ); - if (connected != Connected.True) { - Log.e(TAG, "sendCommand: 蓝牙没连接"); - socketConnect(); - return; - } - - try { - service.write(data); - } catch (IOException e) { - getOnConnectorStateChanged().onRunError(e.getMessage()); - } - } - - @Override - public synchronized void sendData(byte[] data) { - sendCommand(data); - } - - @Override - public void setPttOn(byte[] command) { - sendData(command);//以CAT指令发送PTT - } - - @Override - public void connect() { - super.connect(); - socketConnect(); - } - @Override - public void disconnect() { - super.disconnect(); - socketDisconnect(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java deleted file mode 100644 index 6faf22e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.bg7yoz.ft8cn.connector; - -import android.content.Context; -import android.util.Log; - -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager; - -/** - * 有线连接方式的Connector,这里是指USB方式的,继承于BaseRigConnector - * - * @author BG7YOZ - * @date 2023-03-20 - */ -public class CableConnector extends BaseRigConnector { - private static final String TAG="CableConnector"; - - private final CableSerialPort cableSerialPort; - - - public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate - , int controlMode) { - super(controlMode); - cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged()); - cableSerialPort.ioListener=new SerialInputOutputManager.Listener() { - @Override - public void onNewData(byte[] data) { - if (getOnConnectReceiveData()!=null){ - getOnConnectReceiveData().onData(data); - } - } - - @Override - public void onRunError(Exception e) { - Log.e(TAG, "CableConnector error: "+e.getMessage() ); - getOnConnectorStateChanged().onRunError("与串口失去连接:"+e.getMessage()); - } - } ; - //connect(); - } - - @Override - public synchronized void sendData(byte[] data) { - cableSerialPort.sendData(data); - } - - - @Override - public void setPttOn(boolean on) { - //只处理RTS和DTR - switch (getControlMode()){ - case ControlMode.DTR: cableSerialPort.setDTR_On(on);//打开和关闭DTR - break; - case ControlMode.RTS:cableSerialPort.setRTS_On(on);//打开和关闭RTS - break; - } - } - - @Override - public void setPttOn(byte[] command) { - cableSerialPort.sendData(command);//以CAT指令发送PTT - } - - @Override - public void connect() { - super.connect(); - cableSerialPort.connect(); - } - - @Override - public void disconnect() { - super.disconnect(); - cableSerialPort.disconnect(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java deleted file mode 100644 index 85ddfd0..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java +++ /dev/null @@ -1,374 +0,0 @@ -package com.bg7yoz.ft8cn.connector; -/** - * 用于USB串口操作的类。USB串口驱动在serialport目录中,主要是CDC、CH34x、CP21xx、FTDI等。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbManager; -import android.os.Build; -import android.util.Log; - -import com.bg7yoz.ft8cn.BuildConfig; -import com.bg7yoz.ft8cn.serialport.CdcAcmSerialDriver; -import com.bg7yoz.ft8cn.serialport.UsbSerialDriver; -import com.bg7yoz.ft8cn.serialport.UsbSerialPort; -import com.bg7yoz.ft8cn.serialport.UsbSerialProber; -import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; - - -public class CableSerialPort { - private static final String TAG = "CableSerialPort"; - private OnConnectorStateChanged onStateChanged; - public static final int SEND_TIMEOUT = 2000; - - private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID; - - public enum UsbPermission {Unknown, Requested, Granted, Denied} - - private UsbPermission usbPermission = UsbPermission.Unknown; - - private BroadcastReceiver broadcastReceiver; - private final Context context; - - private int vendorId = 0x0c26;//设备号 - private int portNum = 0;//端口号 - private int baudRate = 19200;//波特率 - - private UsbSerialPort usbSerialPort; - private SerialInputOutputManager usbIoManager; - public SerialInputOutputManager.Listener ioListener = null; - - private UsbManager usbManager; - private UsbDeviceConnection usbConnection; - private UsbSerialDriver driver; - - - private boolean connected = false;//是否处于连接状态 - - public CableSerialPort(Context mContext, SerialPort serialPort, int baud, OnConnectorStateChanged connectorStateChanged) { - vendorId = serialPort.vendorId; - portNum = serialPort.portNum; - baudRate = baud; - context = mContext; - this.onStateChanged=connectorStateChanged; - doBroadcast(); - } - - public CableSerialPort(Context mContext) { - context = mContext; - doBroadcast(); - } - - private void doBroadcast() { - broadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (INTENT_ACTION_GRANT_USB.equals(intent.getAction())) { - usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) - ? UsbPermission.Granted : UsbPermission.Denied; - connect(); - } - } - }; - } - - private boolean prepare() { - registerRigSerialPort(context); - UsbDevice device = null; - usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); - - //此处把connection设成Null,这样在后面来通过是否null判断是否有权限。 - usbConnection = null; - //此处是不是做个权限判断? - if (usbManager == null) { - return false; - } - - - for (UsbDevice v : usbManager.getDeviceList().values()) { - if (v.getVendorId() == vendorId) { - device = v; - } - } - if (device == null) { - Log.e(TAG, String.format("串口设备打开失败: 没有找到设备0x%04x", vendorId)); - return false; - } - driver = UsbSerialProber.getDefaultProber().probeDevice(device); - if (driver == null) { - //试着把未知的设备加入到cdc驱动上 - driver = new CdcAcmSerialDriver(device); - } - if (driver.getPorts().size() < portNum) { - Log.e(TAG, "串口号不存在,无法打开。"); - return false; - } - Log.e(TAG, "connect: port size:" + String.valueOf(driver.getPorts().size())); - usbSerialPort = driver.getPorts().get(portNum); - usbConnection = usbManager.openDevice(driver.getDevice()); - - return true; - - } - - //@RequiresApi(api = Build.VERSION_CODES.S) - public boolean connect() { - connected = false; - if (!prepare()) { - //return false; - } - if (driver == null) { - if (onStateChanged!=null){ - onStateChanged.onRunError("无法连接串口,没有驱动或串口不存在!"); - } - return false; - } - if (usbConnection == null && usbPermission == UsbPermission.Unknown - && !usbManager.hasPermission(driver.getDevice())) { - usbPermission = UsbPermission.Requested; - - PendingIntent usbPermissionIntent; - - //在android12 开始,增加了PendingIntent.FLAG_MUTABLE保护机制,所以要做版本判断 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - usbPermissionIntent = PendingIntent.getBroadcast(context, 0 - , new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE); - } else { - usbPermissionIntent = PendingIntent.getBroadcast(context, 0 - , new Intent(INTENT_ACTION_GRANT_USB), 0); - } - - - //PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0 - // , new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE); - // , new Intent(INTENT_ACTION_GRANT_USB), 0); - - - usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); - prepare(); - } - if (usbConnection == null) { - if (onStateChanged!=null){ - onStateChanged.onRunError("无法连接串口,可能没有访问USB设备的权限!"); - } - - return false; - } - try { - usbSerialPort.open(usbConnection); - //波特率、停止位 - usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); - usbIoManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() { - @Override - public void onNewData(byte[] data) { - if (ioListener != null) { - ioListener.onNewData(data); - } - } - - @Override - public void onRunError(Exception e) { - if (ioListener != null) { - ioListener.onRunError(e); - } - disconnect(); - } - }); - usbIoManager.start(); - Log.d(TAG, "串口打开成功!"); - connected = true; - - if (onStateChanged!=null){ - onStateChanged.onConnected(); - } - - - } catch (Exception e) { - Log.e(TAG, "串口打开失败: " + e.getMessage()); - if (onStateChanged!=null){ - onStateChanged.onRunError("串口打开失败: " + e.getMessage()); - } - disconnect(); - return false; - } - return true; - } - - public boolean sendData(final byte[] src) { - if (usbSerialPort != null) { - try { - usbSerialPort.write(src, SEND_TIMEOUT); - } catch (IOException e) { - e.printStackTrace(); - Log.e(TAG, "发送数据出错:" + e.getMessage()); - return false; - } - return true; - } else { - Log.e(TAG, "无法发送数据,串口没有打开。"); - return false; - } - - } - - public void disconnect() { - connected = false; - if (onStateChanged!=null){ - onStateChanged.onDisconnected(); - } - if (usbIoManager != null) { - usbIoManager.setListener(null); - usbIoManager.stop(); - } - usbIoManager = null; - try { - if (usbSerialPort != null) { - usbSerialPort.close(); - } - } catch (IOException ignored) { - } - usbSerialPort = null; - } - - public void registerRigSerialPort(Context context) { - Log.d(TAG, "registerRigSerialPort: registered!"); - context.registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); - } - - public void unregisterRigSerialPort(Activity activity) { - Log.d(TAG, "unregisterRigSerialPort: unregistered!"); - activity.unregisterReceiver(broadcastReceiver); - } - - - /** - * 打开和关闭RTS - * - * @param rts_on true:打开,false:关闭 - */ - public void setRTS_On(boolean rts_on) { - try { - EnumSet controlLines = usbSerialPort.getSupportedControlLines(); - if (controlLines.contains(UsbSerialPort.ControlLine.RTS)) { - usbSerialPort.setRTS(rts_on); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void setDTR_On(boolean dtr_on) { - try { - EnumSet controlLines = usbSerialPort.getSupportedControlLines(); - if (controlLines.contains(UsbSerialPort.ControlLine.DTR)) { - usbSerialPort.setDTR(dtr_on); - } - } catch (IOException e) { - e.printStackTrace(); - Log.d(TAG, "setDTR_On: " + e.getMessage()); - } - } - - public OnConnectorStateChanged getOnStateChanged() { - return onStateChanged; - } - - public void setOnStateChanged(OnConnectorStateChanged onStateChanged) { - this.onStateChanged = onStateChanged; - } - - public int getVendorId() { - return vendorId; - } - - public void setVendorId(int deviceId) { - this.vendorId = deviceId; - } - - public int getPortNum() { - return portNum; - } - - public void setPortNum(int portNum) { - this.portNum = portNum; - } - - public int getBaudRate() { - return baudRate; - } - - public void setBaudRate(int baudRate) { - this.baudRate = baudRate; - } - - /** - * 获取本机可用的串口设备串列表 - * - * @param context context - * @return 串口设备列表 - */ - public static ArrayList listSerialPorts(Context context) { - ArrayList serialPorts = new ArrayList<>(); - UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); - - for (UsbDevice device : usbManager.getDeviceList().values()) { - UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); - if (driver == null) { - continue; - //试着把未知的设备加入到cdc驱动上 - //driver = new CdcAcmSerialDriver(device); - } - for (int i = 0; i < driver.getPorts().size(); i++) { - serialPorts.add(new SerialPort(device.getDeviceId(), device.getVendorId() - , device.getProductId(), i)); - } - } - return serialPorts; - } - - public boolean isConnected() { - return connected; - } - - - public static class SerialPort { - public int deviceId = 0; - public int vendorId = 0x0c26;//厂商号 - public int productId = 0;//设备号 - public int portNum = 0;//端口号 - - public SerialPort(int deviceId, int vendorId, int productId, int portNum) { - this.deviceId = deviceId; - this.vendorId = vendorId; - this.productId = productId; - this.portNum = portNum; - } - - @SuppressLint("DefaultLocale") - @Override - public String toString() { - return String.format("SerialPort:deviceId=0x%04X, vendorId=0x%04X, portNum=%d" - , deviceId, vendorId, portNum); - } - - @SuppressLint("DefaultLocale") - public String information() { - return String.format("\\0x%04X\\0x%04X\\0x%04X\\0x%d", deviceId, vendorId, productId, portNum); - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java deleted file mode 100644 index 4a43f33..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.bg7yoz.ft8cn.connector; - -/** - * 连接的模式 - * @author BGY70Z - * @date 2023-03-20 - */ -public class ConnectMode { - public static final int USB_CABLE=0; - public static final int BLUE_TOOTH=1; - public static final int NETWORK=2; - public static String getModeStr(int mode){ - switch (mode){ - case USB_CABLE: - return "USB Cable"; - case BLUE_TOOTH: - return "Bluetooth"; - case NETWORK: - return "Network"; - default: - return "-"; - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java deleted file mode 100644 index d51dabe..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java +++ /dev/null @@ -1,325 +0,0 @@ -package com.bg7yoz.ft8cn.connector; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.Log; - -import androidx.lifecycle.MutableLiveData; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.flex.FlexCommand; -import com.bg7yoz.ft8cn.flex.FlexMeterInfos; -import com.bg7yoz.ft8cn.flex.FlexMeterList; -import com.bg7yoz.ft8cn.flex.FlexRadio; -import com.bg7yoz.ft8cn.flex.RadioTcpClient; -import com.bg7yoz.ft8cn.flex.VITA; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; - -/** - * 有线连接方式的Connector,这里是指USB方式的 - * @author BGY70Z - * @date 2023-03-20 - */ -public class FlexConnector extends BaseRigConnector { - public MutableLiveData mutableMeterList=new MutableLiveData<>(); - public FlexMeterInfos flexMeterInfos =new FlexMeterInfos(""); - public FlexMeterList meterList=new FlexMeterList(); - public interface OnWaveDataReceived{ - void OnDataReceived(int bufferLen,float[] buffer); - } - public int maxRfPower; - public int maxTunePower; - - private static final String TAG = "CableConnector"; - - private FlexRadio flexRadio; - - private OnWaveDataReceived onWaveDataReceived; - - - public FlexConnector(Context context, FlexRadio flexRadio, int controlMode) { - super(controlMode); - this.flexRadio = flexRadio; - maxTunePower=GeneralVariables.flexMaxTunePower; - maxRfPower=GeneralVariables.flexMaxRfPower; - setFlexRadioInterface(); - //connect(); - } - - public static int[] byteDataTo16BitData(byte[] buffer){ - int[] data=new int[buffer.length /2]; - for (int i = 0; i < buffer.length/2; i++) { - int res = (buffer[i*2+1] & 0x000000FF) | (((int) buffer[i*2]) << 8); - data[i]=res; - } - return data; - } - - - private void setFlexRadioInterface() { - flexRadio.setOnReceiveStreamData(new FlexRadio.OnReceiveStreamData() { - @Override - public void onReceiveAudio(byte[] data) { - if (onWaveDataReceived!=null){ - float[] buffer=getMonoFloatFromBytes(data);//把24000转成12000,立体声转成单声道 - onWaveDataReceived.OnDataReceived(buffer.length,buffer); - } - } - - @Override - public void onReceiveIQ(byte[] data) { - - } - - @Override - public void onReceiveFFT(VITA vita) { - //if (vita.streamId==0x40000000) { - // mutableVita.postValue(vita.showHeadStr() + "\n" + vita.showPayloadHex()); - //} - } - - @Override - public void onReceiveMeter(VITA vita) { - //Log.e(TAG, "onReceiveMeter: "+vita.showPayloadHex() ); - meterList.setMeters(vita.payload,flexMeterInfos); - - mutableMeterList.postValue(meterList); - - } - - @Override - public void onReceiveUnKnow(byte[] data) { - - } - }); - - - //当有命令返回值时的事件 - flexRadio.setOnCommandListener(new FlexRadio.OnCommandListener() { - @Override - public void onResponse(FlexRadio.FlexResponse response) { - if (response.resultValue!=0) {//只显示失败的命令 - //ToastMessage.show(response.resultStatus()); - //Log.e(TAG, "onResponse: "+response.resultStatus()); - } - - Log.e(TAG, "onResponse: command:"+response.flexCommand.toString()); - //Log.e(TAG, "onResponse: "+response.resultStatus()); - Log.e(TAG, "onResponse: "+response.rawData ); - - if (response.flexCommand== FlexCommand.METER_LIST){ - //FlexMeters flexMeters=new FlexMeters(response.exContent); - flexMeterInfos.setMeterInfos(response.exContent); - flexRadio.commandSubMeterAll();//显示全部仪表消息 - //flexMeters.getAllMeters(); - //Log.e(TAG, "onResponse: ----->>>"+flexMeters.getAllMeters() ); - } - if (response.flexCommand==FlexCommand.STREAM_CREATE_DAX_TX){ - flexRadio.streamTxId=response.daxTxStreamId; - } - -// if (response.flexCommand== FlexCommand.METER_LIST){ -// Log.e(TAG, "onResponse: ."+response.rawData.replace("#","\n") ); -// } - } - }); - - //当有状态信息接收到时 - flexRadio.setOnStatusListener(new FlexRadio.OnStatusListener() { - @Override - public void onStatus(FlexRadio.FlexResponse response) { - //显示状态消息 - //ToastMessage.show(response.content); - Log.e(TAG, "onStatus: "+response.rawData ); - } - }); - - - - flexRadio.setOnTcpConnectStatus(new FlexRadio.OnTcpConnectStatus() { - @SuppressLint("DefaultLocale") - @Override - public void onConnectSuccess(RadioTcpClient tcpClient) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.init_flex_operation) - ,flexRadio.getModel())); - - flexRadio.commandClientDisconnect();//断开之前的全部连接 - flexRadio.commandClientGui();//创建GUI - - flexRadio.commandSubDaxAll();//注册全部DAX流 - - - - flexRadio.commandClientSetEnforceNetWorkGui();//对网络MTU做设置 - - //flexRadio.commandSliceList();//列slice - flexRadio.commandSliceCreate();//创建slice - - - - - flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX - //todo 防止流的端口没有释放,把端口变换一下? - //FlexRadio.streamPort++; - - flexRadio.commandUdpPort();//设置UDP端口 - - - flexRadio.commandStreamCreateDaxRx(1);//创建流数据到DAX通道1 - flexRadio.commandStreamCreateDaxTx(1);//创建流数据到DAX通道1 - //TODO 是否设置??? dax tx T 或者 dax tx 1 - flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f)); - flexRadio.commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式 - flexRadio.commandSetFilter(0, 0, 3000);//设置滤波为3000HZ - - - flexRadio.commandMeterList();//列一下仪表 - //flexRadio.commandSubMeterAll();//此处订阅指令放到了接收响应部分 - - setMaxRfPower(maxRfPower);//设置发射功率 - setMaxTunePower(maxTunePower);//设置调谐功率 - - //flexRadio.commandSubMeterById(5);//列指定的仪表 - - //flexRadio.commandSliceSetNR(0, true); - //flexRadio.commandSliceSetNB(0, true); - - //flexRadio.commandDisplayPan(10, 10); - //flexRadio.commandSetFilter(0,0,3000); - // flexRadio.sendCommand(FlexCommand.FILT_SET, "filt 0 0 3000"); - //flexRadio.commandMeterCreateAmp(); - //flexRadio.commandMeterList(); - - - //flexRadio.sendCommand(FlexCommand.INFO, "info"); - //flexRadio.commandGetInfo(); - //flexRadio.commandSliceGetError(0); - - //flexRadio.sendCommand(FlexCommand.SLICE_GET_ERROR, "slice get_error 0"); - //flexRadio.sendCommand(FlexCommand.REMOTE_RADIO_RX_ON, "remote_audio rx_on on"); - // - //flexRadio.sendCommand("c1|client gui\n"); - //playData(); - - - } - - @Override - public void onConnectFail(RadioTcpClient tcpClient) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource - (R.string.flex_connect_failed),flexRadio.getModel())); - } - }); - - } - public void setMaxRfPower(int power){ - maxRfPower=power; - GeneralVariables.flexMaxRfPower=power; - flexRadio.commandSetRfPower(maxRfPower);//设置发射功率 - - } - public void setMaxTunePower(int power){ - maxTunePower=power; - GeneralVariables.flexMaxTunePower=power; - flexRadio.commandSetTunePower(maxTunePower);//设置调谐功率 - - } - public void startATU(){ - flexRadio.commandStartATU(); - } - public void tuneOnOff(boolean on){ - flexRadio.commandTuneTransmitOnOff(on); - } - public void subAllMeters(){ - if (flexMeterInfos.size()==0) { - flexRadio.commandMeterList();//列一下仪表 - flexRadio.commandSubMeterAll();//显示全部仪表消息 - } - } - - @Override - public void sendData(byte[] data) { - flexRadio.sendData(data); - } - - - @Override - public void setPttOn(boolean on) { - flexRadio.isPttOn=on; - flexRadio.commandPTTOnOff(on); - } - - @Override - public void setPttOn(byte[] command) { - //cableSerialPort.sendData(command);//以CAT指令发送PTT - } - - @Override - public void sendWaveData(float[] data) { - //Log.e(TAG, "sendWaveData: flexConnector:"+data.length ); - flexRadio.sendWaveData(data); - } - - @Override - public void connect() { - super.connect(); - flexRadio.openAudio(); - flexRadio.connect(); - flexRadio.openStreamPort(); - } - - @Override - public void disconnect() { - super.disconnect(); - flexRadio.closeAudio(); - flexRadio.closeStreamPort(); - flexRadio.disConnect(); - } - - public OnWaveDataReceived getOnWaveDataReceived() { - return onWaveDataReceived; - } - - public void setOnWaveDataReceived(OnWaveDataReceived onWaveDataReceived) { - this.onWaveDataReceived = onWaveDataReceived; - } - - /** - * 获取单声道的数据,24000hz采样率改为12000采样率,把立体声改为单声道 - * @param bytes 原始声音数据 - * @return 单声道数据 - */ - public static float[] getMonoFloatFromBytes(byte[] bytes) { - float[] floats = new float[bytes.length / 16]; - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); - for (int i = 0; i < floats.length; i++) { - try { - float f1,f2; - f1=dis.readFloat(); - dis.readFloat();//放弃一个声道 - f2=dis.readFloat(); - floats[i] = Math.max(f1,f2);//取最大值 - dis.readFloat();//放弃1个声道 - } catch (IOException e) { - e.printStackTrace(); - break; - } - } - try { - dis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return floats; - } - - @Override - public boolean isConnected() { - return flexRadio.isConnect(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java deleted file mode 100644 index 18fb917..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.bg7yoz.ft8cn.connector; -/** - * ICom网络方式的连接器。 - * 注:ICom网络方式的音频数据包是Int类型,需要转换成Float类型 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.icom.IComWifiRig; - -public class IComWifiConnector extends BaseRigConnector{ - private static final String TAG = "IComWifiConnector"; - public interface OnWifiDataReceived{ - void OnWaveReceived(int bufferLen,float[] buffer); - void OnCivReceived(byte[] data); - } - - private IComWifiRig iComWifiRig; - private OnWifiDataReceived onWifiDataReceived; - - - public IComWifiConnector(int controlMode,IComWifiRig iComWifiRig) { - super(controlMode); - this.iComWifiRig=iComWifiRig; - - this.iComWifiRig.setOnIComDataEvents(new IComWifiRig.OnIComDataEvents() { - @Override - public void onReceivedCivData(byte[] data) { - if (getOnConnectReceiveData()!=null){ - getOnConnectReceiveData().onData(data); - } - if (onWifiDataReceived!=null) { - onWifiDataReceived.OnCivReceived(data); - } - } - - @Override - public void onReceivedWaveData(byte[] data) {//接收音频数据事件,把音频数据转换成float格式的。 - if (onWifiDataReceived!=null){ - float[] waveFloat=new float[data.length/2]; - for (int i = 0; i { - private final SQLiteDatabase db; - private final AfterCount afterCount; - - public DistanceCount(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL; - Cursor cursor; - double maxDistance=-1; - String maxDistanceGrid=""; - double minDistance=6553500f; - String minDistanceGrid=""; - - ArrayList values=new ArrayList<>(); - - ArrayList bands=new ArrayList<>(); - querySQL="select DISTINCT band from QSLTable q WHERE gridsquare<>\"\""; - cursor = db.rawQuery(querySQL,null); - - while (cursor.moveToNext()){ - bands.add(cursor.getString(cursor.getColumnIndex("band"))); - } - - for (String band:bands) { - - querySQL = "SELECT DISTINCT SUBSTR(gridsquare,1,4) as g FROM QSLTable q where (gridsquare <>\"\")and(band=?)"; - cursor = db.rawQuery(querySQL, new String[]{band}); - double max=-1; - String maxGrid=""; - double min=6553500f; - String minGrid=""; - - while (cursor.moveToNext()) { - String grid = cursor.getString(cursor.getColumnIndex("g")); - double distance = MaidenheadGrid.getDist(grid, GeneralVariables.getMyMaidenheadGrid()); - if (distance > maxDistance) { - maxDistance = distance; - maxDistanceGrid = grid; - } - if (distance < minDistance) { - minDistance = distance; - minDistanceGrid = grid; - } - - if (distance > max) { - max = distance; - maxGrid = grid; - } - if (distance < min) { - min = distance; - minGrid = grid; - } - - } - cursor.close(); - - if ((max>-1)&&(min<6553500f)){ - values.add(new CountValue((int) Math.round(max),String.format( - GeneralVariables.getStringFromResource(R.string.maximum_distance) - ,getQSLInfo(maxGrid)))); - values.add(new CountValue((int) Math.round(min),String.format( - GeneralVariables.getStringFromResource(R.string.minimum_distance) - ,getQSLInfo(minGrid)))); - } - } - - String info=String.format(GeneralVariables.getStringFromResource(R.string.count_distance_info) - ,maxDistance,maxDistanceGrid,minDistance,minDistanceGrid); - - if (afterCount!=null &&(maxDistance>0)&&(minDistance<6553500f)){ - afterCount.countInformation(new CountInfo(info - ,ChartType.None - ,GeneralVariables.getStringFromResource(R.string.distance_statistics) - ,values)); - } - return null; - } - - @SuppressLint("Range") - private String getQSLInfo(String grid){ - String querySQL="SELECT call,band,freq,qso_date,time_on,gridsquare FROM QSLTable q " + - "where SUBSTR(gridsquare,1,4) =? LIMIT 1"; - Cursor cursor = db.rawQuery(querySQL,new String[]{grid}); - StringBuilder result=new StringBuilder(); - int breakLine=0; - while (cursor.moveToNext()){ - String call=cursor.getString(cursor.getColumnIndex("call")); - String band=cursor.getString(cursor.getColumnIndex("band")); - String freq=cursor.getString(cursor.getColumnIndex("freq")); - //String qso_date=cursor.getString(cursor.getColumnIndex("qso_date")); - //String time_on=cursor.getString(cursor.getColumnIndex("time_on")); - String gridsquare=cursor.getString(cursor.getColumnIndex("gridsquare")); - - //获取呼号的位置 - CallsignInfo callsignInfo= GeneralVariables.callsignDatabase.getCallInfo(call); - - if (breakLine>0) result.append("\n"); - result.append(String.format("%s %s(%s) %s\n %s", call, freq, band - ,gridsquare,callsignInfo.toString())); - breakLine++; - } - return result.toString(); - } - - - } - - - /** - * 统计各波段比例 - */ - static class GetBandCount extends AsyncTask { - private final SQLiteDatabase db; - private final AfterCount afterCount; - - public GetBandCount(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint({"Range", "DefaultLocale"}) - @Override - protected Void doInBackground(Void... voids) { - int successCount=0; - ArrayList values=new ArrayList<>(); - String querySQL; - Cursor cursor; - querySQL="SELECT UPPER( band) as band ,count(*) as c FROM QSLTable q \n" + - "GROUP BY UPPER( band) ORDER BY COUNT(*) desc "; - cursor = db.rawQuery(querySQL,null); - while (cursor.moveToNext()){ - int count=cursor.getInt(cursor.getColumnIndex("c")); - successCount=successCount+count; - values.add(new CountValue(count - ,String.format("%s",cursor.getString(cursor.getColumnIndex("band"))))); - } - StringBuilder stringBuilder=new StringBuilder(); - stringBuilder.append(String.format(GeneralVariables.getStringFromResource(R.string.count_total),successCount)); - for (int i = 0; i < values.size(); i++) { - stringBuilder.append(String.format("\n%s:\t%d",values.get(i).name,values.get(i).value)); - } - cursor.close(); - if (afterCount!=null){ - afterCount.countInformation(new CountInfo( - stringBuilder.toString() - ,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.band_statistics) - ,values)); - } - - return null; - } - } - /** - * 统计通联数量 - */ - static class GetTotal extends AsyncTask{ - private final SQLiteDatabase db; - private final AfterCount afterCount; - - public GetTotal(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint({"DefaultLocale", "Range"}) - @Override - protected Void doInBackground(Void... voids) { - - int logCount=0;//日志的数量 - int callsignCount=0;//呼号的数量 - int isQslCount=0;//确认的数量 - int isLotwQslCount=0;//三方平台确认的数量 - - //通联的呼号数量 - String querySQL; - Cursor cursor; - querySQL="SELECT count(*) AS C FROM QSLTable q"; - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - logCount=cursor.getInt(cursor.getColumnIndex("C")); - cursor.close(); - - querySQL="SELECT count(DISTINCT \"call\") AS C FROM QSLTable q "; - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - callsignCount=cursor.getInt(cursor.getColumnIndex("C")); - cursor.close(); - - querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isQSL =1 or isLotW_QSL =1"; - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - isQslCount=cursor.getInt(cursor.getColumnIndex("C")); - cursor.close(); - - querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isLotW_QSL =1"; - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - isLotwQslCount=cursor.getInt(cursor.getColumnIndex("C")); - cursor.close(); - - float qslPercent=0; - if (logCount>0){ - qslPercent=100f*(float) isQslCount/(float) logCount; - } - //result.append(String.format("通联的呼号数量:%d",cursor.getInt(cursor.getColumnIndex("C")))); - - if (afterCount!=null){ - ArrayList values=new ArrayList<>(); - values.add(new CountValue(isQslCount,GeneralVariables.getStringFromResource(R.string.count_confirmed))); - values.add(new CountValue(logCount-isQslCount,GeneralVariables.getStringFromResource(R.string.count_unconfirmed))); - StringBuilder stringBuilder=new StringBuilder(); - stringBuilder.append(GeneralVariables.getStringFromResource(R.string.count_total_logs)); - stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_log)); - stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_lotw_confirmed)); - stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_manually_confirmed)); - stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_proportion)); - stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_tota_callsigns)); - afterCount.countInformation(new CountInfo( - //String.format("日志数:%d\n确认的日志数:%d\n平台确认的日志数:%d" + - // "\n手动确认的日志数:%d\n日志确认比例:%.1f%%\n呼号数量:%d" - String.format(stringBuilder.toString() - ,logCount,isQslCount,isLotwQslCount,isQslCount-isLotwQslCount,qslPercent,callsignCount) - ,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.confirmation_statistics) - ,values)); - } - - return null; - } - } - - /** - * 统计ITU分区数量 - */ - static class GetItuZoneCount extends AsyncTask { - private static final String TAG = "GetItuZoneCount"; - private final SQLiteDatabase db; - private final AfterCount afterCount; - - public GetItuZoneCount(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint({"Range", "DefaultLocale"}) - @Override - protected Void doInBackground(Void... voids) { - ArrayList countValues=new ArrayList<>(); - int ituZoneCount=0; - int successCount=0; - String querySQL; - Cursor cursor; - - - querySQL="SELECT count(DISTINCT itu) as c From ituList il "; - - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - ituZoneCount=cursor.getInt(cursor.getColumnIndex("c")); - cursor.close(); - - querySQL="SELECT il.itu ,count(*) as c FROM ituList il \n" + - "inner join QSLTable q\n" + - "on il.grid =UPPER(SUBSTR(q.gridsquare,1,4))\n" + - "GROUP BY il.itu ORDER BY COUNT(*) DESC"; - - cursor = db.rawQuery(querySQL,null); - while (cursor.moveToNext()){ - successCount++; - countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) - ,String.format(GeneralVariables.getStringFromResource(R.string.count_zone) - ,cursor.getString(cursor.getColumnIndex("itu"))))); - } - - if (afterCount!=null){ - afterCount.countInformation(new CountInfo("" - ,ChartType.Bar - ,GeneralVariables.getStringFromResource(R.string.itu_completion_statistics) - ,countValues)); - } - if (afterCount!=null&&ituZoneCount!=0){ - ArrayList values=new ArrayList<>(); - values.add(new CountValue(successCount - ,GeneralVariables.getStringFromResource(R.string.count_completed))); - values.add(new CountValue(ituZoneCount-successCount - ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); - afterCount.countInformation(new CountInfo( - String.format(GeneralVariables.getStringFromResource(R.string.count_total_itu) - ,ituZoneCount,successCount,100f*(float)successCount/(float) ituZoneCount) - ,ChartType.Pie - ,GeneralVariables.getStringFromResource(R.string.count_itu_completed_scale) - ,values)); - } - return null; - } - } - - - - /** - * 统计CQ分区 - */ - static class GetCqZoneCount extends AsyncTask { - private static final String TAG = "GetCqZoneCount"; - private final SQLiteDatabase db; - private final AfterCount afterCount; - - public GetCqZoneCount(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint({"Range", "DefaultLocale"}) - @Override - protected Void doInBackground(Void... voids) { - ArrayList countValues=new ArrayList<>(); - int cqZoneCount=0; - int successCount=0; - String querySQL; - Cursor cursor; - - - querySQL="SELECT count(DISTINCT cqzone) as c From cqzoneList cl "; - - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - cqZoneCount=cursor.getInt(cursor.getColumnIndex("c")); - cursor.close(); - - querySQL="SELECT cl.cqzone ,count(*) as c FROM cqzoneList cl \n" + - "inner join QSLTable q \n" + - "on cl.grid =UPPER(SUBSTR(q.gridsquare,1,4)) \n" + - "GROUP BY cl.cqzone ORDER BY COUNT(*) DESC"; - - cursor = db.rawQuery(querySQL,null); - while (cursor.moveToNext()){ - successCount++; - countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) - ,String.format(GeneralVariables.getStringFromResource(R.string.count_zone) - ,cursor.getString(cursor.getColumnIndex("cqzone"))))); - } - - if (afterCount!=null){ - afterCount.countInformation(new CountInfo("" - ,ChartType.Bar - ,GeneralVariables.getStringFromResource(R.string.count_cqzone_completed) - ,countValues)); - } - if (afterCount!=null&&cqZoneCount!=0){ - ArrayList values=new ArrayList<>(); - values.add(new CountValue(successCount - ,GeneralVariables.getStringFromResource(R.string.count_completed))); - values.add(new CountValue(cqZoneCount-successCount - ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); - afterCount.countInformation(new CountInfo( - String.format(GeneralVariables.getStringFromResource(R.string.count_total_cqzone) - ,cqZoneCount,successCount,100f*(float)successCount/(float) cqZoneCount) - ,ChartType.Pie - ,GeneralVariables.getStringFromResource(R.string.count_cqzone_proportion) - ,values)); - } - - - return null; - } - } - /** - * 统计DXCC分区的数据 - */ - static class GetDxccCount extends AsyncTask{ - private static final String TAG="GetDxccCount"; - private final SQLiteDatabase db; - private final AfterCount afterCount; - - class DxccInfo{ - String name; - int dxcc; - int count=0; - - public DxccInfo(String name, int dxcc, int count) { - this.name = name; - this.dxcc = dxcc; - this.count = count; - } - } - - public GetDxccCount(SQLiteDatabase db, AfterCount afterCount) { - this.db = db; - this.afterCount = afterCount; - } - - @SuppressLint({"Range", "DefaultLocale"}) - @Override - protected Void doInBackground(Void... voids) { - //DXCC的数量 - ArrayList dxccValues=new ArrayList<>(); - int dxccCount=0; - Cursor cursor; - String querySQL; - querySQL="SELECT count(*) as c FROM dxcclist"; - cursor = db.rawQuery(querySQL,null); - cursor.moveToFirst(); - dxccCount=cursor.getInt(cursor.getColumnIndex("c")); - cursor.close(); - - if (GeneralVariables.isChina) { - querySQL = "SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_grid dg\n" + - "inner join QSLTable q \n" + - "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + - "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; -// querySQL="SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_prefix dg\n" + -// "inner join QSLTable q\n" + -// //"on (q.call like (dg.prefix||'%'))\n" + -// "on ((SUBSTR( q.call,1,LENGTH( dg.prefix)) = (dg.prefix))) \n"+ -// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + -// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; - }else { -// querySQL="SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_prefix dg\n" + -// "inner join QSLTable q\n" + -// "on (q.call like (dg.prefix||'%'))\n" + -// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + -// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; - querySQL = "SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_grid dg\n" + - "inner join QSLTable q \n" + - "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + - "GROUP BY dg.dxcc ,dl.aname ORDER BY count(*) DESC"; - - } - cursor = db.rawQuery(querySQL,null); - int successCount=0; - while (cursor.moveToNext()){ - dxccValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) - ,cursor.getString(cursor.getColumnIndex("dxccName")))); - successCount++; - } - - if (afterCount!=null){ - afterCount.countInformation(new CountInfo("" - ,ChartType.Bar - ,GeneralVariables.getStringFromResource(R.string.dxcc_completion_statistics) - ,dxccValues)); - } - - - if (afterCount!=null&&dxccCount!=0){ - ArrayList values=new ArrayList<>(); - values.add(new CountValue(successCount - ,GeneralVariables.getStringFromResource(R.string.count_completed))); - values.add(new CountValue(dxccCount-successCount - ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); - afterCount.countInformation(new CountInfo( - String.format(GeneralVariables.getStringFromResource(R.string.count_total_dxcc) - ,dxccCount,successCount,100f*(float)successCount/(float) dxccCount) - ,ChartType.Pie - ,GeneralVariables.getStringFromResource(R.string.count_dxcc_proportion) - ,values)); - } - - return null; - } - } - - - - - public interface AfterCount{ - void countInformation(CountInfo countInfo); - } - - static public class CountInfo{ - public String title; - public String info; - public ArrayList values=null; - public ChartType chartType; - - public CountInfo(String info,ChartType chartType,String title - , ArrayList values) { - this.info = info; - this.values = values; - this.title=title; - this.chartType=chartType; - } - } - - static public class CountValue{ - public int value; - public String name; - - public CountValue(int value, String name) { - this.value = value; - this.name = name; - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java deleted file mode 100644 index 0a6f0d2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.bg7yoz.ft8cn.count; -/** - * 通联日志的统计界面 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.fragment.app.Fragment; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.databinding.FragmentCountBinding; - -import java.util.ArrayList; - - -public class CountFragment extends Fragment { - private static final String TAG="CountFragment"; - private MainViewModel mainViewModel; - private FragmentCountBinding binding; - private ArrayList countInfoList=new ArrayList<>(); - private MutableLiveData> mutableInfoList=new MutableLiveData<>(); - - private RecyclerView countInfoListRecyclerView; - private CountInfoAdapter countInfoAdapter; - - private CountDbOpr.AfterCount afterCount=new CountDbOpr.AfterCount() { - @Override - public void countInformation(CountDbOpr.CountInfo countInfo) { - countInfoList.add(countInfo); - mutableInfoList.postValue(countInfoList); - } - }; - - - public CountFragment() { - // Required empty public constructor - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mainViewModel = MainViewModel.getInstance(this); - } - - @SuppressLint("NotifyDataSetChanged") - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - binding=FragmentCountBinding.inflate(getLayoutInflater()); - mainViewModel = MainViewModel.getInstance(this); - countInfoList.clear(); - countInfoListRecyclerView=binding.countRecyclerView; - countInfoAdapter=new CountInfoAdapter(requireContext(),countInfoList); - countInfoListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - countInfoListRecyclerView.setAdapter(countInfoAdapter); - countInfoAdapter.notifyDataSetChanged(); - - - - CountDbOpr.getQSLTotal(mainViewModel.databaseOpr.getDb(),afterCount); - - - CountDbOpr.getDxcc(mainViewModel.databaseOpr.getDb(),afterCount); - CountDbOpr.getCQZoneCount(mainViewModel.databaseOpr.getDb(),afterCount); - CountDbOpr.getItuCount(mainViewModel.databaseOpr.getDb(),afterCount); - - CountDbOpr.getBandCount(mainViewModel.databaseOpr.getDb(),afterCount); - CountDbOpr.getDistanceCount(mainViewModel.databaseOpr.getDb(),afterCount); - - - - mutableInfoList.observe(requireActivity(), new Observer>() { - @Override - public void onChanged(ArrayList countInfos) { - countInfoAdapter.notifyDataSetChanged(); - } - }); - - return binding.getRoot(); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java deleted file mode 100644 index 2f0e619..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.bg7yoz.ft8cn.count; -/** - * 用于列出统计结果的Adapter - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.R; -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Description; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.formatter.IndexAxisValueFormatter; - -import java.util.ArrayList; -import java.util.List; - -public class CountInfoAdapter extends RecyclerView.Adapter{ - private static final String TAG="CountInfoAdapter"; - private final ArrayList countInfoList; - private final Context context; - - public CountInfoAdapter(Context context,ArrayList countInfoList) { - this.countInfoList = countInfoList; - this.context=context; - } - - @NonNull - @Override - public CountInfoItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view ; - switch (viewType){ - case CountDbOpr.ChartPie: - view= layoutInflater.inflate(R.layout.count_info_pie_item, parent, false); - break; - case CountDbOpr.ChartBar: - view= layoutInflater.inflate(R.layout.count_info_bar_item, parent, false); - break; - default: - view= layoutInflater.inflate(R.layout.count_info_none_item, parent, false); - break; - } - - return new CountInfoItemHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull CountInfoItemHolder holder, int position) { - holder.countInfo=countInfoList.get(position); - holder.countInfoTextView.setText(holder.countInfo.info); - holder.countTitleTextView.setText(holder.countInfo.title); - switch (holder.countInfo.chartType){ - case Pie: - drawPie(holder); - break; - case Bar: - drawBar(holder); - break; - default: - addDetail(holder); - } - - } - - private void addDetail(@NonNull CountInfoItemHolder holder){ - if (holder.detailLayout==null) return; - holder.detailLayout.removeAllViews(); - for (int i = 0; i barEntries=new ArrayList<>(); - for (int i = 0; i (int) value) { - return holder.countInfo.values.get((int) value).name; - }else { - return ""; - } - } - }; - barChart.getXAxis().setValueFormatter(formatter); - - barChart.setDescription(null); - - barChart.animateY(500); - barChart.invalidate(); - - } - - private void drawPie(@NonNull CountInfoItemHolder holder){ - PieChart countPieChart; - countPieChart=(PieChart) holder.countChart; - countPieChart.getLegend().setTextColor(context.getResources().getColor(R.color.text_view_color)); - - List pieEntries=new ArrayList<>(); - for (int i = 0; i dxccObjects = loadDxccDataFromFile(); - for (DxccObject obj : dxccObjects) { - obj.insertToDb(sqLiteDatabase); - } - } - }).start(); - } - - } - - /** - * 把ITU分区的对应表导入数据库 - * - * @param sqLiteDatabase 数据库 - */ - private void createItuTables(SQLiteDatabase sqLiteDatabase) { - if (!checkTableExists(sqLiteDatabase, "ituList")) { - sqLiteDatabase.execSQL("CREATE TABLE ituList (itu INTEGER,grid TEXT)"); - new Thread(new Runnable() { - @Override - public void run() { - loadItuDataFromFile(sqLiteDatabase); - } - }).start(); - } - } - - private void createCqZoneTables(SQLiteDatabase sqLiteDatabase) { - if (!checkTableExists(sqLiteDatabase, "cqzoneList")) { - sqLiteDatabase.execSQL("CREATE TABLE cqzoneList (cqzone INTEGER,grid TEXT)"); - new Thread(new Runnable() { - @Override - public void run() { - loadICqZoneDataFromFile(sqLiteDatabase); - } - }).start(); - } - } - - /** - * 创建呼号与网格对应关系表 - * - * @param sqLiteDatabase db - */ - private void createCallsignQTHTables(SQLiteDatabase sqLiteDatabase) { - if (!checkTableExists(sqLiteDatabase, "CallsignQTH")) { - sqLiteDatabase.execSQL("CREATE TABLE CallsignQTH(callsign text, grid text" + - ",updateTime Int ,PRIMARY KEY(callsign))"); - } - } - - private void createSWLTables(SQLiteDatabase sqLiteDatabase) { - if (!checkTableExists(sqLiteDatabase, "SWLMessages")) { - sqLiteDatabase.execSQL("CREATE TABLE SWLMessages (\n" + - "\tID INTEGER PRIMARY KEY AUTOINCREMENT,\n" + - "\tI3 INTEGER,\n" + - "\tN3 INTEGER,\n" + - "\tProtocol TEXT,\n" + - "\tUTC TEXT,\n" + - "\tSNR INTEGER,\n" + - "\tTIME_SEC REAL,\n" + - "\tFREQ INTEGER,\n" + - "\tCALL_TO TEXT,\n" + - "\tCALL_FROM TEXT,\n" + - "\tEXTRAL TEXT,\n" + - "\tREPORT INTEGER,\n" + - "\tBAND INTEGER\n" + - ")"); - sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_CALL_TO_IDX " + - "ON SWLMessages (CALL_TO,CALL_FROM)"); - sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_UTC_IDX ON SWLMessages (UTC)"); - } - if (!checkTableExists(sqLiteDatabase, "SWLQSOTable")) { - sqLiteDatabase.execSQL("CREATE TABLE SWLQSOTable (\n" + - "\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n" + - "\t\"call\" TEXT,\n" + - "\tgridsquare TEXT,\n" + - "\tmode TEXT,\n" + - "\trst_sent TEXT,\n" + - "\trst_rcvd TEXT,\n" + - "\tqso_date TEXT,\n" + - "\ttime_on TEXT,\n" + - "\tqso_date_off TEXT,\n" + - "\ttime_off TEXT,\n" + - "\tband TEXT,\n" + - "\tfreq TEXT,\n" + - "\tstation_callsign TEXT,\n" + - "\tmy_gridsquare TEXT,\n" + - "\tcomment TEXT)"); - } - } - - - /** - * 创建索引,以提高导入速度 - * @param sqLiteDatabase 数据库 - */ - private void createIndex(SQLiteDatabase sqLiteDatabase) { - if (!checkIndexExists(sqLiteDatabase, "QslCallsigns_callsign_IDX")) { - sqLiteDatabase.execSQL("CREATE INDEX QslCallsigns_callsign_IDX ON QslCallsigns (callsign,startTime,finishTime,mode)"); - } - if (!checkIndexExists(sqLiteDatabase, "QSLTable_call_IDX")) { - sqLiteDatabase.execSQL("CREATE INDEX QSLTable_call_IDX ON QSLTable (\"call\",qso_date,time_on,mode)"); - } - } - - - public void loadItuDataFromFile(SQLiteDatabase db) { - AssetManager assetManager = context.getAssets(); - InputStream inputStream; - db.execSQL("delete from ituList"); - - String insertSQL = "INSERT INTO ituList (itu,grid)" + - "VALUES(?,?)"; - try { - inputStream = assetManager.open("ituzone.json"); - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - JSONObject jsonObject = new JSONObject(new String(bytes)); - JSONArray array = jsonObject.names(); - for (int i = 0; i < array.length(); i++) { - JSONObject ituObject = new JSONObject(jsonObject.getString(array.getString(i))); - JSONArray mh = ituObject.getJSONArray("mh"); - for (int j = 0; j < mh.length(); j++) { - db.execSQL(insertSQL, new Object[]{array.getString(i), mh.getString(j)}); - } - } - inputStream.close(); - } catch (IOException | JSONException e) { - e.printStackTrace(); - Log.e(TAG, "loadDataFromFile: " + e.getMessage()); - } - } - - public void loadICqZoneDataFromFile(SQLiteDatabase db) { - AssetManager assetManager = context.getAssets(); - InputStream inputStream; - db.execSQL("delete from cqzoneList"); - String insertSQL = "INSERT INTO cqzoneList (cqzone,grid)" + - "VALUES(?,?)"; - try { - inputStream = assetManager.open("cqzone.json"); - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - JSONObject jsonObject = new JSONObject(new String(bytes)); - JSONArray array = jsonObject.names(); - for (int i = 0; i < array.length(); i++) { - JSONObject ituObject = new JSONObject(jsonObject.getString(array.getString(i))); - JSONArray mh = ituObject.getJSONArray("mh"); - for (int j = 0; j < mh.length(); j++) { - db.execSQL(insertSQL, new Object[]{array.getString(i), mh.getString(j)}); - } - } - inputStream.close(); - } catch (IOException | JSONException e) { - e.printStackTrace(); - Log.e(TAG, "loadDataFromFile: " + e.getMessage()); - } - } - - - public ArrayList loadDxccDataFromFile() { - AssetManager assetManager = context.getAssets(); - InputStream inputStream; - ArrayList dxccObjects = new ArrayList<>(); - try { - inputStream = assetManager.open("dxcc_list.json"); - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - JSONObject jsonObject = new JSONObject(new String(bytes)); - JSONArray array = jsonObject.names(); - - for (int i = 0; i < array.length(); i++) { - if (array.getString(i).equals("-1")) continue; - JSONObject dxccObject = new JSONObject(jsonObject.getString(array.getString(i))); - DxccObject dxcc = new DxccObject(); - dxcc.id = Integer.parseInt(array.getString(i)); - dxcc.dxcc = dxccObject.getInt("dxcc"); - dxcc.cc = dxccObject.getString("cc"); - dxcc.ccc = dxccObject.getString("ccc"); - dxcc.name = dxccObject.getString("name"); - dxcc.continent = dxccObject.getString("continent"); - dxcc.ituZone = dxccObject.getString("ituzone") - .replace("[", "") - .replace("]", "") - .replace("\"", ""); - dxcc.cqZone = dxccObject.getString("cqzone") - .replace("[", "") - .replace("]", "") - .replace("\"", ""); - dxcc.timeZone = dxccObject.getInt("timezone"); - dxcc.cCode = dxccObject.getInt("ccode"); - dxcc.aName = dxccObject.getString("aname"); - dxcc.pp = dxccObject.getString("pp"); - dxcc.lat = dxccObject.getDouble("lat"); - dxcc.lon = dxccObject.getDouble("lon"); - - JSONArray mh = dxccObject.getJSONArray("mh"); - for (int j = 0; j < mh.length(); j++) { - dxcc.grid.add(mh.getString(j)); - } - JSONArray prefix = dxccObject.getJSONArray("prefix"); - for (int j = 0; j < prefix.length(); j++) { - dxcc.prefix.add(prefix.getString(j)); - } - dxccObjects.add(dxcc); - //Log.e(TAG, "loadDataFromFile: id:" + dxcc.id + " dxcc:" + dxcc.dxcc); - } - - inputStream.close(); - } catch (IOException | JSONException e) { - e.printStackTrace(); - Log.e(TAG, "loadDataFromFile: " + e.getMessage()); - } - return dxccObjects; - } - - - /** - * 把呼号和网格对应关系写入表中 - * - * @param callsign 呼号 - * @param grid 网格 - */ - public void addCallsignQTH(String callsign, String grid) { - if (grid.trim().length() < 4) return; - new AddCallsignQTH(db).execute(callsign, grid); - Log.d(TAG, String.format("addCallsignQTH: callsign:%s,grid:%s", callsign, grid)); - } - - //查询配置信息。 - public void getConfigByKey(String KeyName, OnAfterQueryConfig onAfterQueryConfig) { - new QueryConfig(db, KeyName, onAfterQueryConfig).execute(); - } - - public void getCallSign(String callsign, String fieldName, String tableName, OnGetCallsign getCallsign) { - new QueryCallsign(db, tableName, fieldName, callsign, getCallsign).execute(); - } - - /** - * 写配置信息,异步操作 - */ - public void writeConfig(String KeyName, String Value, OnAfterWriteConfig onAfterWriteConfig) { - Log.d(TAG, "writeConfig: Value:" + Value); - new WriteConfig(db, KeyName, Value, onAfterWriteConfig).execute(); - } - - public void writeMessage(ArrayList messages) { - new WriteMessages(db, messages).execute(); - } - - /** - * 读取关注的呼号列表 - * - * @param onAffterQueryFollowCallsigns 回调函数 - */ - public void getFollowCallsigns(OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { - new GetFollowCallSigns(db, onAffterQueryFollowCallsigns).execute(); - } - - /** - * 查询SWL MESSAGE各BAND的数量 - * @param onAfterQueryFollowCallsigns 回调 - */ - public void getMessageLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) { - new GetMessageLogTotal(db, onAfterQueryFollowCallsigns).execute(); - } - - /** - * 查询SWL QSO的在各个月的数量 - * @param onAfterQueryFollowCallsigns 回调 - */ - public void getSWLQsoLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) { - new GetSWLQsoTotal(db, onAfterQueryFollowCallsigns).execute(); - } - - - /** - * 向数据库中添加关注的呼号 - * - * @param callsign 呼号 - */ - public void addFollowCallsign(String callsign) { - new AddFollowCallSign(db, callsign).execute(); - } - - /** - * 清空关注的呼号 - */ - public void clearFollowCallsigns() { - new Thread(new Runnable() { - @Override - public void run() { - db.execSQL("delete from followCallsigns "); - } - }).start(); - } - - /** - * 删除通联的日志 - */ - public void clearLogCacheData() { - new Thread(new Runnable() { - @Override - public void run() { - db.execSQL("delete from SWLMessages "); - } - }).start(); - } - - /** - * 删除SWL QSO日志 - */ - public void clearSWLQsoData() { - new Thread(new Runnable() { - @Override - public void run() { - db.execSQL("delete from SWLQSOTable "); - } - }).start(); - } - /** - * 把通联成功的日志和呼号写到数据库中 - * - * @param qslRecord 通联记录 - */ - public void addQSL_Callsign(QSLRecord qslRecord) { - new AddQSL_Info(this, qslRecord).execute(); - } - - /** - * 把SWL的QSO保存到数据库,SWL的QSO标准:至少要有双方的信号报告。不包含自己的呼号。 - * @param qslRecord 通联日志记录 - */ - public void addSWL_QSO(QSLRecord qslRecord) { - new Add_SWL_QSO_Info(this, qslRecord).execute(); - } - - //删除数据库中关注的呼号 - public void deleteFollowCallsign(String callsign) { - new DeleteFollowCallsign(db, callsign).execute(); - } - - //获取所有配置参数 - public void getAllConfigParameter(OnAfterQueryConfig onAfterQueryConfig) { - new GetAllConfigParameter(db, onAfterQueryConfig).execute(); - } - - /** - * 查询全部成功通联的呼号,能通联的频率为条件 - */ - public void getAllQSLCallsigns() { - new LoadAllQSLCallsigns(db).execute(); - } - - - /** - * 按呼号查找QSL的呼号记录 - * - * @param callsign 呼号 - * @param onQueryQSLCallsign 回调 - */ - public void getQSLCallsignsByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLCallsign onQueryQSLCallsign) { - new GetQLSCallsignByCallsign(showAll,offset,db, callsign, filter, onQueryQSLCallsign).execute(); - } - - /** - * 查询已经QSO的网格,这个主要用在GridTracker上 - * 可以知道哪些网格是QSO,哪些是QSL - * - * @param onGetQsoGrids 当查询结束之后的事件。 - */ - public void getQsoGridQuery(OnGetQsoGrids onGetQsoGrids) { - new GetQsoGrids(db, onGetQsoGrids).execute(); - } - - /** - * 按呼号查询QSL记录 - * - * @param callsign 呼号 - * @param onQueryQSLRecordCallsign 回调 - */ - public void getQSLRecordByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) { - new GetQSLByCallsign(showAll,offset,db, callsign, filter, onQueryQSLRecordCallsign).execute(); - } - - /** - * 删除通联呼号 - * - * @param id id号 - */ - public void deleteQSLCallsign(int id) { - new DeleteQSLCallsignByID(db, id).execute(); - } - - /** - * 删除日志 - * - * @param id id号 - */ - public void deleteQSLByID(int id) { - new DeleteQSLByID(db, id).execute(); - } - - /** - * 修改日志的手工确认 - * - * @param isQSL 是否确认 - * @param id ID号 - */ - public void setQSLTableIsQSL(boolean isQSL, int id) { - new SetQSLTableIsQSL(db, id, isQSL).execute(); - } - - public void setQSLCallsignIsQSL(boolean isQSL, int id) { - new SetQSLCallsignIsQSL(db, id, isQSL).execute(); - } - - /** - * 到数据库中查呼号和网格的对应关系,查出后,会把数据写入到GeneralVariables的callsignAndGrids中 - * - * @param callsign 呼号 - */ - public void getCallsignQTH(String callsign) { - new GetCallsignQTH(db).execute(callsign); - } - - /** - * 把已经通联的DXCC分区列出来 - */ - @SuppressLint("Range") - public void getQslDxccToMap() { - new Thread(new Runnable() { - @Override - public void run() { - String querySQL; - Cursor cursor; - Log.d(TAG, "run: 开始导入分区..."); - - //导入已经通联的dxcc - querySQL = "SELECT DISTINCT dl.pp FROM dxcc_grid dg\n" + - "inner join QSLTable q\n" + - "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc"; - cursor = db.rawQuery(querySQL, null); - while (cursor.moveToNext()) { - GeneralVariables.addDxcc(cursor.getString(cursor.getColumnIndex("pp"))); - } - cursor.close(); - - //导入已经通联的CQ分区 - querySQL = "SELECT DISTINCT cl.cqzone as cq FROM cqzoneList cl\n" + - "inner join QSLTable q\n" + - "on cl.grid =UPPER(SUBSTR(q.gridsquare,1,4)) "; - cursor = db.rawQuery(querySQL, null); - while (cursor.moveToNext()) { - GeneralVariables.addCqZone(cursor.getInt(cursor.getColumnIndex("cq"))); - } - cursor.close(); - - //导入已经通联的itu分区 - querySQL = "SELECT DISTINCT il.itu FROM ituList il\n" + - "inner join QSLTable q\n" + - "on il.grid =UPPER(SUBSTR(q.gridsquare,1,4))"; - cursor = db.rawQuery(querySQL, null); - while (cursor.moveToNext()) { - GeneralVariables.addItuZone(cursor.getInt(cursor.getColumnIndex("itu"))); - } - cursor.close(); - - Log.d(TAG, "run: 分区导入完毕..."); - } - }).start(); - - } - - - /** - * 检查通联的呼号是不是存在,如果存在,返回TRUE,并且更新isLotW_QSL, - * - * @param record 记录 - * @return 是否存在 - */ - @SuppressLint("Range") - public boolean checkQSLCallsign(QSLRecord record) { - QSLRecord newRecord = record; - newRecord.id = -1; - //检查是不是已经存在呼号了 - String querySQL = "select * from QslCallsigns WHERE (callsign=?)" + - "and (startTime=?) and(finishTime=?)" + - "and(mode=?)"; - - Cursor cursor = db.rawQuery(querySQL, new String[]{ - record.getToCallsign() - , record.getStartTime() - , record.getEndTime() - , record.getMode()}); - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - newRecord.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1 - || record.isLotW_QSL; - newRecord.id = cursor.getLong(cursor.getColumnIndex("ID")); - } - cursor.close(); -// if (newRecord.id != -1) {//说明已经存在记录了 -// querySQL = "UPDATE QslCallsigns set isLotW_QSL=? WHERE ID=?"; -// db.execSQL(querySQL, new Object[]{newRecord.isLotW_QSL ? "1" : "0", newRecord.id}); -// } - return newRecord.id != -1;// - } - - @SuppressLint("Range") - public boolean checkIsQSL(QSLRecord record) { - QSLRecord newRecord = record; - newRecord.id = -1; - //检查是不是已经存在日志记录了 - String querySQL = "select * from QSLTable WHERE (call=?)" + - "and (qso_date=?) and(time_on=?)" + - "and(mode=?)"; - - Cursor cursor = db.rawQuery(querySQL, new String[]{ - record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - newRecord.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1 - || record.isLotW_QSL; - newRecord.id = cursor.getLong(cursor.getColumnIndex("id")); - } - cursor.close(); - -// if (newRecord.id != -1) {//说明已经存在记录了 -// querySQL = "UPDATE QSLTable set isLotW_QSL=? WHERE ID=?"; -// db.execSQL(querySQL, new Object[]{newRecord.isLotW_QSL ? "1" : "0", newRecord.id}); -// } - return newRecord.id != -1;// - } - - @SuppressLint("Range") - public boolean doInsertQSLData(QSLRecord record,AfterInsertQSLData afterInsertQSLData) { - if (record.getToCallsign() == null) { - if (afterInsertQSLData!=null){ - afterInsertQSLData.doAfterInsert(true,true);//说明是无效的QSL - } - return false; - } - - String querySQL; - if (!checkQSLCallsign(record)) {//如果不存在记录,就添加 - querySQL = "INSERT INTO QslCallsigns (callsign" + - ",isQSL,isLotW_import,isLotW_QSL" + - ",startTime,finishTime,mode,grid,band,band_i)" + - "values(?,?,?,?,?,?,?,?,?,?)"; - db.execSQL(querySQL, new Object[]{record.getToCallsign() - , record.isQSL ? 1 : 0//是否手工确认 - , record.isLotW_import ? 1 : 0//是否lotw导入 - , record.isLotW_QSL ? 1 : 0//是否lotw确认 - , record.getStartTime() - , record.getEndTime() - , record.getMode() - , record.getToMaidenGrid() - , BaseRigOperation.getFrequencyAllInfo(record.getBandFreq()) - , record.getBandFreq()}); - } else { - if (record.isQSL) { - db.execSQL("UPDATE QslCallsigns SET isQSL=? " + - "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" - , new Object[]{1, record.getToCallsign(), record.getStartTime() - , record.getEndTime(), record.getMode()}); - } - if (record.isLotW_import) { - db.execSQL("UPDATE QslCallsigns SET isLotW_import=? " + - "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" - , new Object[]{1, record.getToCallsign(), record.getStartTime() - , record.getEndTime(), record.getMode()}); - } - - if (record.isLotW_QSL) { - db.execSQL("UPDATE QslCallsigns SET isLotW_QSL=? " + - "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" - , new Object[]{1, record.getToCallsign(), record.getStartTime() - , record.getEndTime(), record.getMode()}); - } - if (record.getToMaidenGrid().length() >= 4) { - db.execSQL("UPDATE QslCallsigns SET grid=? " + - "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" - , new Object[]{record.getToMaidenGrid(), record.getToCallsign(), record.getStartTime() - , record.getEndTime(), record.getMode()}); - } - - } - - - if (!checkIsQSL(record)) {//如果不存在日志数据就添加 - querySQL = "INSERT INTO QSLTable(call, isQSL,isLotW_import,isLotW_QSL,gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + - "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare," + - "comment)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - - db.execSQL(querySQL, new String[]{record.getToCallsign() - , String.valueOf(record.isQSL ? 1 : 0) - , String.valueOf(record.isLotW_import ? 1 : 0) - , String.valueOf(record.isLotW_QSL ? 1 : 0) - , record.getToMaidenGrid() - , record.getMode() - , String.valueOf(record.getSendReport()) - , String.valueOf(record.getReceivedReport()) - , record.getQso_date() - , record.getTime_on() - - , record.getQso_date_off() - , record.getTime_off() - , record.getBandLength()//波长//RigOperationConstant.getMeterFromFreq(qslRecord.getBandFreq()) - , BaseRigOperation.getFrequencyFloat(record.getBandFreq()) - , record.getMyCallsign() - , record.getMyMaidenGrid() - , record.getComment()}); - if (afterInsertQSLData!=null){ - afterInsertQSLData.doAfterInsert(false,true);//说明是新的QSL - } - - } else { - if (record.isQSL) { - db.execSQL("UPDATE QSLTable SET isQSL=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{1, record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.isLotW_import) { - db.execSQL("UPDATE QSLTable SET isLotW_import=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{1, record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.isLotW_QSL) { - db.execSQL("UPDATE QSLTable SET isLotW_QSL=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{1, record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.getToMaidenGrid().length() >= 4) { - db.execSQL("UPDATE QSLTable SET gridsquare=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{record.getToMaidenGrid(), record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.getMyMaidenGrid().length() >= 4) { - db.execSQL("UPDATE QSLTable SET my_gridsquare=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{record.getMyMaidenGrid(), record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.getSendReport() > -100) { - db.execSQL("UPDATE QSLTable SET rst_sent=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{record.getSendReport(), record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - if (record.getReceivedReport() > -100) { - db.execSQL("UPDATE QSLTable SET rst_rcvd=? " + - " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" - , new Object[]{record.getReceivedReport(), record.getToCallsign() - , record.getQso_date() - , record.getTime_on() - , record.getMode()}); - } - - if (afterInsertQSLData!=null){ - afterInsertQSLData.doAfterInsert(false,false);//说明已经存在,需要更新的QSL - } - } - return true; - } - - - /** - * 查询配置信息的类 - */ - static class QueryConfig extends AsyncTask { - private final SQLiteDatabase db; - private final String KeyName; - private final OnAfterQueryConfig afterQueryConfig; - - public QueryConfig(SQLiteDatabase db, String keyName, OnAfterQueryConfig afterQueryConfig) { - this.db = db; - KeyName = keyName; - this.afterQueryConfig = afterQueryConfig; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (afterQueryConfig != null) { - afterQueryConfig.doOnBeforeQueryConfig(KeyName); - } - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL = "select keyName,Value from config where KeyName =?"; - Cursor cursor = db.rawQuery(querySQL, new String[]{KeyName.toString()}); - if (cursor.moveToFirst()) { - if (afterQueryConfig != null) { - afterQueryConfig.doOnAfterQueryConfig(KeyName, cursor.getString(cursor.getColumnIndex("Value"))); - } - } else { - if (afterQueryConfig != null) { - afterQueryConfig.doOnAfterQueryConfig(KeyName, ""); - } - } - cursor.close(); - return null; - } - } - - static class QueryCallsign extends AsyncTask { - private final SQLiteDatabase db; - private final String tableName; - private final String fieldName; - private final String callSign; - private OnGetCallsign onGetCallsign; - - public QueryCallsign(SQLiteDatabase db, String tableName, String fieldName - , String callSign, OnGetCallsign onGetCallsign) { - this.db = db; - this.tableName = tableName; - this.fieldName = fieldName; - this.callSign = callSign; - this.onGetCallsign = onGetCallsign; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String sql = String.format("select count(%s) as a FROM %s where %s=\"%s\" limit 1" - , fieldName, tableName, fieldName, callSign); - Cursor cursor = db.rawQuery(sql, null); - if (cursor.moveToFirst()) { - if (onGetCallsign != null) { - onGetCallsign.doOnAfterGetCallSign(cursor.getInt(cursor.getColumnIndex("a")) > 0); - } - } else { - if (onGetCallsign != null) { - onGetCallsign.doOnAfterGetCallSign(false); - } - - } - cursor.close(); - return null; - } - } - - /** - * 写配置信息的类 - */ - static class WriteConfig extends AsyncTask { - private final SQLiteDatabase db; - private final String KeyName; - private final String Value; - private final OnAfterWriteConfig afterWriteConfig; - - public WriteConfig(SQLiteDatabase db, String keyName, String Value, OnAfterWriteConfig afterWriteConfig) { - this.db = db; - this.KeyName = keyName; - this.afterWriteConfig = afterWriteConfig; - this.Value = Value; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL = "DELETE FROM config where KeyName =?"; - db.execSQL(querySQL, new String[]{KeyName.toString()}); - querySQL = "INSERT INTO config (KeyName,Value)Values(?,?)"; - db.execSQL(querySQL, new String[]{KeyName.toString(), Value.toString()}); - if (afterWriteConfig != null) { - afterWriteConfig.doOnAfterWriteConfig(true); - } - return null; - } - } - - /** - * 把消息写到数据库 - */ - static class WriteMessages extends AsyncTask { - private final SQLiteDatabase db; - private ArrayList messages; - - public WriteMessages(SQLiteDatabase db, ArrayList messages) { - this.db = db; - this.messages = messages; - } - - @Override - protected Void doInBackground(Void... voids) { - String sql = "INSERT INTO SWLMessages(I3,N3,Protocol,UTC,SNR,TIME_SEC,FREQ,CALL_FROM" + - ",CALL_TO,EXTRAL,REPORT,BAND)\n" + - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; - for (Ft8Message message : messages) {//只对与我有关的消息做保存 - db.execSQL(sql, new Object[]{message.i3, message.n3, "FT8" - ,UtcTimer.getDatetimeYYYYMMDD_HHMMSS(message.utcTime) - , message.snr, message.time_sec, Math.round(message.freq_hz) - , message.callsignFrom, message.callsignTo, message.extraInfo - , message.report, message.band}); - - } - return null; - } - } - - /** - * 把关注的呼号写到数据库 - */ - static class AddFollowCallSign extends AsyncTask { - private final SQLiteDatabase db; - private final String callSign; - - public AddFollowCallSign(SQLiteDatabase db, String callSign) { - this.db = db; - this.callSign = callSign; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL = "INSERT OR IGNORE INTO followCallsigns (callsign)values(?)"; - db.execSQL(querySQL, new String[]{callSign}); - return null; - } - } - - /** - * 向呼号网格对应表中写数据,AsyncTask中的String,是多参数,以数组形式给doInBackground - * 所以,写入数据第一个元素是呼号,第二个是网格 - */ - static class AddCallsignQTH extends AsyncTask { - private final SQLiteDatabase db; - - public AddCallsignQTH(SQLiteDatabase db) { - this.db = db; - } - - @Override - protected Void doInBackground(String... strings) { - if (strings.length == 2) { - String querySQL = "INSERT OR REPLACE INTO CallsignQTH (callsign,grid,updateTime)" + - "VALUES (Upper(?),?,?)"; - db.execSQL(querySQL, new Object[]{strings[0], strings[1], System.currentTimeMillis()}); - } - return null; - } - } - - static class Add_SWL_QSO_Info extends AsyncTask{ - private final DatabaseOpr databaseOpr; - private QSLRecord qslRecord; - public Add_SWL_QSO_Info(DatabaseOpr opr, QSLRecord qslRecord) { - this.databaseOpr = opr; - this.qslRecord = qslRecord; - } - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL; - //删除之前重复的记录 - querySQL = "DELETE FROM SWLQSOTable where ([call]=?) and (station_callsign=?) and (qso_date=?) and(time_on=?) and (freq=?)"; - databaseOpr.db.execSQL(querySQL, new String[]{ - qslRecord.getToCallsign() - , qslRecord.getMyCallsign() - , qslRecord.getQso_date() - , qslRecord.getTime_on() - , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) - }); - //添加记录 - querySQL = "INSERT INTO SWLQSOTable([call], gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + - "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare,comment)\n" + - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - - databaseOpr.db.execSQL(querySQL, new String[]{qslRecord.getToCallsign() - , qslRecord.getToMaidenGrid() - , qslRecord.getMode() - , String.valueOf(qslRecord.getSendReport()) - , String.valueOf(qslRecord.getReceivedReport()) - , qslRecord.getQso_date() - , qslRecord.getTime_on() - - , qslRecord.getQso_date_off() - , qslRecord.getTime_off() - , qslRecord.getBandLength()//波长//RigOperationConstant.getMeterFromFreq(qslRecord.getBandFreq()) - , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) - , qslRecord.getMyCallsign() - , qslRecord.getMyMaidenGrid() - , qslRecord.getComment()}); - - - return null; - } - - } - - /** - * 把QSL成功的呼号写到库中 - */ - static class AddQSL_Info extends AsyncTask { - //private final SQLiteDatabase db; - private final DatabaseOpr databaseOpr; - private QSLRecord qslRecord; - - public AddQSL_Info(DatabaseOpr opr, QSLRecord qslRecord) { - this.databaseOpr = opr; - this.qslRecord = qslRecord; - } - - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - databaseOpr.doInsertQSLData(qslRecord,null);//添加日志和通联成功的呼号 - return null; - } - } - - - /** - * 从数据库中删除关注的呼号 - */ - static class DeleteFollowCallsign extends AsyncTask { - private final SQLiteDatabase db; - private final String callSign; - - public DeleteFollowCallsign(SQLiteDatabase db, String callSign) { - this.db = db; - this.callSign = callSign; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String querySQL = "DELETE from followCallsigns WHERE callsign=?"; - db.execSQL(querySQL, new String[]{callSign}); - return null; - } - } - - /** - * 向呼号与网格对应关系表中查网格,参数是呼号 - */ - static class GetCallsignQTH extends AsyncTask { - private final SQLiteDatabase db; - - GetCallsignQTH(SQLiteDatabase db) { - this.db = db; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(String... strings) { - if (strings.length == 0) return null; - String querySQL = "select grid from CallsignQTH cq \n" + - "WHERE callsign =?"; - Cursor cursor = db.rawQuery(querySQL, new String[]{strings[0]}); - if (cursor.moveToFirst()) { - GeneralVariables.addCallsignAndGrid(strings[0] - , cursor.getString(cursor.getColumnIndex("grid"))); - } - cursor.close(); - - return null; - } - } - - static class GetMessageLogTotal extends AsyncTask { - private final SQLiteDatabase db; - private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; - - public GetMessageLogTotal(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { - this.db = db; - this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; - } - - @Override - @SuppressLint({"Range", "DefaultLocale"}) - protected Void doInBackground(Void... voids) { - String querySQL = "SELECT BAND ,count(*) as c from SWLMessages m group by BAND order by BAND "; - Cursor cursor = db.rawQuery(querySQL, new String[]{}); - ArrayList callsigns = new ArrayList<>(); - callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total)); - callsigns.add("---------------------------------------"); - int sum = 0; - while (cursor.moveToNext()) { - long s = cursor.getLong(cursor.getColumnIndex("BAND")); //获取频段 - int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量 - callsigns.add(String.format("%.3fMHz \t %d", s / 1000000f, total)); - sum = sum + total; - } - callsigns.add(String.format("-----------Total %d -----------", sum)); - cursor.close(); - if (onAffterQueryFollowCallsigns != null) { - onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); - } - return null; - } - } - - - static class GetSWLQsoTotal extends AsyncTask { - private final SQLiteDatabase db; - private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; - - public GetSWLQsoTotal(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { - this.db = db; - this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; - } - - @Override - @SuppressLint({"Range", "DefaultLocale"}) - protected Void doInBackground(Void... voids) { - String querySQL = "select count(*) as c,substr(qso_date_off,1,6) as t \n" + - "from SWLQSOTable s\n" + - "group by substr(qso_date_off,1,6)"; - Cursor cursor = db.rawQuery(querySQL, new String[]{}); - ArrayList callsigns = new ArrayList<>(); - //callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total)); - callsigns.add("---------------------------------------"); - int sum = 0; - while (cursor.moveToNext()) { - String date = cursor.getString(cursor.getColumnIndex("t")); //获取频段 - int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量 - callsigns.add(String.format("%s \t %d ", date, total)); - sum = sum + total; - } - callsigns.add(String.format("-----------Total %d -----------", sum)); - cursor.close(); - if (onAffterQueryFollowCallsigns != null) { - onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); - } - return null; - } - } - - - - /** - * 从数据库中获取关注的呼号类 - */ - static class GetFollowCallSigns extends AsyncTask { - private final SQLiteDatabase db; - private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; - - public GetFollowCallSigns(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { - this.db = db; - this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; - } - - @Override - protected Void doInBackground(Void... voids) { - String querySQL = "select callsign from followCallsigns"; - Cursor cursor = db.rawQuery(querySQL, new String[]{}); - ArrayList callsigns = new ArrayList<>(); - while (cursor.moveToNext()) { - @SuppressLint("Range") - String s = cursor.getString(cursor.getColumnIndex("callsign")); //获取第一列的值,第一列的索引从0开始 - if (s != null) { - callsigns.add(s); - } - } - cursor.close(); - if (onAffterQueryFollowCallsigns != null) { - onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); - } - return null; - } - } - - public static class GetCallsignMapGrid extends AsyncTask { - SQLiteDatabase db; - - public GetCallsignMapGrid(SQLiteDatabase db) { - this.db = db; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - - String querySQL = "select DISTINCT callsign,grid from QslCallsigns qc \n" + - "where LENGTH(grid)>3\n" + - "order by ID "; - Cursor cursor = db.rawQuery(querySQL, null); - while (cursor.moveToNext()) { - GeneralVariables.addCallsignAndGrid(cursor.getString(cursor.getColumnIndex("callsign")) - , cursor.getString(cursor.getColumnIndex("grid"))); - - } - cursor.close(); - return null; - } - } - - public interface OnGetQsoGrids { - void onAfterQuery(HashMap grids); - } - - - static class GetQsoGrids extends AsyncTask { - SQLiteDatabase db; - HashMap grids = new HashMap<>(); - OnGetQsoGrids onGetQsoGrids; - - public GetQsoGrids(SQLiteDatabase db, OnGetQsoGrids onGetQsoGrids) { - this.db = db; - this.onGetQsoGrids = onGetQsoGrids; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - - String querySQL = "select qc.gridsquare ,count(*) as cc,SUM(isQSL)+SUM(isLotW_QSL)as isQSL\n" + - "from QSLTable qc\n" + - "WHERE LENGTH (qc.gridsquare)>2 \n" + - "group by qc.gridsquare\n" + - "ORDER by SUM(isQSL)+SUM(isLotW_QSL) desc"; - Cursor cursor = db.rawQuery(querySQL, null); - - while (cursor.moveToNext()) { - grids.put(cursor.getString(cursor.getColumnIndex("gridsquare")) - , cursor.getInt(cursor.getColumnIndex("isQSL")) != 0); - - } - cursor.close(); - if (onGetQsoGrids != null) { - onGetQsoGrids.onAfterQuery(grids); - } - return null; - } - } - - static class GetQSLByCallsign extends AsyncTask { - boolean showAll; - int offset; - SQLiteDatabase db; - String callsign; - int filter; - OnQueryQSLRecordCallsign onQueryQSLRecordCallsign; - - public GetQSLByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) { - this.showAll=showAll; - this.offset=offset; - this.db = db; - this.callsign = callsign; - this.filter = queryFilter; - this.onQueryQSLRecordCallsign = onQueryQSLRecordCallsign; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String filterStr; - switch (filter) { - case 1: - filterStr = "and((isQSL =1)or(isLotW_QSL =1))\n"; - break; - case 2: - filterStr = "and((isQSL =0)and(isLotW_QSL =0))\n"; - break; - default: - filterStr = ""; - } - String limitStr=""; - if (!showAll){ - limitStr="limit 100 offset "+offset; - } - String querySQL = "select * from QSLTable where ([call] like ?) \n" + - filterStr + - " ORDER BY qso_date DESC, time_off DESC\n"+ - //" order by ID desc\n"+ - limitStr; - Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"}); - ArrayList records = new ArrayList<>(); - while (cursor.moveToNext()) { - QSLRecordStr record = new QSLRecordStr(); - record.id = cursor.getInt(cursor.getColumnIndex("id")); - record.setCall(cursor.getString(cursor.getColumnIndex("call"))); - record.isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; - record.isLotW_import = cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1; - record.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; - record.setGridsquare(cursor.getString(cursor.getColumnIndex("gridsquare"))); - record.setMode(cursor.getString(cursor.getColumnIndex("mode"))); - record.setRst_sent(cursor.getString(cursor.getColumnIndex("rst_sent"))); - record.setRst_rcvd(cursor.getString(cursor.getColumnIndex("rst_rcvd"))); - record.setTime_on(String.format("%s-%s" - , cursor.getString(cursor.getColumnIndex("qso_date")) - , cursor.getString(cursor.getColumnIndex("time_on")))); - - record.setTime_off(String.format("%s-%s" - , cursor.getString(cursor.getColumnIndex("qso_date_off")) - , cursor.getString(cursor.getColumnIndex("time_off")))); - record.setBand(cursor.getString(cursor.getColumnIndex("band")));//波长 - record.setFreq(cursor.getString(cursor.getColumnIndex("freq")));//频率 - record.setStation_callsign(cursor.getString(cursor.getColumnIndex("station_callsign"))); - record.setMy_gridsquare(cursor.getString(cursor.getColumnIndex("my_gridsquare"))); - record.setComment(cursor.getString(cursor.getColumnIndex("comment"))); - records.add(record); - } - cursor.close(); - if (onQueryQSLRecordCallsign != null) { - onQueryQSLRecordCallsign.afterQuery(records); - } - return null; - } - } - - /** - * 通过呼号查询联通成功的呼号 - */ - static class GetQLSCallsignByCallsign extends AsyncTask { - SQLiteDatabase db; - String callsign; - int filter; - OnQueryQSLCallsign onQueryQSLCallsign; - int offset; - boolean showAll; - - public GetQLSCallsignByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLCallsign onQueryQSLCallsign) { - this.showAll=showAll; - this.offset=offset; - this.db = db; - this.callsign = callsign; - this.filter = queryFilter; - this.onQueryQSLCallsign = onQueryQSLCallsign; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - String filterStr; - switch (filter) { - case 1: - filterStr = "and((q.isQSL =1)or(q.isLotW_QSL =1))\n"; - break; - case 2: - filterStr = "and((q.isQSL =0)and(q.isLotW_QSL =0))\n"; - break; - default: - filterStr = ""; - } - String limitStr=""; - if (!showAll){ - limitStr="limit 100 offset "+offset; - } - String querySQL = "select q.[call] as callsign ,q.gridsquare as grid" + - ",q.band||\"(\"||q.freq||\" MHz)\" as band \n" + - ",q.qso_date as last_time ,q.mode ,q.isQSL,q.isLotW_QSL\n" + - "from QSLTable q inner join QSLTable q2 ON q.id =q2.id \n" + - "where (q.[call] like ?)\n" + - filterStr + - "group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.band\n" + - ",q.mode,q.isQSL,q.isLotW_QSL\n" + - "HAVING q.qso_date =MAX(q2.qso_date) \n" + - "order by q.qso_date desc\n"+ - limitStr; - - - Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"}); - ArrayList records = new ArrayList<>(); - while (cursor.moveToNext()) { - QSLCallsignRecord record = new QSLCallsignRecord(); - record.setCallsign(cursor.getString(cursor.getColumnIndex("callsign"))); - record.isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; - record.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; - record.setLastTime(cursor.getString(cursor.getColumnIndex("last_time"))); - record.setMode(cursor.getString(cursor.getColumnIndex("mode"))); - record.setGrid(cursor.getString(cursor.getColumnIndex("grid"))); - record.setBand(cursor.getString(cursor.getColumnIndex("band"))); - records.add(record); - } - cursor.close(); - if (onQueryQSLCallsign != null) { - onQueryQSLCallsign.afterQuery(records); - } - return null; - } - } - - - /** - * 获取通联过的呼号 - */ - @SuppressLint("DefaultLocale") - static class GetAllQSLCallsign { - public static void get(SQLiteDatabase db) { - - //String querySQL = "select distinct [call] from QSLTable where freq=?"; - //改为以波长BAND取通联过的呼号 - String querySQL = "select distinct [call] from QSLTable where band=?"; - Cursor cursor = db.rawQuery(querySQL, new String[]{ - BaseRigOperation.getMeterFromFreq(GeneralVariables.band)}); - ArrayList callsigns = new ArrayList<>(); - while (cursor.moveToNext()) { - @SuppressLint("Range") - String s = cursor.getString(cursor.getColumnIndex("call")); - if (s != null) { - callsigns.add(s); - } - } - cursor.close(); - GeneralVariables.QSL_Callsign_list = callsigns; - - querySQL = "select distinct [call] from QSLTable where band<>?"; - cursor = db.rawQuery(querySQL, new String[]{ - BaseRigOperation.getMeterFromFreq(GeneralVariables.band)}); - - ArrayList other_callsigns = new ArrayList<>(); - while (cursor.moveToNext()) { - @SuppressLint("Range") - String s = cursor.getString(cursor.getColumnIndex("call")); - if (s != null) { - other_callsigns.add(s); - } - } - cursor.close(); - GeneralVariables.QSL_Callsign_list_other_band = other_callsigns; - } - - } - - - /** - * 通过ID删除通联呼号 - */ - static class DeleteQSLCallsignByID extends AsyncTask { - private final SQLiteDatabase db; - private final int id; - - public DeleteQSLCallsignByID(SQLiteDatabase db, int id) { - this.db = db; - this.id = id; - } - - - @Override - protected Void doInBackground(Void... voids) { - db.execSQL("delete from QslCallsigns where id=?", new Object[]{id}); - return null; - } - } - - - /** - * 通过ID删除日志 - */ - static class DeleteQSLByID extends AsyncTask { - private final SQLiteDatabase db; - private final int id; - - public DeleteQSLByID(SQLiteDatabase db, int id) { - this.db = db; - this.id = id; - } - - @Override - protected Void doInBackground(Void... voids) { - db.execSQL("delete from QSLTable where id=?", new Object[]{id}); - return null; - } - } - - static class SetQSLCallsignIsQSL extends AsyncTask { - private final SQLiteDatabase db; - private final int id; - private final boolean isQSL; - - public SetQSLCallsignIsQSL(SQLiteDatabase db, int id, boolean isQSL) { - this.db = db; - this.id = id; - this.isQSL = isQSL; - } - - @Override - protected Void doInBackground(Void... voids) { - db.execSQL("UPDATE QslCallsigns SET isQSL=? where id=?", new Object[]{isQSL ? "1" : "0", id}); - return null; - } - } - - /** - * 设置日志手工确认 - */ - static class SetQSLTableIsQSL extends AsyncTask { - private final SQLiteDatabase db; - private final int id; - private final boolean isQSL; - - public SetQSLTableIsQSL(SQLiteDatabase db, int id, boolean isQSL) { - this.db = db; - this.id = id; - this.isQSL = isQSL; - } - - @Override - protected Void doInBackground(Void... voids) { - db.execSQL("UPDATE QSLTable SET isQSL=? where id=?", new Object[]{isQSL ? "1" : "0", id}); - return null; - } - } - - - /** - * 查询全部通联成功的呼号,以通联时的频段为条件 - */ - static class LoadAllQSLCallsigns extends AsyncTask { - private final SQLiteDatabase db; - - public LoadAllQSLCallsigns(SQLiteDatabase db) { - this.db = db; - } - - @Override - protected Void doInBackground(Void... voids) { - GetAllQSLCallsign.get(db);//获取通联过的呼号 - return null; - } - } - - static class GetAllConfigParameter extends AsyncTask { - private final SQLiteDatabase db; - private OnAfterQueryConfig onAfterQueryConfig; - - public GetAllConfigParameter(SQLiteDatabase db, OnAfterQueryConfig onAfterQueryConfig) { - this.db = db; - this.onAfterQueryConfig = onAfterQueryConfig; - } - - @SuppressLint("Range") - private String getConfigByKey(String KeyName) { - String querySQL = "select keyName,Value from config where KeyName =?"; - Cursor cursor = db.rawQuery(querySQL, new String[]{KeyName}); - String result = ""; - if (cursor.moveToFirst()) { - result = cursor.getString(cursor.getColumnIndex("Value")); - } - cursor.close(); - return result; - } - - @SuppressLint("Range") - @Override - protected Void doInBackground(Void... voids) { - - String querySQL = "select keyName,Value from config "; - Cursor cursor = db.rawQuery(querySQL, null); - while (cursor.moveToNext()) { - @SuppressLint("Range") - //String result = ""; - String result = cursor.getString(cursor.getColumnIndex("Value")); - String name = cursor.getString(cursor.getColumnIndex("KeyName")); - - if (name.equalsIgnoreCase("grid")) { - GeneralVariables.setMyMaidenheadGrid(result); - } - if (name.equalsIgnoreCase("callsign")) { - GeneralVariables.myCallsign = result; - String callsign = GeneralVariables.myCallsign; - if (callsign.length() > 0) { - Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); - } - } - if (name.equalsIgnoreCase("toModifier")) { - GeneralVariables.toModifier = result; - } - if (name.equalsIgnoreCase("freq")) { - float freq = 1000; - try { - freq = Float.parseFloat(result); - } catch (Exception e) { - Log.e(TAG, "doInBackground: " + e.getMessage()); - } - //GeneralVariables.setBaseFrequency(result.equals("") ? 1000 : Float.parseFloat(result)); - GeneralVariables.setBaseFrequency(freq); - } - if (name.equalsIgnoreCase("synFreq")) { - GeneralVariables.synFrequency = !(result.equals("") || result.equals("0")); - } - if (name.equalsIgnoreCase("transDelay")) { - if (result.matches("^\\d{1,4}$")) {//正则表达式,1-4位长度的数字 - GeneralVariables.transmitDelay = Integer.parseInt(result); - } else { - GeneralVariables.transmitDelay = FT8Common.FT8_TRANSMIT_DELAY; - } - } - - if (name.equalsIgnoreCase("civ")) { - GeneralVariables.civAddress = result.equals("") ? 0xa4 : Integer.parseInt(result, 16); - } - if (name.equalsIgnoreCase("baudRate")) { - GeneralVariables.baudRate = result.equals("") ? 19200 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("bandFreq")) { - GeneralVariables.band = result.equals("") ? 14074000 : Long.parseLong(result); - GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band); - } - if (name.equalsIgnoreCase("ctrMode")) { - GeneralVariables.controlMode = result.equals("") ? ControlMode.VOX : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("model")) {//电台型号 - GeneralVariables.modelNo = result.equals("") ? 0 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("instruction")) {//指令集 - GeneralVariables.instructionSet = result.equals("") ? 0 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("launchSupervision")) {//发射监管 - GeneralVariables.launchSupervision = result.equals("") ? - GeneralVariables.DEFAULT_LAUNCH_SUPERVISION : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("noReplyLimit")) {// - GeneralVariables.noReplyLimit = result.equals("") ? 0 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("autoFollowCQ")) {//自动关注CQ - GeneralVariables.autoFollowCQ = (result.equals("") || result.equals("1")); - } - if (name.equalsIgnoreCase("autoCallFollow")) {//自动呼叫关注 - GeneralVariables.autoCallFollow = (result.equals("") || result.equals("1")); - } - if (name.equalsIgnoreCase("pttDelay")) {//ptt延时设置 - GeneralVariables.pttDelay = result.equals("") ? 100 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("icomIp")) {//IcomIp地址 - GeneralVariables.icomIp = result.equals("") ? "255.255.255.255" : result; - } - if (name.equalsIgnoreCase("icomPort")) {//Icom端口 - GeneralVariables.icomUdpPort = result.equals("") ? 50001 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("icomUserName")) {//Icom用户名 - GeneralVariables.icomUserName = result.equals("") ? "ic705" : result; - } - if (name.equalsIgnoreCase("icomPassword")) {//Icom密码 - GeneralVariables.icomPassword = result; - } - if (name.equalsIgnoreCase("volumeValue")) {//输出音量大小 - GeneralVariables.volumePercent = result.equals("") ? 1.0f : Float.parseFloat(result) / 100f; - } - if (name.equalsIgnoreCase("excludedCallsigns")) {//排除的呼号 - GeneralVariables.addExcludedCallsigns(result); - } - if (name.equalsIgnoreCase("flexMaxRfPower")) {//指令集 - GeneralVariables.flexMaxRfPower = result.equals("") ? 10 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("flexMaxTunePower")) {//指令集 - GeneralVariables.flexMaxTunePower = result.equals("") ? 10 : Integer.parseInt(result); - } - if (name.equalsIgnoreCase("saveSWL")) {//保存解码信息 - GeneralVariables.saveSWLMessage = result.equals("1"); - } - if (name.equalsIgnoreCase("saveSWLQSO")) {//保存解码信息 - GeneralVariables.saveSWL_QSO = result.equals("1"); - } - if (name.equalsIgnoreCase("audioBits")) {//输出音频是否32位浮点 - GeneralVariables.audioOutput32Bit = result.equals("1"); - } - if (name.equalsIgnoreCase("audioRate")) {//输出音频是否32位浮点 - GeneralVariables.audioSampleRate =Integer.parseInt( result); - } - if (name.equalsIgnoreCase("deepMode")) {//是不是深度解码模式 - GeneralVariables.deepDecodeMode =result.equals("1"); - } - } - - cursor.close(); - - GetAllQSLCallsign.get(db);//获取通联过的呼号 - - if (onAfterQueryConfig != null) { - onAfterQueryConfig.doOnAfterQueryConfig(null, null); - } - - return null; - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java deleted file mode 100644 index da3774d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.bg7yoz.ft8cn.database; -/** - * 与DXCC有关的3个表,dxccList、dxcc_grid、dxcc_prefix,用于保存各DXCC分区所在的国家、经纬度、网格等信息 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.database.sqlite.SQLiteDatabase; - -import java.util.ArrayList; - -public class DxccObject { - int id; - int dxcc; - String cc; - String ccc; - String name; - String continent; - String ituZone; - String cqZone; - int timeZone; - int cCode; - String aName; - String pp; - double lat; - double lon; - ArrayList grid=new ArrayList<>(); - ArrayList prefix=new ArrayList<>(); - - public void insertToDb(SQLiteDatabase db){ - String insertSQL="INSERT INTO dxccList (id,dxcc,cc,ccc,name,continent" + - ",ituzone,cqzone,timezone,ccode,aname,pp,lat,lon)"+ - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; - - db.execSQL(insertSQL,new Object[]{this.id,this.dxcc,this.cc,this.ccc,this.name,this.continent - ,this.ituZone,this.cqZone,this.timeZone,this.cCode,this.aName,this.pp,this.lat,this.lon}); - - String insertGrid="INSERT INTO dxcc_grid(dxcc,grid)VALUES(?,?)"; - for (int i = 0; i < this.grid.size(); i++) { - db.execSQL(insertGrid,new Object[]{this.dxcc,this.grid.get(i)}); - } - - String insert_Prefix="INSERT INTO dxcc_prefix(dxcc,prefix)VALUES(?,?)"; - for (int i = 0; i callsigns); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java deleted file mode 100644 index 7f4eb6e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.database; - -/** - * 保存配置信息的回调 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnAfterWriteConfig { - void doOnAfterWriteConfig(boolean writeDone); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java deleted file mode 100644 index c779300..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.database; - -/** - * 查询呼号的回调 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnGetCallsign { - void doOnAfterGetCallSign(boolean exists); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java deleted file mode 100644 index f2fc067..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.bg7yoz.ft8cn.database; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.AssetManager; -import android.util.Log; - -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -/** - * 用于读取可用的载波波段列表,文件保存在assets/bands.txt中 - * @author BGY70Z - * @date 2023-03-20 - */ - -public class OperationBand { - private static final String TAG="OperationBand"; - private final Context context; - private static OperationBand operationBand = null; - - public static long getDefaultBand() { - return 14074000; - } - - public static String getDefaultWaveLength() { - return "20m"; - } - - public static ArrayList bandList = new ArrayList<>(); - public OperationBand(Context context) { - this.context = context; - //把波段数据导入到内存 - getBandsFromFile(); - } - - public static OperationBand getInstance(Context context) { - if (operationBand == null) { - operationBand=new OperationBand(context); - return operationBand; - } else { - return operationBand; - } - } - - /** - * 获取操作波段的数据,以列表的索引值查找,如果没有返回默认值14.074,20m - * @param index 索引 - * @return - */ - public Band getBandByIndex(int index){ - if (index==-1||index>=bandList.size()){ - return new Band(getDefaultBand(),getDefaultWaveLength()); - }else { - return bandList.get(index); - } - } - - /** - * 检查频率是不是在频率列表中,如果不在,把这个频率加到频段中 - * @param freq - * @return - */ - public static int getIndexByFreq(long freq){ - int result=-1; - for (int i = 0; i < bandList.size(); i++) { - if (bandList.get(i).band==freq){ - result=i; - break; - } - } - if (result==-1){ - bandList.add(new Band(freq, BaseRigOperation.getMeterFromFreq(freq))); - result=bandList.size()-1; - } - return result; - } - /** - * 从bands.txt文件中读出FT8信号列表。 - */ - public void getBandsFromFile(){ - AssetManager assetManager = context.getAssets(); - try { - bandList.clear(); - InputStream inputStream= assetManager.open("bands.txt"); - String[] st=getLinesFromInputStream(inputStream,"\n"); - for (int i = 0; i =bandList.size()){ - return bandList.get(0).getBandInfo(); - }else { - return bandList.get(index).getBandInfo(); - } - } - - /** - * 从InputStream中读出字符串 - * @param inputStream 输入流 - * @param deLimited 每行数据的分隔符。 - * @return String 返回字符串,如果失败,返回null - */ - public static String[] getLinesFromInputStream(InputStream inputStream, String deLimited) { - try { - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - return (new String(bytes)).split(deLimited); - }catch (IOException e){ - return null; - } - } - public static long getBandFreq(int index){ - if (index>bandList.size()){ - return 14074000; - } - return bandList.get(index).band; - } - - public static class Band { - public long band; - public String waveLength; - public boolean marked=false; - - public Band(long band, String waveLength) { - this.band = band; - this.waveLength = waveLength; - } - - public Band(String s) { - String[] info=s.split(":"); - marked= (info[0].equals("*")); - band=Long.parseLong(info[1]); - waveLength=info[info.length-1]; - } - @SuppressLint("DefaultLocale") - public String getBandInfo(){ - return String.format("%s %.3f MHz (%s)" - ,marked?"*":" " - ,(float)(band/1000000f) - ,waveLength); - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java deleted file mode 100644 index 019b0fa..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.bg7yoz.ft8cn.database; -/** - * 各电台信号的列表。文件在rigaddress.txt中 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.content.Context; -import android.content.res.AssetManager; -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -public class RigNameList { - private static final String TAG="RigNameList"; - private Context context; - private static RigNameList rigNameList = null; - - - public ArrayList rigList = new ArrayList<>(); - - public RigNameList(Context context) { - this.context = context; - //电台数据导入到内存 - getRigNamesFromFile(); - } - - public static RigNameList getInstance(Context context) { - if (rigNameList == null) { - return new RigNameList(context); - } else { - return rigNameList; - } - } - - /** - * 获取各电台参数数据,以列表的索引值查找,如果没有返回默认值以空 - * @param index 索引 - * @return 电台参数 - */ - public RigName getRigNameByIndex(int index){ - if (index==-1||index>=rigList.size()){ - return new RigName("",0xA4,19200,0); - }else { - return rigList.get(index); - } - } - - /** - * rigaddress.txt文件中读出各电台参数列表。 - */ - public void getRigNamesFromFile(){ - AssetManager assetManager = context.getAssets(); - try { - InputStream inputStream= assetManager.open("rigaddress.txt"); - String[] st=getLinesFromInputStream(inputStream,"\n"); - rigList.add(new RigName("",0xA4,19200,0)); - for (int i = 0; i { - private static final String TAG = "FlexMeters"; - public int sMeterId = -1; - public int tempCId = -1; - public int swrId = -1; - public int pwrId = -1; - public int alcId = -1; - - public FlexMeterInfos(String content) { - setMeterInfos(content); - } - - /** - * 根据电台回复的消息,获取每个METER的ID与meter定义的映射表 - * - * @param content 消息 - */ - public synchronized void setMeterInfos(String content) { - String[] temp; - if (content.length() == 0) return; - temp = content.substring(content.indexOf("meter ") + "meter ".length()).split("#"); - for (int i = 0; i < temp.length; i++) { - String[] val = temp[i].split("="); - if (val.length == 2) { - if (val[0].contains(".")) { - int index = Integer.parseInt(val[0].substring(0, val[0].indexOf("."))); - FlexMeterInfo meterInfo; - - meterInfo = this.get(index); - if (meterInfo == null) { - meterInfo = new FlexMeterInfo(); - this.put(index, meterInfo); - } - - - if (val[0].toLowerCase().contains(".src")) { - - meterInfo.src = val[1]; - } - if (val[0].toLowerCase().contains(".num")) { - meterInfo.num = val[1]; - } - if (val[0].toLowerCase().contains(".nam")) { - meterInfo.nam = val[1]; - //为了方便MeterList快速查询 - if (val[1].toUpperCase().contains("LEVEL")) { - sMeterId = index; - } else if (val[1].toUpperCase().contains("PATEMP")) { - tempCId = index; - } else if (val[1].toUpperCase().contains("SWR")) { - swrId = index; - } else if (val[1].toUpperCase().contains("FWDPWR")) { - pwrId = index; - } else if (val[1].toUpperCase().contains("ALC")) { - alcId = index; - } - - } - if (val[0].toLowerCase().contains(".low")) { - meterInfo.low = Float.parseFloat(val[1]); - } - if (val[0].toLowerCase().contains(".hi")) { - meterInfo.hi = Float.parseFloat(val[1]); - } - if (val[0].toLowerCase().contains(".desc")) { - meterInfo.desc = val[1]; - } - if (val[0].toLowerCase().contains(".unit")) { - String s = val[1].toUpperCase(); - if (s.contains("DB")) { - meterInfo.unit = FlexMeterType.dBm; - } else if (s.contains("SWR")) { - meterInfo.unit = FlexMeterType.swr; - } else if (s.contains("DEG")) { - meterInfo.unit = FlexMeterType.Temperature; - } else if (s.contains("VOLT")) { - meterInfo.unit = FlexMeterType.volt; - } else { - meterInfo.unit = FlexMeterType.other; - } - - } - if (val[0].toLowerCase().contains(".fps")) { - meterInfo.fps = val[1]; - } - if (val[0].toLowerCase().contains(".peak")) { - meterInfo.peak = val[1]; - } - } - } - } -// sMeterId=getMeterId("LEVEL"); -// tempCId=getMeterId("PATEMP"); -// swrId=getMeterId("SWR"); -// pwrId=getMeterId("FWDPWR"); -// alcId=getMeterId("ALC"); - } - - /** - * 查Meter得id,没有返回-1 - * - * @return id - */ - private int getMeterId(String s) { - for (int key : this.keySet()) { - if (get(key).nam.equalsIgnoreCase(s)) { - return key; - } - } - return -1; - } - - - public static class FlexMeterInfo { - public String src; - public String num; - public String nam; - public float low; - public float hi; - public String desc; - public FlexMeterType unit = FlexMeterType.other; - public String fps; - public String peak; - - @Override - public String toString() { - return "{" + - "src='" + src + '\'' + - ", num='" + num + '\'' + - ", nam='" + nam + '\'' + - ", low='" + low + '\'' + - ", hi='" + hi + '\'' + - ", desc='" + desc + '\'' + - ", unit='" + unit + '\'' + - ", fps='" + fps + '\'' + - ", peak='" + peak + '\'' + - '}'; - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java deleted file mode 100644 index 8e7cf3c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.bg7yoz.ft8cn.flex; -/** - * Meter的哈希表。Meter是2个32位的数,ID+VALUE。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; - -import java.util.HashMap; - -public class FlexMeterList extends HashMap { - public float sMeterVal=-150;//-150~10 - public float tempCVal=0;//0~100 - public float alcVal=-150;//-150~20 - public float swrVal=1;//1~999 - public float pwrVal=0;//0~100W - public synchronized void setMeters(byte[] data, FlexMeterInfos infos) { - for (int i = 0; i < data.length / 4; i++) { - int val = readShortData(data, i * 4); - FlexMeter meter = get(val); - if (meter == null) { - if (infos.get(val) == null) continue; - meter = new FlexMeter(); - meter.name = infos.get(val).nam; - meter.desc = infos.get(val).desc; - meter.type = infos.get(val).unit; - meter.id = val; - } - switch (meter.type) { - case dBm: - case swr: - meter.value = readShortData(data, i * 4 + 2) / 128f; - if (meter.name.contains("PWR")){//把dBm转换成功率值 - meter.value=(float) Math.pow(10,meter.value/10f)/1000f; - } - //节省资源,提前赋值 - if (meter.id==infos.sMeterId) sMeterVal=meter.value; - if (meter.id==infos.swrId) swrVal=meter.value; - if (meter.id==infos.pwrId) pwrVal=meter.value; - if (meter.id==infos.alcId) alcVal=meter.value; - break; - case volt: - meter.value = readShortData(data, i * 4 + 2) / 256f; - break; - case Temperature: - meter.value = readShortData(data, i * 4 + 2) / 64f; - //节省资源,提前赋值 - if (meter.id==infos.tempCId) tempCVal=meter.value; - break; - case other: - default: - meter.value = readShortData(data, i * 4 + 2); - } - - put(val, meter); - } - - } - - public synchronized String getMeters(){ - StringBuilder temp=new StringBuilder(); - int i=0; - for (int key:this.keySet()) { - i++; - temp.append(String.format("%-35s",get(key).toString())); - if (i%2==0){ - temp.append("\n"); - } - } - return temp.toString(); - } - - - - /** - * 把字节转换成short,不做小端转换!! - * - * @param data 字节数据 - * @return short - */ - public static short readShortData(byte[] data, int start) { - if (data.length - start < 2) return 0; - return (short) ((short) data[start + 1] & 0xff - | ((short) data[start] & 0xff) << 8); - } - - public static float readShortFloat(byte[] data, int start) { - if (data.length - start < 2) return 0.0f; - int accum = 0; - accum = accum | (data[start] & 0xff) << 0; - accum = accum | (data[start + 1] & 0xff) << 8; - return Float.intBitsToFloat(accum); - } - - - public static class FlexMeter { - public int id; - public float value; - public String name; - public String desc; - public FlexMeterType type=FlexMeterType.other; - - @SuppressLint("DefaultLocale") - @Override - public String toString() { - return String.format("%02d.%s : %.1f",id,name,value); - - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java deleted file mode 100644 index 164ecad..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.flex; - -/** - * meter的常用类型 - * @author BGY70Z - * @date 2023-03-20 - */ -public enum FlexMeterType { - dBm,swr,Temperature,volt,other -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java deleted file mode 100644 index 630b050..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java +++ /dev/null @@ -1,1412 +0,0 @@ -package com.bg7yoz.ft8cn.flex; -/** - * Flex的操作,命令使用TCP,数据流使用UDP。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.media.AudioAttributes; -import android.media.AudioFormat; -import android.media.AudioTrack; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.HashSet; - - -public class FlexRadio { - - public enum FlexMode {LSB, USB, AM, CW, DIGL, DIGU, SAM, FM, NFM, DFM, RTTY, RAW, ARQ, UNKNOW} - - public enum AntMode {ANT1, ANT2, RX_A, XVTA, UNKNOW} - - - private static final String TAG = "FlexRadio"; - public static int streamPort = 7051; - private int flexStreamPort = 4993; - public boolean isPttOn = false; - public long streamTxId = 0x084000000; - - public static int getStreamPort() {//获取用于流传输的UDP端口,防止重复,采用自增方式 - return ++streamPort; - } - - //private int streamPort;//当前用于流传输的UDP端口,这个是本实例的端口 - - - /********************* - * 电台的基本信息,从discovery协议中获取 - *************************/ - private String discovery_protocol_version;//=3.0.0.2 - private String model;//=FLEX-6400 - private String serial;//=1418-6579-6400-0461 - private String version;//=3.3.32.8203 - private String nickname;//=FlexRADIO - private String callsign;//=FlexRADIO - private String ip = "";//=192.168.3.86 - private int port = 4992;//=4992//用于控制电台的TCP端口 - private String status;//=Available - private String inUse_ip;//=192.168.3.5 - private String inUse_host;//=DESKTOP-RR564NK.local - private String max_licensed_version;//=v3 - private String radio_license_id;//=00-1C-2D-05-04-70 - private String requires_additional_license;//=0 - private String fpc_mac;//= - private int wan_connected;//=1 - private int licensed_clients;//=2 - private int available_clients;//=1 - private int max_panadapters;//=2 - private int available_panadapters;//=1 - private int max_slices;//=2 - private int available_slices;//=1 - private String gui_client_ips;//=192.168.3.5 - private String gui_client_hosts;//=DESKTOP-RR564NK.local - private String gui_client_programs;//=SmartSDR-Win - private String gui_client_stations;//=DESKTOP-RR564NK - private String gui_client_handles;//=0x19EAFA02 - - private long lastSeen;//最后一次消息的时间 - private boolean isAvailable = true;//电台是不是有效 - - - private int commandSeq = 1;//指令的序列 - private FlexCommand flexCommand; - private int handle = 0; - private String commandStr; - - - private final StringBuilder buffer = new StringBuilder();//指令的缓存 - private final RadioTcpClient tcpClient = new RadioTcpClient(); - private RadioUdpClient streamClient; - - private boolean allFlexRadioStatusEvent = false; - private String clientID = ""; - private long daxAudioStreamId = 0; - private long daxTxAudioStreamId = 0; - private long panadapterStreamId = 0; - private final HashSet streamIdSet = new HashSet<>(); - - //************************事件处理接口******************************* - private OnReceiveDataListener onReceiveDataListener;//当前接收到的数据事件 - private OnTcpConnectStatus onTcpConnectStatus;//当TCP连接状态变化的事件 - private OnReceiveStreamData onReceiveStreamData;//当接收到流数据后的处理事件 - private OnCommandListener onCommandListener;//触发命令事件 - private OnMessageListener onMessageListener;//触发消息事件 - private OnStatusListener onStatusListener;//触发状态事件 - //***************************************************************** - private AudioTrack audioTrack = null; - - public FlexRadio() { - updateLastSeen(); - } - - public FlexRadio(String discoverStr) { - update(discoverStr); - updateLastSeen(); - } - - public void updateLastSeen() { - this.lastSeen = System.currentTimeMillis(); - } - - /** - * 到参数列表中找指定的字符类型参数 - * - * @param parameters 参数列表 - * @param prefix 参数名前缀 - * @return 参数 - */ - private String getParameterStr(String[] parameters, String prefix) { - for (int i = 0; i < parameters.length; i++) { - if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { - return parameters[i].substring(prefix.length() + 1); - } - } - //如果没找到,返回空字符串 - return ""; - - } - - /** - * 到参数列表中找指定的int类型参数 - * - * @param parameters 参数列表 - * @param prefix 参数名前缀 - * @return 参数 - */ - private int getParameterInt(String[] parameters, String prefix) { - for (int i = 0; i < parameters.length; i++) { - if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { - try { - return Integer.parseInt(parameters[i].substring(prefix.length() + 1)); - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "getParameterInt exception: " + e.getMessage()); - return 0; - } - } - } - //如果没找到,返回0 - return 0; - - } - - /** - * 从discovery协议中更新参数 - * - * @param discoverStr 参数 - */ - public void update(String discoverStr) { - String[] paras = discoverStr.split(" "); - discovery_protocol_version = getParameterStr(paras, "discovery_protocol_version"); - model = getParameterStr(paras, "model"); - serial = getParameterStr(paras, "serial"); - version = getParameterStr(paras, "version"); - nickname = getParameterStr(paras, "nickname"); - callsign = getParameterStr(paras, "callsign"); - ip = getParameterStr(paras, "ip"); - port = getParameterInt(paras, "port"); - status = getParameterStr(paras, "status"); - inUse_ip = getParameterStr(paras, "inUse_ip"); - inUse_host = getParameterStr(paras, "inUse_host"); - max_licensed_version = getParameterStr(paras, "max_licensed_version"); - radio_license_id = getParameterStr(paras, "radio_license_id"); - requires_additional_license = getParameterStr(paras, "requires_additional_license"); - fpc_mac = getParameterStr(paras, "fpc_mac"); - wan_connected = getParameterInt(paras, "wan_connected"); - licensed_clients = getParameterInt(paras, "licensed_clients"); - available_clients = getParameterInt(paras, "available_clients"); - max_panadapters = getParameterInt(paras, "max_panadapters"); - available_panadapters = getParameterInt(paras, "available_panadapters"); - max_slices = getParameterInt(paras, "max_slices"); - available_slices = getParameterInt(paras, "available_slices"); - gui_client_ips = getParameterStr(paras, "gui_client_ips"); - gui_client_hosts = getParameterStr(paras, "gui_client_hosts"); - gui_client_programs = getParameterStr(paras, "gui_client_programs"); - gui_client_stations = getParameterStr(paras, "gui_client_stations"); - gui_client_handles = getParameterStr(paras, "gui_client_handles"); - } - - /** - * 检查这个实例是否是同一个电台 - * - * @param serialNum 电台序列号 - * @return 是/否 - */ - public boolean isEqual(String serialNum) { - return this.serial.equalsIgnoreCase(serialNum); - } - - - /** - * 连接到控制电台 - */ - public void connect() { - this.connect(this.ip, this.port); - } - - /** - * 连接控制到电台,TCP - * - * @param ip 地址 - * @param port 端口 - */ - public void connect(String ip, int port) { - if (tcpClient.isConnect()) { - tcpClient.disconnect(); - } - //Tcp连接触发的事件 - tcpClient.setOnDataReceiveListener(new RadioTcpClient.OnDataReceiveListener() { - @Override - public void onConnectSuccess() { - if (onTcpConnectStatus != null) { - onTcpConnectStatus.onConnectSuccess(tcpClient); - } - } - - @Override - public void onConnectFail() { - if (onTcpConnectStatus != null) { - onTcpConnectStatus.onConnectFail(tcpClient); - } - } - - @Override - public void onDataReceive(byte[] buffer) { - if (onReceiveDataListener != null) { - onReceiveDataListener.onDataReceive(buffer); - } - onReceiveData(buffer); - } - }); - clearBufferData();//清除一下缓存的指令数据 - tcpClient.connect(ip, port);//连接TCP - - //openStreamPort();//打开接收数据流的端口 - } - - /** - * 当接收到音频数据时的处理 - * - * @param data 音频数据 - */ - private void doReceiveAudio(byte[] data) { - if (onReceiveStreamData != null) { - onReceiveStreamData.onReceiveAudio(data); - } - if (audioTrack != null) {//如果音频播放已经打开,就写音频流数据 - float[] sound = getFloatFromBytes(data); - audioTrack.write(sound, 0, sound.length, AudioTrack.WRITE_NON_BLOCKING); - } - } - - /** - * 当接收到IQ数据时的处理 - * - * @param data 数据 - */ - private void doReceiveIQ(byte[] data) { - if (onReceiveStreamData != null) { - onReceiveStreamData.onReceiveIQ(data); - } - } - - /** - * 当接收到FFT数据时的处理 - * - * @param vita 数据 - */ - private void doReceiveFFT(VITA vita) { - if (onReceiveStreamData != null) { - onReceiveStreamData.onReceiveFFT(vita); - } - } - - /** - * 当接收到仪表数据时的处理 - * - * @param vita 数据 - */ - private void doReceiveMeter(VITA vita) { - if (onReceiveStreamData != null) { - onReceiveStreamData.onReceiveMeter(vita); - } - } - - /** - * 当接收到未知数据时的处理 - * - * @param data 数据 - */ - private void doReceiveUnKnow(byte[] data) { - if (onReceiveStreamData != null) { - onReceiveStreamData.onReceiveUnKnow(data); - } - } - - /** - * 打开音频,流方式。当收到音频流的时候,播放数据 - */ - public void openAudio() { - AudioAttributes attributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .build(); - AudioFormat myFormat = new AudioFormat.Builder().setSampleRate(24000) - .setEncoding(AudioFormat.ENCODING_PCM_FLOAT) - .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build(); - int mySession = 0; - audioTrack = new AudioTrack(attributes, myFormat - , 24000 * 4, AudioTrack.MODE_STREAM - , mySession); - audioTrack.play(); - } - - /** - * 关闭音频 - */ - public void closeAudio() { - if (audioTrack != null) { - audioTrack.stop(); - //audioTrack.release(); - audioTrack = null; - } - } - - private synchronized void addStreamIdToSet(long streamId) { - streamIdSet.add(streamId); - } - - /** - * 打开接收数据流的端口 - */ - public void openStreamPort() { - if (streamClient != null) { - if (streamClient.isActivated()) { - try { - streamClient.setActivated(false); - } catch (Exception e) { - e.printStackTrace(); - } - - } - } - - - RadioUdpClient.OnUdpEvents onUdpEvents = new RadioUdpClient.OnUdpEvents() { - @Override - public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { - if (flexStreamPort != packet.getPort()) flexStreamPort = packet.getPort(); - - VITA vita = new VITA(data); - addStreamIdToSet(vita.streamId); - - //Log.e(TAG, String.format("OnReceiveData: stream id:0x%x,class id:0x%x",vita.streamId,vita.classId) ); - switch (vita.classId) { - case VITA.FLEX_DAX_AUDIO_CLASS_ID://音频数据 - //Log.e(TAG, String.format("FLEX_DAX_AUDIO_CLASS_ID stream id:0x%x",vita.streamId )); - doReceiveAudio(vita.payload); - break; - case VITA.FLEX_DAX_IQ_CLASS_ID://IQ数据 - doReceiveIQ(vita.payload); - break; - case VITA.FLEX_FFT_CLASS_ID://频谱数据 - doReceiveFFT(vita); - //Log.e(TAG, String.format("OnReceiveData: FFT:%d,STREAM ID:0x%x",vita.payload.length,vita.streamId)); - break; - case VITA.FLEX_METER_CLASS_ID://仪表数据 - //Log.e(TAG, String.format("FLEX_METER_CLASS_ID: stream id:0x%x",vita.streamId )); - doReceiveMeter(vita); - //Log.e(TAG, String.format("OnReceiveData: METER class id:0x%x,stream id:0x%x,length:%d\n%s" - // ,vita.classId,vita.streamId,vita.payload.length,vita.showPayload() )); - break; - default://未知类型的数据 - doReceiveUnKnow(data); - break; - } - } - }; - - //此处要确定stream的udp端口 - streamPort = getStreamPort(); - streamClient = new RadioUdpClient(streamPort); - streamClient.setOnUdpEvents(onUdpEvents); - try { - streamClient.setActivated(true); - } catch (SocketException e) { - e.printStackTrace(); - Log.d(TAG, "onCreate: " + e.getMessage()); - } - - - } - - /** - * 关闭接收数据流的端口 - */ - public synchronized void closeStreamPort() { - if (streamClient != null) { - if (streamClient.isActivated()) { - try { - streamClient.setActivated(false); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - streamClient = null; - } - - /** - * 断开与电台的连接 - */ - public synchronized void disConnect() { - if (tcpClient.isConnect()) { - tcpClient.disconnect(); - } - } - - /** - * flexRadio要把12000采样率改为24000采样率,还要把单声道改为立体声 - * @param data 音频 - */ - public void sendWaveData(float[] data) { - float[] temp = new float[data.length * 2]; - for (int i = 0; i < data.length; i++) {//转成立体声,24000采样率 - temp[i * 2] = data[i]; - temp[i * 2 + 1] = data[i]; - } - //port=4991; - //streamTxId=0x084000001; - //每5毫秒一个包?立体声,共256个float - Log.e(TAG, String.format("sendWaveData: streamid:0x%x,ip:%s,port:%d",streamTxId,ip, port) ); - new Thread(new Runnable() { - @Override - public void run() { - - VITA vita = new VITA(); - - int count = 0; - int packetCount=0; - while (count temp.length) break; - } - - byte[] send = vita.audioDataToVita(packetCount, streamTxId, voice); - packetCount++; - try { - streamClient.sendData(send, ip, port); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - if (count>temp.length) break; - //} - while (isPttOn) { - if (System.currentTimeMillis() - now >= 5) {//5毫秒一个周期,每个周期256个float。 - break; - } - } - if (!isPttOn){ - // Log.e(TAG, String.format("count:%d,temp.length:%d",count,temp.length )); - } - - } - - -// for (int i = 0; i < (temp.length / (24 * 2 * 40)); i++) {//40毫秒的数据量 -// if (!isPttOn) return; -// long now = System.currentTimeMillis() - 1;//获取当前时间 -// -// float[] voice = new float[24 * 2 * 10]; -// for (int j = 0; j < 24 * 2 *10; j++) { -// voice[j] = temp[i * 24 * 2 * 10 + j]; -// } -// //Log.e(TAG, "sendWaveData: "+floatToStr(voice) ); -// //streamTxId=0x84000001; -// byte[] send = vita.audioDataToVita(count, streamTxId, voice); -// count++; -// -// try { -// streamClient.sendData(send, ip, port); -// } catch (UnknownHostException e) { -// throw new RuntimeException(e); -// } -// -// while (isPttOn) { -// if (System.currentTimeMillis() - now >= 41) {//40毫秒一个周期,每个周期3个包,每个包64个float。 -// break; -// } -// } -// } - } - }).start(); - - - //设置发送音频包 - //streamClient.sendData(); - } - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%02x ", data[i] & 0xff)); - } - return s.toString(); - } - @SuppressLint("DefaultLocale") - public static String floatToStr(float[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%f ", data[i])); - } - return s.toString(); - } - /** - * 电台是否连接 - * - * @return 是否 - */ - public boolean isConnect() { - return tcpClient.isConnect(); - } - - public synchronized void sendData(byte[] data) { - tcpClient.sendByte(data); - } - - /** - * 制作命令,命令序号规则:后3位是命令的种类,序号除1000,是命令的真正序号 - * - * @param command 命令的种类 - * @param cmdContent 命令的具体内容 - */ - @SuppressLint("DefaultLocale") - public void sendCommand(FlexCommand command, String cmdContent) { - if (tcpClient.isConnect()) { - commandSeq++; - flexCommand = command; - commandStr = String.format("C%d%03d|%s\n", commandSeq, command.ordinal() - , cmdContent); - tcpClient.sendByte(commandStr.getBytes()); - Log.e(TAG, "sendCommand: " + commandStr); - } - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - /** - * 当接收到数据时触发的事件,此处是TCP连接得到的数据 - * - * @param data 数据 - */ - private void onReceiveData(byte[] data) { - String s = new String(data); - if (!s.contains("\n")) {//不包含换行符,说明命令行没有接受完。 - buffer.append(s); - } else {//说明已经有命令行了。可能不止一个哦。在此部分要触发OnReceiveLine - String[] commands = s.split("\n"); - if (commands.length > 0) {//把收到数据的第一行,追加到之前接收的命令数据上 - buffer.append(commands[0]); - } - - //先把缓存中的数据触发出来 - doReceiveLineEvent(buffer.toString()); - clearBufferData(); - //从第二行开始触发,最后一行不触发,最后一行要看是不是换行结尾 - for (int i = 1; i < commands.length - 1; i++) { - doReceiveLineEvent(commands[i]); - } - - if (commands.length > 1) {//当数据是多行的时候,最后一行的处理 - if (s.endsWith("\n")) {//如果是以换行结尾,或者缓冲区没满(接收完全了),就触发事件 - doReceiveLineEvent(commands[commands.length - 1]); - } else {//如果不是以换行结尾,说明指令没有接收完全 - buffer.append(commands[commands.length - 1]); - } - } - } - } - - /** - * 当接收到数据行时,触发的事件。可以触发两种事件: - * 1.行数据事件onReceiveLineListener; - * 2.命令事件onCommandListener。 - * - * @param line 数据行 - */ - private void doReceiveLineEvent(String line) { - - FlexResponse response = new FlexResponse(line); - //更新一下句柄 - switch (response.responseStyle) { - case VERSION: - this.version = response.version; - break; - case HANDLE: - this.handle = response.handle; - break; - case RESPONSE: - if (response.daxStreamId != 0) { - this.daxAudioStreamId = response.daxStreamId; - } - if (response.panadapterStreamId != 0) { - this.panadapterStreamId = response.panadapterStreamId; - } - if (response.daxTxStreamId != 0) { - this.daxTxAudioStreamId = response.daxTxStreamId; - Log.e(TAG, String.format("doReceiveLineEvent: txStreamID:0x%x", daxTxAudioStreamId)); - } - - break; - } - - if (response.responseStyle == FlexResponseStyle.RESPONSE) { - if (getCommandStyleFromResponse(response) == FlexCommand.CLIENT_GUI) { - setClientIDFromResponse(response);//设置CLIENT ID - } - } - - //是不是显示其它终端的状态信息 - if (response.responseStyle == FlexResponseStyle.STATUS) { - if (!allFlexRadioStatusEvent && (!(handle == response.handle || response.handle == 0))) { - return; - } - - } - - switch (response.responseStyle) { - case RESPONSE://当接收到的是指令的返回消息 - doCommandResponse(response);//对一些指令返回的消息要处理一下。 - break; - case STATUS://当接收到的是状态消息 - if (onStatusListener != null) { - onStatusListener.onStatus(response); - } - break; - case MESSAGE://当接收到的是消息 - if (onMessageListener != null) { - onMessageListener.onMessage(response); - break; - } - } - } - - /** - * 处理命令返回的消息,同时触发命令返回消息事件 - * - * @param response 返回消息 - */ - private void doCommandResponse(FlexResponse response) { - if (onCommandListener != null) { - onCommandListener.onResponse(response); - } - } - - - private void setClientIDFromResponse(FlexResponse response) { - if (response.responseStyle != FlexResponseStyle.RESPONSE) return; - if (getCommandStyleFromResponse(response) != FlexCommand.CLIENT_GUI) return; - if (response.content.equals("0")) {//R3001|0|0BF06C76-EB9E-47E0-B570-EAFB7D556055 - String[] temp = response.rawData.split("\\|"); - if (temp.length < 3) return; - clientID = temp[2]; - } - } - - public FlexCommand getCommandStyleFromResponse(FlexResponse response) { - if (response.responseStyle != FlexResponseStyle.RESPONSE) { - return FlexCommand.UNKNOW; - } - //Log.e(TAG, "getCommandStyleFromResponse: "+response.rawData ); - - try { - return FlexCommand.values()[Integer.parseInt(response.head.substring(response.head.length() - 3))]; - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "getCommandStyleFromResponse exception: " + e.getMessage()); - } - return FlexCommand.UNKNOW; - } - - /** - * 检查是不是 刚刚 离线,离线条件:5秒内没有收到电台的广播数据包 - * - * @return 是否 - */ - public boolean isInvalidNow() { - if (isAvailable) {//如果标记在线,而大于5秒的时间没有收到数据包,就视为刚刚离线。 - isAvailable = System.currentTimeMillis() - lastSeen < 1000 * 5;//小于5秒,就视为在线 - return !isAvailable; - } else {//如果已经标记不在线了,就不是刚刚离弦的。 - return false; - } - } - - @NonNull - @Override - public String toString() { - return String.format("FlexRadio{version='%s', handle=%X}", version, handle); - } - - //**************封装FlexRadio各种指令*开始*********************** - public synchronized void commandClientDisconnect() { - sendCommand(FlexCommand.CLIENT_DISCONNECT, "client disconnect"); - } - - public synchronized void commandClientGui() { - sendCommand(FlexCommand.CLIENT_GUI, "client gui"); - } - - public synchronized void commandClientSetEnforceNetWorkGui() { - sendCommand(FlexCommand.CLIENT_SET_ENFORCE_NETWORK - , "client set enforce_network_mtu=1 network_mtu=1450"); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceRemove(int sliceOder) { - sendCommand(FlexCommand.SLICE_REMOVE, String.format("slice r %d", sliceOder)); - } - - public synchronized void commandSliceList() { - sendCommand(FlexCommand.SLICE_LIST, "slice list"); - } - - public synchronized void commandSliceCreate() { - sendCommand(FlexCommand.SLICE_CREATE_FREQ, "slice create"); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceTune(int sliceOder, String freq) { - sendCommand(FlexCommand.SLICE_TUNE, String.format("slice t %d %s", sliceOder, freq)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetRxAnt(int sliceOder, AntMode antMode) { - sendCommand(FlexCommand.SLICE_SET_RX_ANT, String.format("slice s %d rxant=%s", sliceOder, antMode.toString())); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetTxAnt(int sliceOder, AntMode antMode) { - sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d txant=%s", sliceOder, antMode.toString())); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetMode(int sliceOder, FlexMode mode) { - sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d mode=%s", sliceOder, mode.toString())); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetNR(int sliceOder, boolean on) { - sendCommand(FlexCommand.SLICE_SET_NR, String.format("slice s %d nr=%s", sliceOder, on ? "on" : "off")); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceGetError(int sliceOder) { - sendCommand(FlexCommand.SLICE_GET_ERROR, String.format("slice get_error %d", sliceOder)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetNB(int sliceOder, boolean on) { - sendCommand(FlexCommand.SLICE_SET_NB, String.format("slice s %d nb=%s", sliceOder, on ? "on" : "off")); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSetDaxAudio(int channel, int sliceOder, boolean txEnable) { - sendCommand(FlexCommand.DAX_AUDIO, String.format("dax audio set %d slice=%d tx=%s", channel, sliceOder, txEnable ? "1" : "0")); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSetDaxIQ(int channel, int panadapter, int rate) { - sendCommand(FlexCommand.DAX_IQ, String.format("dax iq set %d pan=%d rat=%d", channel, panadapter, rate)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandUdpPort() { - sendCommand(FlexCommand.CLIENT_UDPPORT, String.format("client udpport %d", streamPort)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandStreamCreateDaxRx(int channel) { - sendCommand(FlexCommand.STREAM_CREATE_DAX_RX, String.format("stream create type=dax_rx dax_channel=%d", channel)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandStreamCreateDaxTx(int channel) { - //sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx dax_channel=%d", channel)); -// sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx compression=none")); - sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=remote_audio_tx")); - } - - public synchronized void commandRemoveDaxStream() { - sendCommand(FlexCommand.STREAM_REMOVE, String.format("stream remove 0x%x", getDaxAudioStreamId())); - } - - public synchronized void commandRemoveAllStream() { - for (Long id : streamIdSet) { - sendCommand(FlexCommand.STREAM_REMOVE, String.format("stream remove 0x%x", id)); - } - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSetFilter(int sliceOrder, int filt_low, int filt_high) { - sendCommand(FlexCommand.FILT_SET, String.format("filt %d %d %d", sliceOrder, filt_low, filt_high)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandStartATU() { - sendCommand(FlexCommand.FILT_SET, "atu start"); - } - - public synchronized void commandGetInfo() { - sendCommand(FlexCommand.INFO, "info"); - } - - public synchronized void commandPanadapterCreate() { - sendCommand(FlexCommand.PANADAPTER_CREATE, "display pan c freq=9.5 ant=ANT1 x=800 y=400"); - } - - public synchronized void commandPanadapterRemove() { - sendCommand(FlexCommand.PANADAPTER_REMOVE, String.format("display pan r 0x%x", panadapterStreamId)); - //sendCommand(FlexCommand.PANADAPTER_REMOVE,"display pan r 0x40000001"); - //sendCommand(FlexCommand.PANADAPTER_REMOVE,"display pan r 0x40000000"); - } - - public synchronized void commandMeterCreateAmp() { - sendCommand(FlexCommand.METER_CREATE_AMP, "meter create name=AFRAMP type=AMP min=-150.0 max=20.0 units=AMPS"); - } - - public synchronized void commandMeterList() { - sendCommand(FlexCommand.METER_LIST, "meter list"); - } - - public synchronized void commandSubClientAll() { - sendCommand(FlexCommand.SUB_CLIENT_ALL, "sub client all"); - } - - public synchronized void commandSubTxAll() { - sendCommand(FlexCommand.SUB_TX_ALL, "sub client all"); - } - - public synchronized void commandSubAtuAll() { - sendCommand(FlexCommand.SUB_ATU_ALL, "sub atu all"); - } - - public synchronized void commandSubAmplifierAtuAll() { - sendCommand(FlexCommand.SUB_amplifier_ALL, "sub amplifier all"); - } - - public synchronized void commandSubMeterAll() { - sendCommand(FlexCommand.SUB_METER_ALL, "sub meter all"); - //sendCommand(FlexCommand.SUB_METER_ALL,"sub meter 15"); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSubMeterById(int id) { - sendCommand(FlexCommand.SUB_METER_ID, String.format("sub meter %d", id)); - } - - public synchronized void commandSubPanAll() { - sendCommand(FlexCommand.SUB_PAN_ALL, "sub pan all"); - } - - public synchronized void commandSubSliceAll() { - sendCommand(FlexCommand.SUB_METER_ALL, "sub slice all"); - } - - public synchronized void commandSubAudioStreamAll() { - sendCommand(FlexCommand.SUB_AUDIO_STREAM_ALL, "sub audio_stream all"); - } - - public synchronized void commandSubDaxIqAll() { - sendCommand(FlexCommand.SUB_DAX_IQ_ALL, "sub daxiq all"); - } - - public synchronized void commandSubDaxAll() { - sendCommand(FlexCommand.SUB_DAX_ALL, "sub dax all"); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSetRfPower(int power) { - sendCommand(FlexCommand.TRANSMIT_MAX_POWER, String.format("transmit set max_power_level=%d", power)); - sendCommand(FlexCommand.TRANSMIT_POWER, String.format("transmit set rfpower=%d", power)); - //sendCommand(FlexCommand.TRANSMIT_MAX_POWER,"info"); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSetTunePower(int power) { - sendCommand(FlexCommand.AUT_TUNE_MAX_POWER, String.format("transmit set tunepower=%d", power)); - } - - public synchronized void commandPTTOnOff(boolean on) { - if (on) { - sendCommand(FlexCommand.PTT_ON, "xmit 1"); - } else { - sendCommand(FlexCommand.PTT_ON, "xmit 0"); - } - } - - public synchronized void commandTuneTransmitOnOff(boolean on) { - if (on) { - sendCommand(FlexCommand.PTT_ON, "transmit tune on"); - } else { - sendCommand(FlexCommand.PTT_ON, "transmit tune off"); - } - } - - - @SuppressLint("DefaultLocale") - public synchronized void commandDisplayPan(int x, int y) { - sendCommand(FlexCommand.DISPLAY_PAN, String.format("display pan set 0x%X xpixels=%d", 0x40000000, x)); - sendCommand(FlexCommand.DISPLAY_PAN, String.format("display pan set 0x%X ypixels=%d", 0x40000000, y)); - } - //**************封装FlexRadio各种指令*结束*********************** - - @Override - protected void finalize() throws Throwable { - closeStreamPort(); - super.finalize(); - } - - - //**************各种接口********************** - - /** - * 当TCP接收到数据 - */ - public interface OnReceiveDataListener { - void onDataReceive(byte[] data); - } - - /** - * 当接收到指令回复 - */ - public interface OnCommandListener { - void onResponse(FlexResponse response); - } - - public interface OnStatusListener { - void onStatus(FlexResponse response); - } - - public interface OnMessageListener { - void onMessage(FlexResponse response); - } - - /** - * 当TCP连接状态变化 - */ - public interface OnTcpConnectStatus { - void onConnectSuccess(RadioTcpClient tcpClient); - - void onConnectFail(RadioTcpClient tcpClient); - } - - /** - * 当接收到流数据时的事件 - */ - public interface OnReceiveStreamData { - void onReceiveAudio(byte[] data);//音频数据 - - void onReceiveIQ(byte[] data);//IQ数据 - - void onReceiveFFT(VITA vita);//频谱数据 - - void onReceiveMeter(VITA vita);//仪表数据 - - void onReceiveUnKnow(byte[] data);//未知数据 - } - //******************************************* - - - /** - * 电台TCP回复数据的基础类 - */ - public static class FlexResponse { - private static final String TAG = "FlexResponse"; - public FlexResponseStyle responseStyle; - public String head;//消息头 - public String content;//消息内容 - public String exContent;//扩展潇潇兮,有的返回消息分为3段,取第3段消息 - public String rawData;//原始数据 - public int seq_number;//32位int,指令序号 - public int handle;//句柄,32位,16进制 - public String version;//版本信息 - public int message_num;//消息号,32位,16进制。其中位24-25包含消息的严重性(0=信息,1=警告,2=错误,3=致命错误) - public long daxStreamId = 0; - public long daxTxStreamId = 0; - public long panadapterStreamId = 0; - public FlexCommand flexCommand = FlexCommand.UNKNOW; - public long resultValue = 0; - - public FlexResponse(String line) { - //Log.e(TAG, "FlexResponse: line--->"+line ); - rawData = line; - char header; - if (line.length() > 0) { - header = line.toUpperCase().charAt(0); - } else { - header = 0; - } - switch (header) { - case 'S': - responseStyle = FlexResponseStyle.STATUS; - getHeadAndContent(line, "\\|"); - try { - this.handle = Integer.parseInt(head.substring(1), 16);//解析16进制 - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "FlexResponse status handle exception: " + e.getMessage()); - } - break; - case 'R': - responseStyle = FlexResponseStyle.RESPONSE; - getHeadAndContent(line, "\\|"); - try { - seq_number = Integer.parseInt(head.substring(1));//解析指令序号 - flexCommand = FlexCommand.values()[seq_number % 1000]; - switch (flexCommand) { - case STREAM_CREATE_DAX_RX: - this.daxStreamId = getStreamId(line); - break; - case PANADAPTER_CREATE: - this.panadapterStreamId = getStreamId(line); - break; - case STREAM_CREATE_DAX_TX: - this.daxTxStreamId = getStreamId(line); - break; - } - resultValue = Integer.parseInt(content, 16);//取命令的返回值 - - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "FlexResponse parseInt seq_number exception: " + e.getMessage()); - } - break; - case 'H': - responseStyle = FlexResponseStyle.HANDLE; - head = line; - content = line; - Log.e(TAG, "FlexResponse: handle:" + line.substring(1)); - try { - this.handle = Integer.parseInt(line.substring(1), 16);//解析16进制 - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "FlexResponse parseInt handle exception: " + e.getMessage()); - } - - break; - case 'V': - responseStyle = FlexResponseStyle.VERSION; - head = line; - content = line; - this.version = line.substring(1); - break; - case 'M': - responseStyle = FlexResponseStyle.MESSAGE; - getHeadAndContent(line, "\\|"); - try { - //Log.e(TAG, "FlexResponse: "+line ); - this.message_num = Integer.parseInt(head.substring(2), 16);//消息号,32位,16进制 - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "FlexResponse parseInt message_num exception: " + e.getMessage()); - } - - break; - case 'C': - responseStyle = FlexResponseStyle.COMMAND; - getHeadAndContent(line, "\\|"); - int index = 1; - if (head.length() > 2) { - if (head.toUpperCase().charAt(1) == 'D') index = 2; - } - try { - seq_number = Integer.parseInt(head.substring(index)); - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "FlexResponse parseInt seq_number exception: " + e.getMessage()); - } - - break; - case 0: - default: - responseStyle = FlexResponseStyle.UNKNOW; - break; - } - } - - private long getStreamId(String line) { - String[] lines = line.split("\\|"); - if (lines.length > 2) { - if (lines[1].equals("0")) { - try { - return Long.parseLong(lines[2], 16);//stream id,16进制 - } catch (NumberFormatException e) { - e.printStackTrace(); - Log.e(TAG, "getDaxStreamId exception: " + e.getMessage()); - } - } - } - return 0; - } - - /** - * 分割消息的头和内容,并分别负值给head和content - * - * @param line 消息 - * @param split 分隔符 - */ - private void getHeadAndContent(String line, String split) { - String[] temp = line.split(split); - if (line.length() > 1) { - head = temp[0]; - content = temp[1]; - } else { - head = ""; - content = ""; - - } - - if (temp.length > 2) { - exContent = temp[2]; - } else { - exContent = ""; - } - - } - - public String resultStatus() { - if (resultValue == 0) { - return String.format(GeneralVariables.getStringFromResource( - R.string.instruction_success), flexCommand.toString()); - } else { - return String.format(GeneralVariables.getStringFromResource( - R.string.instruction_failed), flexCommand.toString(), rawData); - } - } - - } - - - // ********事件的Getter和Setter********* - - public OnReceiveDataListener getOnReceiveDataListener() { - return onReceiveDataListener; - } - - public void setOnReceiveDataListener(OnReceiveDataListener onReceiveDataListener) { - this.onReceiveDataListener = onReceiveDataListener; - } - - public OnCommandListener getOnCommandListener() { - return onCommandListener; - } - - public void setOnCommandListener(OnCommandListener onCommandListener) { - this.onCommandListener = onCommandListener; - } - - public RadioTcpClient getTcpClient() { - return tcpClient; - } - - public String getVersion() { - return version; - } - - public int getHandle() { - return handle; - } - - public String getHandleStr() { - return String.format("%X", handle); - } - - public void setHandle(int handle) { - this.handle = handle; - } - - public boolean isAllFlexRadioStatusEvent() { - return allFlexRadioStatusEvent; - } - - public void setAllFlexRadioStatusEvent(boolean allFlexRadioStatusEvent) { - this.allFlexRadioStatusEvent = allFlexRadioStatusEvent; - } - - public String getClientID() { - return clientID; - } - - public void setClientID(String clientID) { - clientID = clientID; - } - - public int getCommandSeq() { - return commandSeq * 1000 + flexCommand.ordinal(); - } - - public String getCommandStr() { - return commandStr; - } - - public long getDaxAudioStreamId() { - return daxAudioStreamId; - } - - public String getDiscovery_protocol_version() { - return discovery_protocol_version; - } - - public String getModel() { - return model; - } - - public String getSerial() { - return serial; - } - - public String getNickname() { - return nickname; - } - - public String getCallsign() { - return callsign; - } - - public String getIp() { - return ip; - } - - public int getPort() { - return port; - } - - public String getStatus() { - return status; - } - - public String getInUse_ip() { - return inUse_ip; - } - - public String getInUse_host() { - return inUse_host; - } - - public String getMax_licensed_version() { - return max_licensed_version; - } - - public String getRadio_license_id() { - return radio_license_id; - } - - public String getRequires_additional_license() { - return requires_additional_license; - } - - public String getFpc_mac() { - return fpc_mac; - } - - public int getWan_connected() { - return wan_connected; - } - - public int getLicensed_clients() { - return licensed_clients; - } - - public int getAvailable_clients() { - return available_clients; - } - - public int getMax_panadapters() { - return max_panadapters; - } - - public int getAvailable_panadapters() { - return available_panadapters; - } - - public int getMax_slices() { - return max_slices; - } - - public int getAvailable_slices() { - return available_slices; - } - - public String getGui_client_ips() { - return gui_client_ips; - } - - public String getGui_client_hosts() { - return gui_client_hosts; - } - - public String getGui_client_programs() { - return gui_client_programs; - } - - public String getGui_client_stations() { - return gui_client_stations; - } - - public String getGui_client_handles() { - return gui_client_handles; - } - - public boolean isAvailable() { - return isAvailable; - } - - public OnTcpConnectStatus getOnTcpConnectStatus() { - return onTcpConnectStatus; - } - - public void setOnTcpConnectStatus(OnTcpConnectStatus onTcpConnectStatus) { - this.onTcpConnectStatus = onTcpConnectStatus; - } - - public OnReceiveStreamData getOnReceiveStreamData() { - return onReceiveStreamData; - } - - public void setOnReceiveStreamData(OnReceiveStreamData onReceiveStreamData) { - this.onReceiveStreamData = onReceiveStreamData; - } - - public OnStatusListener getOnStatusListener() { - return onStatusListener; - } - - public void setOnStatusListener(OnStatusListener onStatusListener) { - this.onStatusListener = onStatusListener; - } - - public RadioUdpClient getStreamClient() { - return streamClient; - } - - public AudioTrack getAudioTrack() { - return audioTrack; - } - - - public static float[] getFloatFromBytes(byte[] bytes) { - float[] floats = new float[bytes.length / 4]; - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); - for (int i = 0; i < floats.length; i++) { - try { - floats[i] = dis.readFloat(); - } catch (IOException e) { - e.printStackTrace(); - Log.e(TAG, "getFloat: ------>>" + e.getMessage()); - break; - } - } - try { - dis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return floats; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public void setModel(String model) { - this.model = model; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java deleted file mode 100644 index c89d532..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.bg7yoz.ft8cn.flex; - -import android.util.Log; - -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; - -// VITA 形成的发现消息解析器的枚举定义 -enum VitaTokens { - nullToken , - ipToken, - portToken, - modelToken, - serialToken, - callsignToken, - nameToken, - dpVersionToken, - versionToken, - statusToken, -}; -/** - * RadioFactory 当前发现的所有收音机。 - * RadioFactory: 实例化这个类来创建一个 Radio Factory,它将为网络上发现的无线电维护FlexRadio列表flexRadios。 - * - * 通过Upd协议,在4992端口的广播数据中获取vita协议数据,并解析出序列号,用于更新电台列表flexRadios。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class FlexRadioFactory { - private static final String TAG="FlexRadioFactory"; - private static final int FLEX_DISCOVERY_PORT =4992; - private static FlexRadioFactory instance=null; - private final RadioUdpClient broadcastClient ; - private OnFlexRadioEvents onFlexRadioEvents; - - private Timer refreshTimer=null; - private TimerTask refreshTask=null; - - public ArrayList flexRadios=new ArrayList<>(); - - /** - * 获取电台列表实例 - * @return 电台列表实例 - */ - public static FlexRadioFactory getInstance(){ - if (instance==null){ - instance= new FlexRadioFactory(); - } - return instance; - } - - - - public FlexRadioFactory() { - broadcastClient = new RadioUdpClient(FLEX_DISCOVERY_PORT); - - broadcastClient.setOnUdpEvents(new RadioUdpClient.OnUdpEvents() { - @Override - public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { - VITA vita = new VITA(data); - if (vita.isAvailable//如果数据包有效,且classId=0x534CFFFF,StreamId=0x800,更新电台列表 - &&vita.informationClassCode==VITA.FLEX_CLASS_ID - &&vita.packetClassCode==VITA.VS_Discovery - &&vita.streamId==VITA.FLEX_Discovery_stream_ID){ - updateFlexRadioList(new String(vita.payload)); - } - } - }); - try { - broadcastClient.setActivated(true); - } catch (SocketException e) { - e.printStackTrace(); - Log.e(TAG, "FlexRadioFactory: "+e.getMessage()); - } - - } - - - public void startRefreshTimer(){ - if (refreshTimer==null) { - refreshTask=new TimerTask() { - @Override - public void run() { - Log.e(TAG, "run: 检查离线" ); - checkOffLineRadios(); - } - }; - refreshTimer=new Timer(); - refreshTimer.schedule(refreshTask, 1000, 1000);//检查电台列表中的电台是否在线(每一秒) - } - } - public void cancelRefreshTimer(){ - if (refreshTimer!=null){ - refreshTimer.cancel(); - refreshTimer=null; - refreshTask.cancel(); - refreshTask=null; - } - } - - /** - * 从数据中查找电台的序列号 - * @param s 数据 - * @return 序列号 - */ - private String getSerialNum(String s){ - String[] strings=s.split(" "); - for (int i = 0; i > 28) -public static intVH_C(x) ((x & 0x08000000) >> 26) -public static intVH_T(x) ((x & 0x04000000) >> 25) -public static intVH_TSI(x) ((x & 0x00c00000) >> 21) -public static intVH_TSF(x) ((x & 0x00300000) >> 19) -public static intVH_PKT_CNT(x) ((x & 0x000f0000) >> 16) -public static intVH_PKT_SIZE(x) (x & 0x0000ffff) - */ - -// Enumerates for field values - -import android.annotation.SuppressLint; -import android.util.Log; - -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; - -enum VitaPacketType { - IF_DATA,//IF Data packet without Stream Identifier - IF_DATA_WITH_STREAM,//IF Data packet with Stream Identifier - EXT_DATA,//Extension Data packet without Stream Identifier - EXT_DATA_WITH_STREAM,//Extension Data packet with Stream Identifier - IF_CONTEXT,//IF Context packet(see Section 7) - EXT_CONTEXT//Extension Context packet(see Section 7); -}; - -//时间戳的类型 -//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, -//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 -//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, -//所有的时间带来都是在以一个采样数据为该reference-point 时间 -enum VitaTSI { - TSI_NONE,//No Integer-seconds Timestamp field included - TSI_UTC,//Coordinated Universal Time(UTC) - TSI_GPS,//GPS time - TSI_OTHER//Other -}; - -//时间戳小数部分类型 -//小数部分主要有三种: -// 一种是sample-count ,以采样周期为最小分辨率, -// 一种是real-time以ps为最小单位, -// 第三种是以任意选择的时间进行累加得出的, -// 前面两种时间戳可以直接与整数部分叠加, -// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 -// 小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, -// 所有的时间带来都是在以一个采样数据为该参考点(reference-point)的时间。 -enum VitaTSF { - TSF_NONE,//No Fractional-seconds Timestamp field included. 不包括分数秒时间戳字段 - TSF_SAMPLE_COUNT,//Sample Count Timestamp. 样本计数时间戳 - TSF_REALTIME,//Real Time(Picoseconds) Timestamp. 实时(皮秒)时间戳 - TSF_FREERUN,//Free Running Count Timestamp. 自由运行计数时间戳 -}; - -public class VITA { - private static final String TAG = "VITA"; - - // 最小有效的VITA包长度 - private static final int VITAmin = 28; - - public static final int FRS_OUI = 0x12cd; - //public static final int VITA_PORT = 4991; - - public static final int FLEX_CLASS_ID = 0x534C; - public static final int FLEX_DAX_AUDIO_CLASS_ID = 0x534C03E3; - public static final int FLEX_DAX_IQ_CLASS_ID = 0x534C00E3; - public static final int FLEX_FFT_CLASS_ID = 0x534C8003; - public static final int FLEX_METER_CLASS_ID = 0x534C8002; - public static final int FLEX_Discovery_stream_ID = 0x800; - - - public static final int VS_Meter = 0x8002; - public static final int VS_PAN_FFT = 0x8003; - public static final int VS_Waterfall = 0x8004; - public static final int VS_Opus = 0x8005; - public static final int DAX_IQ_24Khz = 0x00e3; - public static final int DAX_IQ_48Khz = 0x00e4; - public static final int DAX_IQ_96Khz = 0x00e5; - public static final int DAX_IQ_192KHz = 0x00e6; - public static final int VS_DAX_Audio = 0x03e3; - public static final int VS_Discovery = 0xffff; - - - private byte[] buffer; - public VitaPacketType packetType; - public boolean classIdPresent;//指示数据包中是否包含类标识符(类ID)字段 - public boolean trailerPresent;//指示数据包是否包含尾部。 - public VitaTSI tsi;//时间戳的类型。 - public VitaTSF tsf;//时间戳小数部分类型 - public int packetCount;//包计数器,可以对连续的IF data packet进行计数,这些packet具有相同的Stream Identifier 和packet type。 - public int packetSize;//表示有多少32bit数在IF Data packet 里面 - - //时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位,小数部分64位。 - public long integerTimestamp;//u_int32,long是64位的 - public long fracTimeStamp; - public long oui; - public int informationClassCode;//无用了,用classId代替 - public int packetClassCode;//无用了,用classId代替 - public int classId;//FLEX应该是0x534CFFF,是informationClassCode与packetClassCode合并的 - public byte[] payload = null; - public long trailer; - public boolean isAvailable = false;//电台对象是否有效。 - - public boolean streamIdPresent;//是否有流字符 - - //用来区分不同的 packet stream 。 - //stream ID 不是必须的,如果仅有一个数据包在单一数据链路传递的话就可以不用要, - //如果 packet stream想用同一 stream ID 的话那每一个packet都得有, - //在系统内部,不同的packet stream 之间的 Stream ID是不同的。 - //如果要用到 data-context 配对,那么IF data packet需要 Stream ID - public long streamId;//流ID,32位,FLEX应当是0x0800 - - /* - VITA前28个字节是VITA头 - */ - - - /** - * 生成音频流的VITA数据包,id应当是电台create stream是赋给的 - * - * @param id streamId - * @param data 音频流数据 - * @return vita数据包 - */ - public byte[] audioDataToVita(int count,long id, float[] data) { - byte[] result = new byte[data.length*4 + 28];//一个float占用4个字节,28字节是包头的长度7个word - //packetType = VitaPacketType.EXT_DATA_WITH_STREAM; - packetType = VitaPacketType.IF_DATA_WITH_STREAM; - classIdPresent = true; - trailerPresent = false;//没有尾巴 - tsi = VitaTSI.TSI_NONE;// -// tsi = VitaTSI.TSI_OTHER;// - //tsf = VitaTSF.TSF_SAMPLE_COUNT;//--TODO---查一下这个数字是不是变化 - tsf = VitaTSF.TSF_NONE;//--TODO---查一下这个数字是不是变化 - //packetCount动态变化 - //packetCount=?应该是这个全部音频流的总包数 - - //packetSize是以word(32位,4字节)为单位, - //packetSize值为263居多估计以音频,还有其它的长度,263是包含7个word(28字节)的头长度。 - packetSize = (data.length ) + 7;//7个word是VITA的包头 - //----以上是Header,32位,第一个word------- - - streamId = id;//第二个word,此id是电台赋给的。经常是0x40000xx。 - - oui = 0x00001c2d;//第三个word,FlexRadio Systems OUI - classId = 0x534c0123;//第四个word,64位 - //classId = 0x534c03e3;//第四个word,64位 - - //integerTimestamp =0;// System.currentTimeMillis() / 1000;//第五个word,时间戳的整数部分,以秒为单位。应该是取当前时间 - //fracTimeStamp = 0;//第六七个word,时间戳的小数部分,64位,此处为0。 - //fracTimeStamp = frac;//第六七个word,时间戳的小数部分,64位,此处为0。 - - byte temp = 0; - if (classIdPresent) { - temp = 0x08; - } - if (trailerPresent) { - temp |= 0x04; - } - //----HEADER--No.1 word------ -// result[0]=0x18; - result[0] = (byte) (packetType.ordinal() << 4);//packetType - result[0] |= temp;//其实就是0011 1000,0x38//CTRR,classIdPresent、trailerPresent、R、R - result[0] |= 0x03c0;//CTRR,classIdPresent、trailerPresent、R、R - - result[1]=(byte) 0xd0; - result[1]|=(byte)(count&0xf);//packet count - result[1] = (byte) (tsi.ordinal() << 6);//TSI - result[1] |= (byte) (tsf.ordinal() << 4);//TSF - result[1] |= (byte) (packetCount & 0xff);//packetCount - - //packetSize默认263(words) - result[2] = (byte) ((packetSize >> 8) & 0xff);//packetSize 1(高8位) - result[3] = (byte) (packetSize & 0xff);//packetSize 2(低8位) - - //-----Stream Identifier--No.2 word---- - //streamId=id;//最后两位应当是Dax编号 - result[4] = (byte) ((streamId& 0x00ff000000 >> 24) & 0xff); - result[5] = (byte) (((streamId & 0x00ff0000) >> 16) & 0xff); - result[6] = (byte) (((streamId & 0x0000ff00) >> 8) & 0xff); - result[7] = (byte) (streamId & 0x000000ff); - - //----OUI--No.3 words---- - //OUI = 0x001C2D - result[8] = 0x00; - result[9] = 0x00; - result[10] = 0x1c; - result[11] = 0x2d; - //---Class Identifier--No.4 word---- - //class id=0x534c0123 - result[12] = 0x53; - result[13] = 0x4c; - result[14] = (byte) 0x01; - result[15] = (byte) 0x23; - - //---Timestamp--No.5 word---- - //integerTimestamp=0x01020304 - -// result[16] = (byte) 0x01; -// result[17] = (byte) 0x02; -// result[18] = (byte) 0x03; -// result[19] = (byte) 0x04; - - //---FracTimeStamp No.5~6 words---- - //fracTimeStamp=0x10200300506070c0 -// result[20] = 0x10; -// result[21] = 0x20; -// result[22] = 0x03; -// result[23] = 0x00; -// result[24] = 0x50; -// result[25] = 0x60; -// result[26] = 0x70; -// result[27] = (byte) 0xc0; -// result[20] = (byte) ((fracTimeStamp >> 56) & 0x000000ff); -// result[21] = (byte) ((fracTimeStamp >> 48) & 0x000000ff); -// result[22] = (byte) ((fracTimeStamp >> 40) & 0x000000ff); -// result[23] = (byte) ((fracTimeStamp >> 32) & 0x000000ff); -// -// result[24] = (byte) ((fracTimeStamp >> 24) & 0x000000ff); -// result[25] = (byte) ((fracTimeStamp >> 16) & 0x000000ff); -// result[26] = (byte) ((fracTimeStamp >> 8) & 0x000000ff); -// result[27] = (byte) (fracTimeStamp & 0x000000ff); - for (int i = 0; i < data.length; i++) { - byte[] bytes=ByteBuffer.allocate(4).putFloat(data[i]).array();//float转byte[] - result[i*4+28]= bytes[0]; - result[i*4+29]= bytes[1]; - result[i*4+30]= bytes[2]; - result[i*4+31]= bytes[3]; - } - - /* - 也就是payload的长度+28字节:byte[] result=new byte[data.length+28]; - streamIdPresent=true; - streamIdPresent=packetType==VitaPacketType.IF_DATA_WITH_STREAM - ||packetType==VitaPacketType.EXT_DATA_WITH_STREAM; - streamId:0x4000008,此处应该是STREAM_CREATE_DAX_TX的值 - classIdPresent:0x534c03e3,packetSize:263 - integerTimestamp=now/1000;以秒为单位 - fracTimeStamp=0; - */ - - - return result; - } - - public VITA() { - } - - public VITA(byte[] data) { - this.buffer = data; - //如果包的长度太小,或包为空,就退出计算 - if (data == null) return; - if (data.length < VITAmin) return; - - isAvailable = true;//数据长度达到28个字节,说明是有效的。 - packetType = VitaPacketType.values()[(data[0] >> 4) & 0x0f]; - classIdPresent = (data[0] & 0x8) == 0x8;//指示数据包中是否包含类标识符(类ID)字段 - trailerPresent = (data[0] & 0x4) == 0x4;//指示数据包是否包含尾部。 - tsi = VitaTSI.values()[(data[1] >> 6) & 0x3];//如果有时间戳的话指示时间戳的整数部分是啥类型的 - tsf = VitaTSF.values()[(data[1] >> 4) & 0x3]; - packetCount = data[1] & 0x0f; - packetSize = ((((int) data[2]) & 0x00ff) << 8) | ((int) data[3]) & 0x00ff; - - int offset = 4;//定位 - //检查是否有流字符 - streamIdPresent = packetType == VitaPacketType.IF_DATA_WITH_STREAM - || packetType == VitaPacketType.EXT_DATA_WITH_STREAM; - - if (streamIdPresent) {//是否有流ID,获取流ID,32位 - streamId = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) - | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; - offset += 4; - } - - if (classIdPresent) { - //只取24位,前8位保留 - oui = ((((int) data[offset + 1]) & 0x00ff) << 16) - | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; - - informationClassCode = ((((int) data[offset + 4]) & 0x00ff) << 8) | ((int) data[offset + 5]) & 0x00ff; - packetClassCode = ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; - - classId = ((((int) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16) - | ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; - offset += 8; - } - //Log.e(TAG, "VITA: "+String.format("id: 0x%x, classIdPresent:0x%x,packetSize:%d",streamId,classId,packetSize) ); - - //获取时间戳,以秒为单位的时间戳,32位。 - //时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, - //小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 - //小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, - //所有的时间带来都是在以一个采样数据为该reference-point 时间 - if (tsi != VitaTSI.TSI_NONE) {//32位, - integerTimestamp = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) - | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; - offset += 4; - } - //获取时间戳的小数部分,64位。 - if (tsf != VitaTSF.TSF_NONE) { - fracTimeStamp = ((((long) data[offset]) & 0x00ff) << 56) | ((((long) data[offset + 1]) & 0x00ff) << 48) - | ((((long) data[offset + 2]) & 0x00ff) << 36) | ((int) data[offset + 3]) & 0x00ff - | ((((long) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16) - | ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; - offset += 8; - } - - - //Log.e(TAG, String.format("VITA: data length:%d,offset:%d",data.length,offset) ); - if (offset < data.length) { - payload = new byte[data.length - offset - (trailerPresent ? 2 : 0)];//如果有尾部,就减去一个word的位置 - System.arraycopy(data, offset, payload, 0, payload.length); - } - if (trailerPresent) { - trailer = ((((int) data[data.length - 2]) & 0x00ff) << 8) | ((int) data[data.length - 1]) & 0x00ff; - } - } - - /** - * 获取payload的长度,如果没有数据,payload长度为0; - * - * @return payload长度 - */ - public int getPayloadLength() { - if (buffer == null) { - return 0; - } else { - return buffer.length; - } - } - - /** - * 显示扩展数据 - * - * @return string - */ - public String showPayload() { - if (payload != null) { - return new String(payload).replace(" ", "\n"); - } else { - return ""; - } - } - - public String showPayloadHex() { - if (payload != null) { - return byteToStr(payload); - } else { - return ""; - } - } - - /** - * 显示VITA 49的包头信息 - * - * @return string - */ - @SuppressLint("DefaultLocale") - public String showHeadStr() { - return String.format("包类型(packetType): %s\n" + - "包数量(packetCount): %d\n" + - "包大小(packetSize): %d\n" + - "是否有流ID(streamIdPresent): %s\n" + - "流ID(streamId): 0x%X\n" + - "是否有类ID(classIdPresent): %s\n" + - "类ID(classId): 0x%X\n" + - "类高位(informationClassCode): 0x%X\n" + - "类低位(packetClassCode): 0x%X\n" + - "公司标识码(oui): 0x%X\n" + - "时间戳类型(tsi): %s\n" + - "时间戳整数部分(integerTimestamp):%s\n" + - "时间戳小数部分类型(tsf): %s\n" + - "时间戳小数部分值(fracTimeStamp): %d\n" + - "负载长度(payloadLength): %d\n" + - "是否有尾部(trailerPresent): %s\n" - - , packetType.toString() - , packetCount - , packetSize - , streamIdPresent ? "是" : "否" - , streamId - , classIdPresent ? "是" : "否" - , classId - , informationClassCode - , packetClassCode - , oui - , tsi.toString() - , timestampToDateStr(integerTimestamp * 1000) - , tsf.toString() - , fracTimeStamp - , (payload == null ? 0 : payload.length) - , trailerPresent ? "是" : "否" - ); - } - - /** - * 显示VITA 49 包数据 - * - * @return string - */ - @SuppressLint("DefaultLocale") - @Override - public String toString() { - return String.format("%s负载(payload):\n%s\n" - , showHeadStr() - , (payload == null ? "" : new String(payload)) - ); - - - } - - public static String timestampToDateStr(Long timestamp) { - //final String DATETIME_CONVENTIONAL_CN = "yyyy-MM-dd HH:mm:ss"; - //SimpleDateFormat sdf = new SimpleDateFormat(DATETIME_CONVENTIONAL_CN); - @SuppressLint("SimpleDateFormat") - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String sd = sdf.format(new Date(timestamp)); // 时间戳转换日期 - //System.out.println(sd); - return sd; - } - - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%02x ", data[i] & 0xff)); - } - return s.toString(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java deleted file mode 100644 index 618b442..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java +++ /dev/null @@ -1,381 +0,0 @@ -package com.bg7yoz.ft8cn.floatview; -/** - * FloatButton的主界面 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ImageButton; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; -import androidx.constraintlayout.widget.Constraints; - -import java.util.ArrayList; - - -public class FloatView extends ConstraintLayout { - private static final String TAG = "FloatView"; - - public enum FLOAT_BOARD { - LEFT, RIGHT, TOP, BUTTON - } - - - private int parentViewHeight = 100;//上一级view的高度 - private int parentViewWidth = 100;//上一级view的宽度 - private float mDownX = 0; - private float mDownY = 0; - private int lastLeft = 0; - private int lastTop = 0; - - - //------------悬浮窗口的属性-------------------- - private int buttonSize = 96;//按钮的大小 - private final ArrayList buttons = new ArrayList<>(); - private boolean originalFromTop = false;//是否上下靠边 - private boolean originalFromLeft = true;//是否左右靠边 - private int buttonBackgroundResourceId = -1;//按钮的背景 - private int backgroundResourceId = -1;//浮窗的背景 - private int buttonMargin = 0;//按钮在浮窗中的边界宽度 - private int floatMargin = 40;//浮窗距离边界的距离 - private FLOAT_BOARD floatBoard = FLOAT_BOARD.LEFT; - - - /** - * 构造函数,需要大小 - * - * @param context context - * @param buttonSize 按钮大小,正方形 - */ - public FloatView(@NonNull Context context, int buttonSize) { - this(context); - this.buttonSize = buttonSize; - } - - public FloatView(@NonNull Context context) { - this(context, null); - } - - public FloatView(@NonNull Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - - public FloatView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initView(); - } - - public FloatViewButton addButton(String name, int imageResourceId, OnClickListener onClickListener) { - FloatViewButton floatViewButton=getButtonByName(name); - if (floatViewButton==null){ - floatViewButton =addButton(View.generateViewId(), imageResourceId, onClickListener); - } - floatViewButton.setName(name); - return floatViewButton; - } - - public FloatViewButton addButton(int id, String name, int imageResourceId, OnClickListener onClickListener) { - FloatViewButton floatViewButton=getButtonByName(name); - if (floatViewButton==null){ - floatViewButton = addButton(id, imageResourceId, onClickListener); - } - floatViewButton.setName(name); - return floatViewButton; - } - - public FloatViewButton addButton(int id, int imageResourceId, OnClickListener onClickListener) { - FloatViewButton imageButton = new FloatViewButton(getContext()); - //imageButton.setScaleType(ImageView.ScaleType.FIT_CENTER); - imageButton.setImageResource(imageResourceId); - if (buttonBackgroundResourceId != -1) { - imageButton.setBackgroundResource(buttonBackgroundResourceId); - } - //imageButton.setId(R.id.float_nav); - imageButton.setId(id); - imageButton.setOnClickListener(onClickListener); - - imageButton.setAlpha(0.5f); - - addView(imageButton); - buttons.add(imageButton); - resetView(); - - return imageButton; - } - - /** - * 通过按钮的名称删除按钮 - * - * @param name 按钮的名称 - */ - public void deleteButtonByName(String name) { - for (int i = buttons.size() - 1; i >= 0; i--) { - FloatViewButton floatViewButton = buttons.get(i); - if (floatViewButton.getName().equals(name)) { - buttons.remove(i); - removeView(floatViewButton); - } - resetView(); - } - } - - public void deleteButtonByIndex(int index) { - if (buttons.size() > index && index > -1) { - FloatViewButton floatViewButton = buttons.get(index); - buttons.remove(index); - removeView(floatViewButton); - resetView(); - } - } - - public FloatViewButton getButtonByName(String name) { - for (FloatViewButton button : buttons) { - if (button.getName().equals(name)) { - return button; - } - } - return null; - } - - /** - * 把dp值转换为像素点 - * - * @param dp dp值 - * @return 像素点 - */ - private int dpToPixel(int dp) { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp - , getResources().getDisplayMetrics()); -// return (int) (dp*getResources().getDisplayMetrics().density); - } - - /** - * 重新设置一下按钮 - */ - public void resetView() { - for (int i = 0; i < buttons.size(); i++) { - //LayoutParams buttonLp = new LayoutParams(buttonSize, buttonSize); - LayoutParams buttonLp = new LayoutParams(dpToPixel(buttonSize), dpToPixel(buttonSize)); - buttonLp.startToStart = ConstraintSet.PARENT_ID; - buttonLp.endToEnd = ConstraintSet.PARENT_ID; - buttonLp.leftMargin = buttonMargin; - buttonLp.rightMargin = buttonMargin; - if (i == 0) { - buttonLp.topToTop = ConstraintSet.PARENT_ID; - buttonLp.topMargin = buttonMargin; - } else { - buttonLp.topToBottom = buttons.get(i - 1).getId(); - buttonLp.topMargin = buttonMargin+dpToPixel(4);//按钮之间留一点点空隙 - } - if (i == buttons.size() - 1) { - buttonLp.bottomToBottom = ConstraintSet.PARENT_ID; - buttonLp.bottomMargin = buttonMargin; - } - buttons.get(i).setLayoutParams(buttonLp); - } - - - } - - @SuppressLint("ClickableViewAccessibility") - private void initView() { - LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);// LayoutParams.WRAP_CONTENT); - lp.startToStart = ConstraintSet.PARENT_ID;//只连接窗口的左边和上边 - lp.topToTop = ConstraintSet.PARENT_ID; - this.setLayoutParams(lp); - } - - public void initLocation() { - initLocation(this.floatBoard); - } - - - /** - * 初始化浮窗的位置,默认在窗口的右侧, - */ - public void initLocation(FLOAT_BOARD float_board) { - this.floatBoard = float_board; - getParentViewHeightAndWidth(); - int width; - int height; - - if (parentViewWidth == 0 && parentViewHeight == 0) { - WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); - width = wm.getDefaultDisplay().getWidth(); - height = wm.getDefaultDisplay().getHeight(); - } else {//这部分基本没有执行过 - width = parentViewWidth; - height = parentViewHeight; - } - switch (float_board) { - case RIGHT: - setLayoutLeftTop(width - dpToPixel(floatMargin * 2 - buttonSize) - 10 - , (int) (height / 2f - dpToPixel(buttonMargin + buttonSize * buttons.size())/2f)); - break; - case LEFT: - setLayoutLeftTop(floatMargin + 10 - , (int) (height / 2f - dpToPixel(buttonMargin + buttonSize * buttons.size() )/2f)); - break; - case TOP: - setLayoutLeftTop((int) (width / 2f - dpToPixel(buttonMargin - buttonSize) / 2f), floatMargin); - break; - case BUTTON: - setLayoutLeftTop((int) (width / 2f - dpToPixel(buttonMargin - buttonSize) / 2f) - , height - dpToPixel(floatMargin - buttonMargin * 2 - buttonSize * buttons.size())); - break; - } - - } - - /** - * 获取父View的高度和宽度 - */ - private void getParentViewHeightAndWidth() { - View view = (View) getParent(); - if (view != null) { - parentViewHeight = view.getHeight(); - parentViewWidth = view.getWidth(); - } - - } - - public void setLayoutLeftTop(int left, int top) { - ConstraintLayout.LayoutParams layoutParams = new Constraints.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT - , ViewGroup.LayoutParams.WRAP_CONTENT); - layoutParams.topToTop = ConstraintSet.PARENT_ID; - layoutParams.startToStart = ConstraintSet.PARENT_ID; - layoutParams.leftMargin = left; - layoutParams.topMargin = top; - setLayoutParams(layoutParams); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mDownX = event.getX(); - mDownY = event.getY(); - break; - case MotionEvent.ACTION_MOVE: - offsetTopAndBottom((int) (event.getY() - mDownY)); - offsetLeftAndRight((int) (event.getX() - mDownX)); - - lastLeft = getLeft(); - lastTop = getTop(); - setLayoutLeftTop(getLeft(), getTop()); - - case MotionEvent.ACTION_UP: - - setLayoutLeftTop(lastLeft, lastTop); - - adsorbTopAdnBottom();//吸附上下 - adsorbLeftAndRight();//吸附左右 - break; - case MotionEvent.ACTION_CANCEL: - break; - - } - return super.onInterceptTouchEvent(event); - } - - - private void adsorbTopAdnBottom() { - if (originalFromTop) { - getParentViewHeightAndWidth(); - - float boundaryLine = parentViewHeight / 4f; - if (getTop() < boundaryLine) { - setLayoutLeftTop(getLeft(), floatMargin); - } else if (getBottom() > parentViewHeight - boundaryLine) { - setLayoutLeftTop(getLeft(), parentViewHeight - getHeight() - floatMargin); - } - } - } - - private void adsorbLeftAndRight() { - if (originalFromLeft) { - getParentViewHeightAndWidth(); - float boundaryLine = parentViewWidth / 4f; - if (getLeft() < boundaryLine) { - setLayoutLeftTop(floatMargin, getTop()); - } else if (getRight() > parentViewWidth - boundaryLine) { - setLayoutLeftTop(parentViewWidth - getWidth() - floatMargin, getTop()); - //animate().setInterpolator(new DecelerateInterpolator()).setDuration(300).x(parentViewWidth - getWidth() - floatMargin).start(); - } - } - } - - - public boolean isOriginalFromTop() { - return originalFromTop; - } - - public void setOriginalFromTop(boolean originalFromTop) { - this.originalFromTop = originalFromTop; - } - - public boolean isOriginalFromLeft() { - return originalFromLeft; - } - - public void setOriginalFromLeft(boolean originalFromLeft) { - this.originalFromLeft = originalFromLeft; - } - - public int getButtonBackgroundResourceId() { - return buttonBackgroundResourceId; - } - - public void setButtonBackgroundResourceId(int buttonBackgroundResourceId) { - this.buttonBackgroundResourceId = buttonBackgroundResourceId; - for (ImageButton button : this.buttons) { - button.setBackgroundResource(buttonBackgroundResourceId); - } - } - - public int getBackgroundResourceId() { - return backgroundResourceId; - } - - public void setBackgroundResourceId(int backgroundResourceId) { - this.setBackgroundResource(backgroundResourceId); - this.backgroundResourceId = backgroundResourceId; - } - - public int getButtonMargin() { - return buttonMargin; - } - - public void setButtonMargin(int buttonMargin) { - this.buttonMargin = buttonMargin; - } - - public int getFloatMargin() { - return floatMargin; - } - - public void setFloatMargin(int floatMargin) { - this.floatMargin = floatMargin; - } - - public FLOAT_BOARD getFloatBoard() { - return floatBoard; - } - - public void setFloatBoard(FLOAT_BOARD float_board) { - this.floatBoard = float_board; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java deleted file mode 100644 index cba7670..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.bg7yoz.ft8cn.floatview; -/** - * 自定义FloatButton - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.widget.ImageButton; - -@SuppressLint("AppCompatCustomView") -public class FloatViewButton extends ImageButton { - private String name; - public FloatViewButton(Context context) { - super(context); - } - - public FloatViewButton(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public FloatViewButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public String getName() { - if (name==null){ - return ""; - }else { - return name; - } - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java deleted file mode 100644 index c516314..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.bg7yoz.ft8cn.ft8listener; - -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; - -import java.util.ArrayList; - -public class A91List { - - public ArrayList list=new ArrayList<>(); - public void clear(){ - list.clear(); - } - public void add(byte[] data,float freq,float sec){ - A91 a91=new A91(data,sec,freq); - list.add(a91); - } - public int size(){ - return list.size(); - } - public static class A91{ - public byte[] a91 ;//= new byte[GenerateFT8.FTX_LDPC_K_BYTES]; - public float time_sec = 0;//时间偏移(秒) - public float freq_hz = 0;//频率 - - public A91(byte[] a91, float time_sec, float freq_hz) { - this.a91 = a91; - this.time_sec = time_sec; - this.freq_hz = freq_hz; - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java deleted file mode 100644 index 45f600e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java +++ /dev/null @@ -1,369 +0,0 @@ -package com.bg7yoz.ft8cn.ft8listener; -/** - * 用于监听音频的类。监听通过时钟UtcTimer来控制周期,通过OnWaveDataListener接口来读取音频数据。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import androidx.lifecycle.MutableLiveData; - -import com.bg7yoz.ft8cn.FT8Common; -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; -import com.bg7yoz.ft8cn.timer.OnUtcTimer; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; -import com.bg7yoz.ft8cn.wave.WaveFileReader; -import com.bg7yoz.ft8cn.wave.WaveFileWriter; - -import java.util.ArrayList; - -public class FT8SignalListener { - private static final String TAG = "FT8SignalListener"; - private final UtcTimer utcTimer; - //private HamRecorder hamRecorder; - private final OnFt8Listen onFt8Listen;//当开始监听,解码结束后触发的事件 - //private long band; - public MutableLiveData decodeTimeSec = new MutableLiveData<>();//解码的时长 - public long timeSec=0;//解码的时长 - - private OnWaveDataListener onWaveDataListener; - - - private DatabaseOpr db; - - private final A91List a91List = new A91List();//a91列表 - - - static { - System.loadLibrary("ft8cn"); - } - - public interface OnWaveDataListener { - void getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone); - } - - public FT8SignalListener(DatabaseOpr db, OnFt8Listen onFt8Listen) { - //this.hamRecorder = hamRecorder; - this.onFt8Listen = onFt8Listen; - this.db = db; - - //创建动作触发器,与UTC时间同步,以15秒一个周期,DoOnSecTimer是在周期起始时触发的事件。150是15秒 - utcTimer = new UtcTimer(FT8Common.FT8_SLOT_TIME_M, false, new OnUtcTimer() { - @Override - public void doHeartBeatTimer(long utc) {//不触发时的时钟信息 - } - - @Override - public void doOnSecTimer(long utc) {//当指定间隔时触发时 - Log.d(TAG, String.format("触发录音,%d", utc)); - runRecorde(utc); - } - }); - } - - public void startListen() { - utcTimer.start(); - } - - public void stopListen() { - utcTimer.stop(); - } - - public boolean isListening() { - return utcTimer.isRunning(); - } - - /** - * 获取当前时间的偏移量,这里包括总的时钟偏移,也包括本实例的偏移 - * - * @return int - */ - public int time_Offset() { - return utcTimer.getTime_sec() + UtcTimer.delay; - } - - /** - * 录音。在后台以多线程的方式录音,录音自动生成一个临时的Wav格式文件。 - * 有两个回调函数,用于开始录音时和结束录音时。当结束录音时,激活解码程序。 - * - * @param utc 当前解码的UTC时间 - */ - private void runRecorde(long utc) { - Log.d(TAG, "开始录音..."); - - if (onWaveDataListener != null) { - onWaveDataListener.getVoiceData(FT8Common.FT8_SLOT_TIME_MILLISECOND, true - , new OnGetVoiceDataDone() { - @Override - public void onGetDone(float[] data) { - Log.d(TAG, "开始解码...###"); - decodeFt8(utc, data); - } - }); - } - } - - public void decodeFt8(long utc, float[] voiceData) { - - //此处是测试用代码------------------------- -// String fileName = getCacheFileName("test_01.wav"); -// Log.e(TAG, "onClick: fileName:" + fileName); -// WaveFileReader reader = new WaveFileReader(fileName); -// int data[][] = reader.getData(); - //---------------------------------------------------------- - - new Thread(new Runnable() { - @Override - public void run() { - long time = System.currentTimeMillis(); - if (onFt8Listen != null) { - onFt8Listen.beforeListen(utc); - } - -// float[] tempData = ints2floats(data); - - - ///读入音频数据,并做预处理 - //其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了 - long ft8Decoder = InitDecoder(utc, FT8Common.SAMPLE_RATE - , voiceData.length, true); -// , tempData.length, true); - DecoderMonitorPressFloat(voiceData, ft8Decoder);//读入音频数据 -// DecoderMonitorPressFloat(tempData, ft8Decoder);//读入音频数据 - - - ArrayList allMsg = new ArrayList<>(); -// ArrayList msgs = runDecode(utc, voiceData,false); - ArrayList msgs = runDecode(ft8Decoder, utc, false); - addMsgToList(allMsg, msgs); - timeSec = System.currentTimeMillis() - time; - decodeTimeSec.postValue(timeSec);//解码耗时 - if (onFt8Listen != null) { - onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, false); - } - - - if (GeneralVariables.deepDecodeMode) {//进入深度解码模式 - //float[] newSignal=tempData; - msgs = runDecode(ft8Decoder, utc, true); - addMsgToList(allMsg, msgs); - timeSec = System.currentTimeMillis() - time; - decodeTimeSec.postValue(timeSec);//解码耗时 - if (onFt8Listen != null) { - onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); - } - - - do { - if (timeSec > FT8Common.DEEP_DECODE_TIMEOUT) break;//此处做超时检测,超过一定时间(7秒),就不做减码操作了 - //减去解码的信号 - ReBuildSignal.subtractSignal(ft8Decoder, a91List); - - //再做一次解码 - msgs = runDecode(ft8Decoder, utc, true); - addMsgToList(allMsg, msgs); - timeSec = System.currentTimeMillis() - time; - decodeTimeSec.postValue(timeSec);//解码耗时 - if (onFt8Listen != null) { - onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); - } - - } while (msgs.size() > 0 ); - - } - //移到finalize() 方法中调用了 - DeleteDecoder(ft8Decoder); - - Log.d(TAG, String.format("解码耗时:%d毫秒", System.currentTimeMillis() - time)); - - } - }).start(); - } - - - private ArrayList runDecode(long ft8Decoder, long utc, boolean isDeep) { - ArrayList ft8Messages = new ArrayList<>(); - Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE); - - ft8Message.utcTime = utc; - ft8Message.band = GeneralVariables.band; - a91List.clear(); - - setDecodeMode(ft8Decoder, isDeep);//设置迭代次数,isDeep==true,迭代次数增加 - - int num_candidates = DecoderFt8FindSync(ft8Decoder);//最多120个 - //long startTime = System.currentTimeMillis(); - for (int idx = 0; idx < num_candidates; ++idx) { - //todo 应当做一下超时计算 - try {//做一下解码失败保护 - if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) { - - if (ft8Message.isValid) { - Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉 - byte[] a91 = DecoderGetA91(ft8Decoder); - a91List.add(a91, ft8Message.freq_hz, ft8Message.time_sec); - - if (checkMessageSame(ft8Messages, msg)) { - continue; - } - - msg.isWeakSignal = isDeep;//是不是弱信号 - ft8Messages.add(msg); - - } - } - } catch (Exception e) { - Log.e(TAG, "run: " + e.getMessage()); - } - - } - - - return ft8Messages; - } - - /** - * 计算平均时间偏移值 - * - * @param messages 消息列表 - * @return 偏移值 - */ - private float averageOffset(ArrayList messages) { - if (messages.size() == 0) return 0f; - float dt = 0; - //int dtAverage = 0; - for (Ft8Message msg : messages) { - dt += msg.time_sec; - } - return dt / messages.size(); - } - - /** - * 把消息添加到列表中 - * - * @param allMsg 消息列表 - * @param newMsg 新的消息 - */ - private void addMsgToList(ArrayList allMsg, ArrayList newMsg) { - for (int i = newMsg.size() - 1; i >= 0; i--) { - if (checkMessageSame(allMsg, newMsg.get(i))) { - newMsg.remove(i); - } else { - allMsg.add(newMsg.get(i)); - } - } - } - - /** - * 检查消息列表里同样的内容是否存在 - * - * @param ft8Messages 消息列表 - * @param ft8Message 消息 - * @return boolean - */ - private boolean checkMessageSame(ArrayList ft8Messages, Ft8Message ft8Message) { - for (Ft8Message msg : ft8Messages) { - if (msg.getMessageText().equals(ft8Message.getMessageText())) { - if (msg.snr < ft8Message.snr) { - msg.snr = ft8Message.snr; - } - return true; - } - } - return false; - } - - @Override - protected void finalize() throws Throwable { - //DeleteDecoder(ft8Decoder); - super.finalize(); - } - - public OnWaveDataListener getOnWaveDataListener() { - return onWaveDataListener; - } - - public void setOnWaveDataListener(OnWaveDataListener onWaveDataListener) { - this.onWaveDataListener = onWaveDataListener; - } - - - public String getCacheFileName(String fileName) { - return GeneralVariables.getMainContext().getCacheDir() + "/" + fileName; - } - - public float[] ints2floats(int data[][]) { - float temp[] = new float[data[0].length]; - for (int i = 0; i < data[0].length; i++) { - temp[i] = data[0][i] / 32768.0f; - } - return temp; - } - - public int[] floats2ints(float data[]) { - int temp[] = new int[data.length]; - for (int i = 0; i < data.length; i++) { - temp[i] = (int) (data[i] * 32767.0f); - } - return temp; - } - - /** - * 解码的第一步,初始化解码器,获取解码器的地址。 - * - * @param utcTime UTC时间 - * @param sampleRat 采样率,12000 - * @param num_samples 缓冲区数据的长度 - * @param isFt8 是否是FT8信号 - * @return 返回解码器的地址 - */ - public native long InitDecoder(long utcTime, int sampleRat, int num_samples, boolean isFt8); - - /** - * 解码的第二步,读取Wav数据。 - * - * @param buffer Wav数据缓冲区 - * @param decoder 解码器数据的地址 - */ - public native void DecoderMonitorPress(int[] buffer, long decoder); - - public native void DecoderMonitorPressFloat(float[] buffer, long decoder); - - - /** - * 解码的第三步,同步数据。 - * - * @param decoder 解码器地址 - * @return 中标信号的数量 - */ - public native int DecoderFt8FindSync(long decoder); - - /** - * 解码的第四步,分析出消息。(需要在一个循环里) - * - * @param idx 中标信号的序号 - * @param decoder 解码器的地址 - * @param ft8Message 解出来的消息 - * @return boolean - */ - public native boolean DecoderFt8Analysis(int idx, long decoder, Ft8Message ft8Message); - - /** - * 解码的最后一步,删除解码器数据 - * - * @param decoder 解码器数据的地址 - */ - public native void DeleteDecoder(long decoder); - - public native void DecoderFt8Reset(long decoder, long utcTime, int num_samples); - - public native byte[] DecoderGetA91(long decoder);//获取当前message的a91数据 - - public native void setDecodeMode(long decoder, boolean isDeep);//设置解码的模式,isDeep=true是多次迭代,=false是快速迭代 -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java deleted file mode 100644 index 04492d0..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.bg7yoz.ft8cn.ft8listener; -/** - * 监听音频的回调,当结束解码后,调用afterDecode来通知解码的消息 - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.Ft8Message; - -import java.util.ArrayList; - -public interface OnFt8Listen { - /** - * 当开始监听时触发的事件 - * @param utc 当前的UTC时间 - */ - void beforeListen(long utc); - - /** - *当解码结束后触发的事件 - * @param utc 当前周期的UTC时间 - * @param time_sec 此次平均的偏移时间(秒) - * @param sequential 当前的时序 - * @param messages 消息列表 - */ - void afterDecode(long utc,float time_sec,int sequential, ArrayList messages,boolean isDeep); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java deleted file mode 100644 index 5e2a3a5..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.bg7yoz.ft8cn.ft8listener; - -import android.util.Log; - -import com.bg7yoz.ft8cn.FT8Common; -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; -import com.bg7yoz.ft8cn.wave.WaveFileWriter; - -import java.util.ArrayList; - -public class ReBuildSignal { - private static String TAG = "ReBuildSignal"; - static { - System.loadLibrary("ft8cn"); - } - - - public static void subtractSignal(long decoder,A91List a91List){ - for (A91List.A91 a91 : a91List.list) { - doSubtractSignal(decoder,a91.a91,FT8Common.SAMPLE_RATE,a91.freq_hz,a91.time_sec); - } - } - - private static native void doSubtractSignal(long decoder,byte[] payload,int sample_rate - ,float frequency,float time_sec); - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java deleted file mode 100644 index 749e199..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java +++ /dev/null @@ -1,378 +0,0 @@ -package com.bg7yoz.ft8cn.ft8signal; -/** - * 按照FT8协议打包符号。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; - -public class FT8Package { - private static final String TAG = "FT8Package"; - public static final int NTOKENS = 2063592; - public static final int MAX22 = 4194304; - public static final int MAXGRID4 = 32400; - - - private static final String A1 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static final String A2 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static final String A3 = "0123456789"; - private static final String A4 = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - private static final String A5 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"; - - static { - System.loadLibrary("ft8cn"); - } - - - public static byte[] generatePack77_i4(Ft8Message message) { - - String toCall = message.callsignTo.replace("<", "").replace(">", ""); - String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); - int hash12; - if (message.checkIsCQ()) {//如果是CQ,就把自己的呼号的哈希加上 - hash12 = getHash12(fromCall); - } else { - hash12 = getHash12(toCall); - } - if (fromCall.length() > 11) {//非标准呼号的长度不等长于11位 - fromCall = fromCall.substring(0, 11); - } - - byte[] data = new byte[10]; - long n58 = 0; - for (int i = 0; i < fromCall.length(); i++) { - n58 = n58 * 38 + A5.indexOf(fromCall.charAt(i)); - } - //n58=3479529522318088L; - - data[0] = (byte) ((hash12 & 0x00000fff) >> 4); - data[1] = (byte) ((hash12 & 0x0000000f) << 4); - data[1] = (byte) (data[1] | ((n58 & 0x0fff_ffff_ffff_ffffL) >> 54)); - data[2] = (byte) (((n58 & 0x00ff_ffff_ffff_ffffL) >> 54 - 8)); - data[3] = (byte) (((n58 & 0x0000_ffff_ffff_ffffL) >> 54 - 8 - 8)); - data[4] = (byte) (((n58 & 0x0000_00ff_ffff_ffffL) >> 54 - 8 - 8 - 8)); - data[5] = (byte) (((n58 & 0x0000_0000_ffff_ffffL) >> 54 - 8 - 8 - 8 - 8)); - data[6] = (byte) (((n58 & 0x0000_0000_00ff_ffffL) >> 54 - 8 - 8 - 8 - 8 - 8)); - data[7] = (byte) (((n58 & 0x0000_0000_0000_ffffL) >> 54 - 48)); - data[8] = (byte) (((n58 & 0x0000_0000_0000_00ffL) << 2)); - //RRR=1,RR73=2,73=3,""=0 - if (message.checkIsCQ()) { - //data[8]=(byte) (data[8]| );//h1=0,r2=0 i3-4 - data[9] = (byte) 0x60; - } else { - //data[9]=(byte)(data[9]&0xbf);//h1=0; - data[9] = (byte) 0x20; - int r2; - if (message.extraInfo.equals("RRR")) {//r2=1 - data[8] = (byte) (data[8] & 0xfe); - data[9] = (byte) (data[9] | 0x80); - r2 = 1; - } else if (message.extraInfo.equals("RR73")) {//r2=2 - data[8] = (byte) (data[8] | 0x01); - data[9] = (byte) (data[9] | 0x80); - r2 = 2; - } else if (message.extraInfo.equals("73")) {//r2=3 - data[8] = (byte) (data[8] | 0x01); - data[9] = (byte) (data[9] | 0x80); - r2 = 3; - } - } - - return data; - } - - - /** - * i1=1,i1=2,在FT8协议的定义当中,分别是标准消息,和欧盟甚高频(EU VHF),这两个消息的唯一区别是: - * i1=1,消息可以带/R,i1=2,消息是可以带/P - * 所以,这两个消息可以合并为一个类型。 - * - * @param message 原始消息 - * @return - */ - public static byte[] generatePack77_i1(Ft8Message message) { - //Log.e(TAG, "generatePack77_i1: "+message.toString() ); - Log.e(TAG, "generatePack77_i1: message.callsignTo " + message.callsignTo); - Log.e(TAG, "generatePack77_i1: message.callsignFrom " + message.callsignFrom); - String toCall = message.callsignTo.replace("<", "").replace(">", ""); - String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); - - if (message.checkIsCQ()&& message.modifier!=null){//把修饰符加上 - if (message.modifier.length()>0) { - toCall = toCall + " " + message.modifier; - } - } - - //如果以/P 或/R结尾的呼号,要把这个/P /R去掉 - if (toCall.endsWith("/P") || toCall.endsWith("/R")) { - toCall = toCall.substring(0, toCall.length() - 2); - } - - if (fromCall.endsWith("/P") || fromCall.endsWith("/R")) { - fromCall = message.callsignFrom.substring(0, message.callsignFrom.length() - 2); - } - - //当双方都是复合呼号或非标准呼号时(带/的呼号),我的呼号变成标准呼号 - if ((toCall.contains("/")) && fromCall.contains("/")) { - fromCall = fromCall.substring(0, fromCall.indexOf("/")); - } - - - byte[] data = new byte[12]; - data[0] = (byte) ((pack_c28(toCall) & 0x0fffffff) >> 20); - data[1] = (byte) ((pack_c28(toCall) & 0x00ffffff) >> 12); - data[2] = (byte) ((pack_c28(toCall) & 0x0000ffff) >> 4); - data[3] = (byte) ((pack_c28(toCall) & 0x0000000f) << 4); - data[3] = (byte) (data[3] | (pack_r1_p1(message.callsignTo) << 3)); - data[3] = (byte) (data[3] | (pack_c28(fromCall) & 0x00fffffff) >> 25); - - - data[4] = (byte) ((pack_c28(fromCall) & 0x003ffffff) >> 25 - 8); - data[5] = (byte) ((pack_c28(fromCall) & 0x00003ffff) >> 25 - 8 - 8); - data[6] = (byte) ((pack_c28(fromCall) & 0x0000003ff) >> 25 - 8 - 8 - 8); - data[7] = (byte) ((pack_c28(fromCall) & 0x0000000ff) << 7); - - - data[7] = (byte) (data[7] | (pack_r1_p1(message.getCallsignFrom())) << 6); - data[7] = (byte) (data[7] | (pack_R1_g15(message.extraInfo) & 0x0ffff) >> 10); - data[8] = (byte) ((pack_R1_g15(message.extraInfo) & 0x0003fff) >> 2); - data[9] = (byte) ((pack_R1_g15(message.extraInfo) & 0x00000ff) << 6); - data[9] = (byte) (data[9] | (message.i3 & 0x3) << 3); - return data; - } - - /** - * 生成R1+g15数据(网格、或信号报告),实际是16位,包括前面R1。如R-17:R1=1,-17:R1=0 - * - * @param grid4 网格或信号报告 - * @return 返回R1+g15数据 - */ - public static int pack_R1_g15(String grid4) { - if (grid4 == null)// 只有两个呼号,没有信号报告和网格 - { - return MAXGRID4 + 1; - } - if (grid4.length() == 0) {// 只有两个呼号,没有信号报告和网格 - return MAXGRID4 + 1; - } - - // 特殊的报告,RRR,RR73,73 - if (grid4.equals("RRR")) - return MAXGRID4 + 2; - if (grid4.equals("RR73")) - return MAXGRID4 + 3; - if (grid4.equals("73")) - return MAXGRID4 + 4; - - - // 检查是不是标准的4字符网格 - if (grid4.matches("[A-Z][A-Z][0-9][0-9]")) { - int igrid4 = grid4.charAt(0) - 'A'; - igrid4 = igrid4 * 18 + (grid4.charAt(1) - 'A'); - igrid4 = igrid4 * 10 + (grid4.charAt(2) - '0'); - igrid4 = igrid4 * 10 + (grid4.charAt(3) - '0'); - return igrid4; - } - - - // 检查是不是信号报告: +dd / -dd / R+dd / R-dd - // 信号报告在-30到99dB之间 - // 信号报告的正则:[R]?[+-][0-9]{1,2} - String s = grid4; - if (grid4.charAt(0) == 'R') { - s = grid4.substring(1); - int irpt = 35 + Integer.parseInt(s); - return (MAXGRID4 + irpt) | 0x8000; // R1 = 1 - } else { - int irpt = 35 + Integer.parseInt(grid4); - return (MAXGRID4 + irpt); // R1 = 0 - } - - } - - public static byte pack_r1_p1(String callsign) { - String s = callsign.substring(callsign.length() - 2); - if (s.equals("/R") || s.equals("/P")) { - return 1; - } else { - return 0; - } - } - - /** - * 根据呼号生成c28数据。呼号为标准呼号时,不带/R或/P。如果呼号不是标准呼号,用hash22+2063592; - * - * @param callsign 呼号 - * @return c28数据 - */ - public static int pack_c28(String callsign) { - //byte[] data=new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}; - switch (callsign) { - case "DE": - return 0; - case "QRZ": - return 1; - case "CQ": - return 2; - } - - //判断是否有修饰符000-999,A-Z,AA-ZZ,AAA-ZZZ,AAAA-ZZZZ - if (callsign.startsWith("CQ ")&&callsign.length()>3){ - String temp=callsign.substring(3).trim().toUpperCase(); - if (temp.matches("[0-9]{3}")){ - int i=Integer.parseInt(temp); - return i+3; - } - if (temp.matches("[A-Z]{1,4}")) { - - int a0 = 0; - int a1 = 0; - int a2 = 0; - int a3 = 0; - if (temp.length() == 1) {//A-Z - a0= (int) temp.charAt(0) - 65; - return a0+1004; - } - if (temp.length() == 2) {//AA-ZZ - a0 = (int) temp.charAt(0) - 65; - a1 = (int) temp.charAt(1) - 65; - return a0*27+a1+1031; - } - if (temp.length() == 3) {//AAA-ZZZ - a0 = (int) temp.charAt(0) - 65; - a1 = (int) temp.charAt(1) - 65; - a2 = (int) temp.charAt(2) - 65; - return a0*27*27+a1*27+a2+1760; - } - if (temp.length() == 4) {//AAAA-ZZZZ - a0 = (int) temp.charAt(0) - 65; - a1 = (int) temp.charAt(1) - 65; - a2 = (int) temp.charAt(2) - 65; - a3 = (int) temp.charAt(3) - 65; - return a0*27*27*27+a1*27*27+a2*27+a3+21443; - } - } - } - - - //格式化成标准的呼号。6位、第3位带数字 - //c6也可以是非标准呼号。大于6位的都是非标准呼号 - String c6 = formatCallsign(callsign); - //if (c6.length()>6){//生成HASH22+2063592 - if (!GenerateFT8.checkIsStandardCallsign(callsign)) {//生成HASH22+2063592 - return NTOKENS + getHash22(callsign); - } - - //6位呼号取值 - int i0, i1, i2, i3, i4, i5; - i0 = A1.indexOf(c6.substring(0, 1)); - i1 = A2.indexOf(c6.substring(1, 2)); - i2 = A3.indexOf(c6.substring(2, 3)); - i3 = A4.indexOf(c6.substring(3, 4)); - i4 = A4.indexOf(c6.substring(4, 5)); - i5 = A4.indexOf(c6.substring(5, 6)); - - int n28 = i0; - n28 = n28 * 36 + i1; - n28 = n28 * 10 + i2; - n28 = n28 * 27 + i3; - n28 = n28 * 27 + i4; - n28 = n28 * 27 + i5; - - - return NTOKENS + MAX22 + n28; - - } - - public static long pack_c58(String callsign) { - //byte[] data=new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}; - switch (callsign) { - case "DE": - return 0; - case "QRZ": - return 1; - case "CQ": - return 2; - } - - //格式化成标准的呼号。6位、第3位带数字 - String c6 = formatCallsign(callsign); - -// n58 = ((uint64_t) (a77[1] & 0x0F) << 54); //57 ~ 54 : 4 -// n58 |= ((uint64_t) a77[2] << 46); //53 ~ 46 : 12 -// n58 |= ((uint64_t) a77[3] << 38); //45 ~ 38 : 12 -// n58 |= ((uint64_t) a77[4] << 30); //37 ~ 30 : 12 -// n58 |= ((uint64_t) a77[5] << 22); //29 ~ 22 : 12 -// n58 |= ((uint64_t) a77[6] << 14); //21 ~ 14 : 12 -// n58 |= ((uint64_t) a77[7] << 6); //13 ~ 6 : 12 -// n58 |= ((uint64_t) a77[8] >> 2); //5 ~ 0 : 765432 10 - - - //6位呼号取值 - int i0, i1, i2, i3, i4, i5; - i0 = A1.indexOf(c6.substring(0, 1)); - i1 = A2.indexOf(c6.substring(1, 2)); - i2 = A3.indexOf(c6.substring(2, 3)); - i3 = A4.indexOf(c6.substring(3, 4)); - i4 = A4.indexOf(c6.substring(4, 5)); - int n28 = i0; - n28 = n28 * 36 + i1; - n28 = n28 * 10 + i2; - n28 = n28 * 27 + i3; - n28 = n28 * 27 + i4; - - if (c6.length() >= 5) { - i5 = A4.indexOf(c6.substring(5, 6)); - n28 = n28 * 27 + i5; - } - - return NTOKENS + MAX22 + n28; - } - - /** - * 格式化标准的呼号 - * 标准呼号是6位,前缀是1~2个字母+1个数字,后缀对多3个字母 - * 格式化的内容: - * 1.斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ - * 2.几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ - * 3.第2位是数字的呼号,前面用空格补充。A0XYZ->" A0XYZ" - * 4.后缀不足3位的,也要补足空格。BA2BI->"BA2BI " - * - * @param callsign 呼号 - * @return 返回C28值,以int的值来表示 - */ - private static String formatCallsign(String callsign) { - String c6 = callsign; - // 解决斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ - if (callsign.length() > 3 && callsign.substring(0, 4).equals("3DA0") && callsign.length() <= 7) { - c6 = "3D0" + callsign.substring(4); - // 解决几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ - } else if (callsign.length() > 3 && callsign.substring(0, 3).matches("3X[A-Z]") && callsign.length() <= 7) { - c6 = "Q" + callsign.substring(2); - } else { - // 第2位是数字,第3位是字母的,要在前面补充空格:A0XYZ -> " A0XYZ",A6开头的除外 - if (callsign.substring(0, 3).matches("[A-Z][0-9][A-Z]")) { - c6 = " " + callsign; - } - } - - if (c6.length() < 6) {//如果长度不足6位,结尾补充空格 - for (int i = 0; i < 6 - c6.length() + 1; i++) { - c6 = c6 + " "; - } - } - - return c6; - } - - public static native int getHash12(String callsign); - - - public static native int getHash10(String callsign); - - public static native int getHash22(String callsign); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java deleted file mode 100644 index 14626ce..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java +++ /dev/null @@ -1,1106 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 与发射信号有关的类。包括分析通联过程的自动程序。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.media.AudioAttributes; -import android.media.AudioFormat; -import android.media.AudioTrack; -import android.util.Log; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; - -import com.bg7yoz.ft8cn.FT8Common; -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.log.QSLRecord; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.OnUtcTimer; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.ArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class FT8TransmitSignal { - private static final String TAG = "FT8TransmitSignal"; - - private boolean transmitFreeText = false; - private String freeText = "FREE TEXT"; - - private final DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库 - private TransmitCallsign toCallsign;//目标呼号 - public MutableLiveData mutableToCallsign = new MutableLiveData<>(); - - private int functionOrder = 6; - public MutableLiveData mutableFunctionOrder = new MutableLiveData<>();//指令的顺序变化 - private boolean activated = false;//是否处于可以发射的模式 - public MutableLiveData mutableIsActivated = new MutableLiveData<>(); - public int sequential;//发射的时序 - public MutableLiveData mutableSequential = new MutableLiveData<>(); - private boolean isTransmitting = false; - public MutableLiveData mutableIsTransmitting = new MutableLiveData<>();//是否处于发射状态 - public MutableLiveData mutableTransmittingMessage = new MutableLiveData<>();//当前消息的内容 - - //public MutableLiveData currentOrder = new MutableLiveData<>();//当前要发射的指令 - - //******************************************** - //此处的信息是用于保存QSL的 - private long messageStartTime = 0;//消息开始的时间 - private long messageEndTime = 0;//消息结束的时间 - private String toMaidenheadGrid = "";//目标的网格信息 - private int sendReport = 0;//我发送到对方的报告 - private int sentTargetReport = -100;// - - - private int receivedReport = 0;//我接收到的报告 - private int receiveTargetReport = -100;//发送给对方的信号报告 - //******************************************** - private final OnTransmitSuccess onTransmitSuccess;//一般是用于保存QSL数据 - - - //防止播放中止,变量不能放在方法中 - private AudioAttributes attributes = null; - private AudioFormat myFormat = null; - private AudioTrack audioTrack = null; - - public UtcTimer utcTimer; - - - public ArrayList functionList = new ArrayList<>(); - public MutableLiveData> mutableFunctions = new MutableLiveData<>(); - - private final OnDoTransmitted onDoTransmitted;//一般是用于打开关闭PTT - private final ExecutorService doTransmitThreadPool = Executors.newCachedThreadPool(); - private final DoTransmitRunnable doTransmitRunnable = new DoTransmitRunnable(this); - - static { - System.loadLibrary("ft8cn"); - } - - /** - * 发射模块的构造函数,需要两个回调,一个是当发射时(有两个动作,用于打开/关闭PTT),另一个时当成功时(用于保存QSL)。 - * - * @param databaseOpr 数据库 - * @param doTransmitted 当发射前后时的回调 - * @param onTransmitSuccess 当发射成功时的回调 - */ - public FT8TransmitSignal(DatabaseOpr databaseOpr - , OnDoTransmitted doTransmitted, OnTransmitSuccess onTransmitSuccess) { - this.onDoTransmitted = doTransmitted;//用于打开关闭PTT的事件 - this.onTransmitSuccess = onTransmitSuccess;//用于保存QSL的事件 - this.databaseOpr = databaseOpr; - - setTransmitting(false); - setActivated(false); - - - //观察音量设置的变化 - GeneralVariables.mutableVolumePercent.observeForever(new Observer() { - @Override - public void onChanged(Float aFloat) { - if (audioTrack != null) { - audioTrack.setVolume(aFloat); - } - } - }); - - utcTimer = new UtcTimer(FT8Common.FT8_SLOT_TIME_M, false, new OnUtcTimer() { - @Override - public void doHeartBeatTimer(long utc) { - - } - - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public void doOnSecTimer(long utc) { - //超过自动监管时间,就停止 - if (GeneralVariables.isLaunchSupervisionTimeout()) { - setActivated(false); - return; - } - if (UtcTimer.getNowSequential() == sequential && activated) { - if (GeneralVariables.myCallsign.length() < 3) { - //我的呼号不正确,不能发射! - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); - return; - } - doTransmit();//发射动作还是准确按时间来,延迟是音频信号的延迟 - } - } - }); - - - //utcTimer.setTime_sec(GeneralVariables.transmitDelay);//默认晚500毫秒发射,确保上一时序解码结束 - utcTimer.start(); - - } - - /** - * 立即发射 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - public void transmitNow() { - if (GeneralVariables.myCallsign.length() < 3) { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); - return; - } - ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.adjust_call_target) - , toCallsign.callsign)); - - //把信号报告相关的复位 - resetTargetReport(); - - if (UtcTimer.getNowSequential() == sequential) { - if ((UtcTimer.getSystemTime() % 15000) < 2500) { - setTransmitting(false); - doTransmit(); - } - } - } - - //发射信号 - //@RequiresApi(api = Build.VERSION_CODES.N) - public void doTransmit() { - if (!activated) { - return; - } - //检测是不是黑名单频率,WSPR-2的频率,频率=电台频率+声音频率 - if (BaseRigOperation.checkIsWSPR2( - GeneralVariables.band + Math.round(GeneralVariables.getBaseFrequency()))) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.use_wspr2_error) - , BaseRigOperation.getFrequencyAllInfo(GeneralVariables.band))); - setActivated(false); - return; - } - Log.d(TAG, "doTransmit: 开始发射..."); - doTransmitThreadPool.execute(doTransmitRunnable); -// new Thread(new Runnable() { -// @SuppressLint("DefaultLocale") -// @Override -// public void run() { -// //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来 -// if (functionOrder == 1 || functionOrder == 2) {//当消息处于1或2时,说明开始了通联 -// messageStartTime = UtcTimer.getSystemTime(); -// } -// if (messageStartTime == 0) {//如果起始时间没有,就取现在的 -// messageStartTime = UtcTimer.getSystemTime(); -// } -// -// //用于显示将要发射的消息内容 -// Ft8Message msg; -// if (transmitFreeText){ -// msg=new Ft8Message("CQ",GeneralVariables.myCallsign,freeText); -// msg.i3=0; -// msg.n3=0; -// }else { -// msg = getFunctionCommand(functionOrder); -// } -// -// if (onDoTransmitted != null) { -// //此处用于处理PTT等事件 -// onDoTransmitted.onBeforeTransmit(msg, functionOrder); -// } -// //short[] buffer = new short[FT8Common.SAMPLE_RATE * FT8Common.FT8_SLOT_TIME]; -// //79个符号,每个符号0.16秒,采样率12000, -// short[] buffer = new short[(int) (0.5f + -// GenerateFT8.num_tones * GenerateFT8.symbol_period -// * GenerateFT8.sample_rate)]; // 数据信号中的采样数0.5+79*0.16*12000]; -// -// -// isTransmitting = true; -// mutableIsTransmitting.postValue(true); -// -// -// mutableTransmittingMessage.postValue(String.format(" (%.0fHz) %s" -// , GeneralVariables.getBaseFrequency() -// , msg.getMessageText())); -// if (!GenerateFT8.generateFt8(msg -// , GeneralVariables.getBaseFrequency(), buffer)) { -// return; -// } -// ; -// //电台动作可能有要有个延迟时间,所以时间并不一定完全准确 -// try {//给电台一个100毫秒的响应时间 -// Thread.sleep(GeneralVariables.pttDelay);//给PTT指令后,电台一个响应时间,默认100毫秒 -// } catch (InterruptedException e) { -// e.printStackTrace(); -// } -// -// if (onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 -// onDoTransmitted.onAfterGenerate(buffer); -// } -// //播放音频 -// playFT8Signal(buffer); -// } -// }).start(); - mutableFunctions.postValue(functionList); - } - - /** - * 设置呼叫,生成发射消息列表 - * - * @param transmitCallsign 目标呼号 - * @param functionOrder 命令顺序 - * @param toMaidenheadGrid 目标网格 - */ - @SuppressLint("DefaultLocale") - //@RequiresApi(api = Build.VERSION_CODES.N) - public void setTransmit(TransmitCallsign transmitCallsign - , int functionOrder, String toMaidenheadGrid) { - - messageStartTime = 0;//复位起始的时间 - - Log.d(TAG, "准备发射数据..."); - if (GeneralVariables.checkFun1(toMaidenheadGrid)) { - this.toMaidenheadGrid = toMaidenheadGrid; - } else { - this.toMaidenheadGrid = ""; - } - mutableToCallsign.postValue(transmitCallsign);//设定呼叫的目标对象(含报告、时序,频率,呼号) - toCallsign = transmitCallsign;//设定呼叫的目标 - //mutableToCallsign.postValue(toCallsign);//设定呼叫的目标 - - if (functionOrder == -1) {//说明是回复消息 - //此时的toMaidenheadGrid是extraInfo - this.functionOrder = GeneralVariables.checkFunOrderByExtraInfo(toMaidenheadGrid) + 1; - if (this.functionOrder == 6) {//如果已经是73了,就改到消息1 - this.functionOrder = 1; - } - } else { - this.functionOrder = functionOrder;//当前指令的序号 - } - - if (transmitCallsign.frequency == 0) { - transmitCallsign.frequency = GeneralVariables.getBaseFrequency(); - } - if (GeneralVariables.synFrequency) {//如果是同频发送,就与目标呼号频率一致 - setBaseFrequency(transmitCallsign.frequency); - } - - sequential = (toCallsign.sequential + 1) % 2;//发射的时序 - mutableSequential.postValue(sequential);//通知发射时序改变 - generateFun(); - mutableFunctionOrder.postValue(functionOrder); - - } - - @SuppressLint("DefaultLocale") - public void setBaseFrequency(float freq) { - GeneralVariables.setBaseFrequency(freq); - //写到数据中 - databaseOpr.writeConfig("freq", String.format("%.0f", freq), null); - } - - /** - * 根据消息号,生成对应的消息 - * - * @param order 消息号 - * @return FT8消息 - */ - public Ft8Message getFunctionCommand(int order) { - switch (order) { - //发射模式1,BG7YOY BG7YOZ OL50 - case 1: - resetTargetReport();//把给对方的信号报告记录复位成-100 - return new Ft8Message(1, 0, toCallsign.callsign, GeneralVariables.myCallsign - , GeneralVariables.getMyMaidenhead4Grid()); - //发射模式2,BG7YOY BG7YOZ -10 - case 2: - sentTargetReport = toCallsign.snr; - - return new Ft8Message(1, 0, toCallsign.callsign - , GeneralVariables.myCallsign, toCallsign.getSnr()); - //发射模式3,BG7YOY BG7YOZ R-10 - case 3: - sentTargetReport = toCallsign.snr; - return new Ft8Message(1, 0, toCallsign.callsign - , GeneralVariables.myCallsign, "R" + toCallsign.getSnr()); - //发射模式4,BG7YOY BG7YOZ RRR - case 4: - return new Ft8Message(1, 0, toCallsign.callsign - , GeneralVariables.myCallsign, "RR73"); - //发射模式5,BG7YOY BG7YOZ 73 - case 5: - return new Ft8Message(1, 0, toCallsign.callsign - , GeneralVariables.myCallsign, "73"); - //发射模式6,CQ BG7YOZ OL50 - case 6: - resetTargetReport();//把给对方的信号报告,接收到对方的信号报告记录复位成-100 - Ft8Message msg = new Ft8Message(1, 0, "CQ", GeneralVariables.myCallsign - , GeneralVariables.getMyMaidenhead4Grid()); - msg.modifier = GeneralVariables.toModifier; - return msg; - } - - return new Ft8Message("CQ", GeneralVariables.myCallsign - , GeneralVariables.getMyMaidenhead4Grid()); - } - - /** - * 生成指令序列 - */ - public void generateFun() { - //ArrayList functions = new ArrayList<>(); - GeneralVariables.noReplyCount = 0; - functionList.clear(); - for (int i = 1; i <= 6; i++) { - if (functionOrder == 6) {//如果当前的指令序列是6(CQ),那么就只生成一个消息 - functionList.add(new FunctionOfTransmit(6, getFunctionCommand(6), false)); - break; - } else { - functionList.add(new FunctionOfTransmit(i, getFunctionCommand(i), false)); - } - } - mutableFunctions.postValue(functionList); - setCurrentFunctionOrder(functionOrder);//设置当前消息 - } - - /** - * 为了最大限度兼容,把32位浮点转换成16位整型,有些声卡不支持32位的浮点。 - * @param buffer 32位浮点音频 - * @return 16位整型 - */ - private short[] float2Short(float[] buffer) { - short[] temp = new short[buffer.length + 8];//多出8个为0的数据包,是为了兼容QP-7C的RP2040音频判断 - for (int i = 0; i < buffer.length; i++) { - float x = buffer[i]; - if (x > 1.0) - x = 1.0f; - else if (x < -1.0) - x = -1.0f; - temp[i] = (short) (x * 32767.0); - } - return temp; - } - - //private void playFT8Signal(float[] buffer) { - private void playFT8Signal(Ft8Message msg) { - - if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//网络方式就不播放音频了 - Log.d(TAG, "playFT8Signal: 进入网络发射程序,等待音频发送。"); - - - if (onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 - onDoTransmitted.onTransmitByWifi(msg); - } - - - long now = System.currentTimeMillis(); - while (isTransmitting) {//等待音频数据包发送完毕再退出,以触发afterTransmitting - try { - Thread.sleep(1); - long current = System.currentTimeMillis() - now; - if (current > 13000) {//实际发射的时长 - isTransmitting = false; - break; - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - Log.d(TAG, "playFT8Signal: 退出网络音频发送。"); - afterPlayAudio(); - return; - } - - - //进入声卡模式 - float[] buffer; - buffer = GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency() - , GeneralVariables.audioSampleRate); - if (buffer == null) { - afterPlayAudio(); - return; - } - - Log.d(TAG, String.format("playFT8Signal: 准备声卡播放....位数:%s,采样率:%d" - , GeneralVariables.audioOutput32Bit ? "Float32" : "Int16" - , GeneralVariables.audioSampleRate)); - attributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .build(); - - //myFormat = new AudioFormat.Builder().setSampleRate(FT8Common.SAMPLE_RATE) - myFormat = new AudioFormat.Builder().setSampleRate(GeneralVariables.audioSampleRate) - .setEncoding(GeneralVariables.audioOutput32Bit ? //浮点与整型 - AudioFormat.ENCODING_PCM_FLOAT : AudioFormat.ENCODING_PCM_16BIT) - .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); - int mySession = 0; - audioTrack = new AudioTrack(attributes, myFormat - , GeneralVariables.audioOutput32Bit ? GeneralVariables.audioSampleRate * 15 * 4 - : GeneralVariables.audioSampleRate * 15 * 2//浮点与整型 - , AudioTrack.MODE_STATIC - , mySession); - - //区分32浮点和整型 - int writeResult; - if (GeneralVariables.audioOutput32Bit) { - writeResult = audioTrack.write(buffer, 0, buffer.length - , AudioTrack.WRITE_NON_BLOCKING); - } else { - short[] audio_data = float2Short(buffer); - writeResult = audioTrack.write(audio_data, 0, audio_data.length - , AudioTrack.WRITE_NON_BLOCKING); - } - - if (buffer.length > writeResult) { - Log.e(TAG, String.format("播放缓冲区不足:%d--->%d", buffer.length, writeResult)); - } - - //检查写入的结果,如果是异常情况,则直接需要释放资源 - if (writeResult == AudioTrack.ERROR_INVALID_OPERATION - || writeResult == AudioTrack.ERROR_BAD_VALUE - || writeResult == AudioTrack.ERROR_DEAD_OBJECT - || writeResult == AudioTrack.ERROR) { - //出异常情况 - Log.e(TAG, String.format("播放出错:%d", writeResult)); - afterPlayAudio(); - return; - } - audioTrack.setNotificationMarkerPosition(buffer.length); - audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { - @Override - public void onMarkerReached(AudioTrack audioTrack) { - afterPlayAudio(); - } - - @Override - public void onPeriodicNotification(AudioTrack audioTrack) { - - } - }); - if (audioTrack != null) { - audioTrack.play(); - audioTrack.setVolume(GeneralVariables.volumePercent);//设置播放的音量 - } - } - - /** - * 播放完声音后的处理动作。包括回调onAfterTransmit,用于关闭PTT - */ - private void afterPlayAudio() { - if (onDoTransmitted != null) { - onDoTransmitted.onAfterTransmit(getFunctionCommand(functionOrder), functionOrder); - } - isTransmitting = false; - mutableIsTransmitting.postValue(false); - if (audioTrack != null) { - audioTrack.release(); - audioTrack = null; - } - } - - //当通联成功时的动作 - private void doComplete() { - messageEndTime = UtcTimer.getSystemTime();//获取结束的时间 - - //如对方没有网格,就从历史呼号与网格对应表中查找 - toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); - - if (messageStartTime == 0) {//如果起始时间没有,就取现在的 - messageStartTime = UtcTimer.getSystemTime(); - } - - - //从历史记录中查信号报告 - //此处处理信号报告,是因为保存的信号报告经常与实际通联的信号报告不一致。 - //遍历接收到对方的信号报告 - for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { - Ft8Message message = GeneralVariables.transmitMessages.get(i); - if ((GeneralVariables.checkFun3(message.extraInfo) - || GeneralVariables.checkFun2(message.extraInfo)) - && (message.callsignFrom.equals(toCallsign.callsign) - && message.callsignTo.equals(GeneralVariables.myCallsign))) { - receiveTargetReport = getReportFromExtraInfo(message.extraInfo); - break; - } - } - //遍历我发送给对方的信号报告 - for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { - Ft8Message message = GeneralVariables.transmitMessages.get(i); - if ((GeneralVariables.checkFun3(message.extraInfo) - || GeneralVariables.checkFun2(message.extraInfo)) - && (message.callsignTo.equals(toCallsign.callsign) - && message.callsignFrom.equals(GeneralVariables.myCallsign))) { - sentTargetReport = getReportFromExtraInfo(message.extraInfo); - break; - } - } - - - messageEndTime = UtcTimer.getSystemTime(); - if (onDoTransmitted != null) {//用于保存通联记录 - onTransmitSuccess.doAfterTransmit(new QSLRecord( - messageStartTime, - messageEndTime, - GeneralVariables.myCallsign, - GeneralVariables.getMyMaidenhead4Grid(), - toCallsign.callsign, - toMaidenheadGrid, - sentTargetReport != -100 ? sentTargetReport : sendReport, - receiveTargetReport != -100 ? receiveTargetReport : receivedReport,//如果给对方的信号报告是不是-100,就用发给对方的信号报告记录 - "FT8", - GeneralVariables.band, - Math.round(GeneralVariables.getBaseFrequency()) - )); - - GeneralVariables.addQSLCallsign(toCallsign.callsign);//把通联成功的呼号添加到列表中 - ToastMessage.show(String.format("QSO : %s , at %s", toCallsign.callsign - , BaseRigOperation.getFrequencyAllInfo(GeneralVariables.band))); - } - - } - - /** - * 设置当前要发射的指令顺序 - * - * @param order 顺序 - */ - public void setCurrentFunctionOrder(int order) { - functionOrder = order; - for (int i = 0; i < functionList.size(); i++) { - functionList.get(i).setCurrentOrder(order); - } - if (order == 1) { - resetTargetReport();//复位信号报告 - } - if (order == 4 || order == 5) { - updateQSlRecordList(order, toCallsign); - } - mutableFunctions.postValue(functionList); - } - - - /** - * 当目标是复合呼号(非标准信号),JTDX回复可能会缩短 - * - * @param fromCall 对方的呼号 - * @param toCall 我的目标呼号 - * @return 是不是 - */ - private boolean checkCallsignIsCallTo(String fromCall, String toCall) { - if (toCall.contains("/")) {//当对方的呼号在斜线时,JTDX会把/后面的字符去掉 - return toCall.contains(fromCall); - } else { - return fromCall.equals(toCall); - } - } - - /** - * 检查消息中from中有目标呼号的数量。当有目标呼号呼叫我的消息,返回0, - * @param messages 消息列表 - * @return 0:有目标呼叫我的,1:没有任何目标呼号发出的消息,>1:有目标呼号呼叫别人的消息 - */ - private int checkTargetCallMe(ArrayList messages){ - int fromCount=1; - for (int i = messages.size() - 1; i >= 0; i--) { - Ft8Message ft8Message = messages.get(i); - if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 - if (toCallsign == null) { - continue; - } - if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) - && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { - return 0; - } - if (checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)){ - fromCount++;//计数器,from是目标呼号的情况 - } - } - return fromCount; - } - /** - * 检测本消息列表中对方回复消息的序号,如果没有,返回-1 - * - * @param messages 消息列表 - * @return 消息的序号 - */ - private int checkFunctionOrdFromMessages(ArrayList messages) { - for (int i = messages.size() - 1; i >= 0; i--) { - Ft8Message ft8Message = messages.get(i); - if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 - if (toCallsign == null) { - continue; - } - //是双方的呼叫信息 - if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) - && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { - //--TODO ----检查起始时间是不是0,如果是0,补充起始时间。因为有的呼叫会越过第一步 - - //检测是不是对方给我的信号报告 - if (GeneralVariables.checkFun3(ft8Message.extraInfo) - || GeneralVariables.checkFun2(ft8Message.extraInfo)) { - //从消息中取信号报告,如果不正确(-100),那么就取消息中的信号报告 - receivedReport = getReportFromExtraInfo(ft8Message.extraInfo); - receiveTargetReport = receivedReport;//对方给我的信号报告,要保存下来 - if (receivedReport == -100) {//如果不正确,就取消息的报告 - receivedReport = ft8Message.report; - } - } - sendReport = messages.get(i).snr;//把接收到的信号保存下来 - - int order = GeneralVariables.checkFunOrder(ft8Message);//检查消息的序号 - if (order != -1) return order;//说明成功解析出序号 - } - } - - return -1;//说明没找到消息 - } - - /** - * 从扩展消息中获取对方给的信号报告,获取失败,值-100 - * - * @param extraInfo 扩展消息 - * @return 信号报告 - */ - private int getReportFromExtraInfo(String extraInfo) { - String s = extraInfo.replace("R", "").trim(); - try { - return Integer.parseInt(s); - } catch (Exception e) { - return -100; - } - } - - /** - * 检查有没有人CQ我,或我关注的呼号在CQ - * - * @param messages 消息列表 - * @return false=没有符合的消息,TRUE=有符合的消息 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - private boolean checkCQMeOrFollowCQMessage(ArrayList messages) { - //此message是刚刚解码出的消息 - //检查CQ我,且是我呼叫的目标 - for (int i = messages.size() - 1; i >= 0; i--) {//此处是检查有没有CQ我。(TO:ME,且不能是73) - Ft8Message msg = messages.get(i); - if (msg.getSequence() == sequential) {//如果与发射时序相同,不理会 - continue; - } - if (msg.band != GeneralVariables.band) {//如果消息不在相同的波段内,不呼叫 - continue; - } - if (GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//如果是在过滤范围内的呼叫,不理会 - continue; - } - - if ((msg.getCallsignTo().equals(GeneralVariables.myCallsign) - && !GeneralVariables.checkFun5(msg.extraInfo))) {//不能是73 - //设置发射之前,确定消息的序号,避免从头开始 - setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz - , msg.getSequence(), msg.snr) - , GeneralVariables.checkFunOrder(msg) + 1 - , msg.extraInfo); - return true; - } - } - - - //如果不自动呼叫我关注的消息,就退出 - if (!GeneralVariables.autoCallFollow) { - return false; - } - - if (toCallsign == null) { - return false; - } - //当已经有目标呼号的时候,不对关注的呼号做反应 - if (toCallsign.haveTargetCallsign()) { - return false; - } - - //我关注的呼号次之,!!!到关注的消息列表中找 - //此处是检查关注的呼号在CQ。(TO:CQ,且不能本次通联能成功的呼号) - for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { - Ft8Message msg = GeneralVariables.transmitMessages.get(i); - if (msg.getSequence() == sequential) {//如果与发射时序相同,不理会 - continue; - } - if (msg.band != GeneralVariables.band) {//如果消息不在相同的波段内,不呼叫 - continue; - } - - //处于CQ,FROM是我的关注呼号,并且不在通联成功的呼号列表中 - if ((msg.checkIsCQ()//在CQ - && ((GeneralVariables.autoCallFollow && GeneralVariables.autoFollowCQ)//自动呼叫CQ - || GeneralVariables.callsignInFollow(msg.getCallsignFrom()))//是我关注的 - && !GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())//之前没有联通成功过 - && !msg.callsignFrom.equals(GeneralVariables.myCallsign))) {//不是我自己 - - resetTargetReport(); - setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz - , msg.getSequence(), msg.snr), 1, msg.extraInfo); - - return true; - } - } - - return false; - - } - - - public void updateQSlRecordList(int order, TransmitCallsign toCall) { - if (toCall == null) return; - if (toCall.callsign.equals("CQ")) return; - - QSLRecord record = GeneralVariables.qslRecordList.getRecordByCallsign(toCall.callsign); - if (record == null) { - toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); - record = GeneralVariables.qslRecordList.addQSLRecord(new QSLRecord( - messageStartTime, - messageEndTime, - GeneralVariables.myCallsign, - GeneralVariables.getMyMaidenhead4Grid(), - toCallsign.callsign, - toMaidenheadGrid, - sentTargetReport != -100 ? sentTargetReport : sendReport, - receiveTargetReport != -100 ? receiveTargetReport : receivedReport,//如果给对方的信号报告是不是-100,就用发给对方的信号报告记录 - "FT8", - GeneralVariables.band, - Math.round(GeneralVariables.getBaseFrequency() - ))); - } - //根据消息序列更新内容 - switch (order) { - case 1://更新网格,和对方消息的SNR - record.setToMaidenGrid(toMaidenheadGrid); - record.setSendReport(sentTargetReport != -100 ? sentTargetReport : sendReport); - GeneralVariables.qslRecordList.deleteIfSaved(record); - break; - - case 2://更新对方返回的信号报告,和对方的信号报告 - case 3: - record.setSendReport(sentTargetReport != -100 ? sentTargetReport : sendReport); - record.setReceivedReport(receiveTargetReport != -100 ? receiveTargetReport : receivedReport); - GeneralVariables.qslRecordList.deleteIfSaved(record); - break; - - //当RR73或73的状态下,就保存日志。 - case 4: - case 5: - if (!record.saved) { - doComplete();//保存到数据库 - record.saved = true; - } - - break; - } - - } - - /** - * 从关注列表解码的消息中,此处是变化发射程序的入口 - * - * @param msgList 消息列表 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - public void parseMessageToFunction(ArrayList msgList) { - if (GeneralVariables.myCallsign.length() < 3) { - return; - } - if (msgList.size() == 0) return;//没有消息解析,返回 - - if (msgList.get(0).getSequence() == sequential) { - return; - } - ArrayList messages =new ArrayList<>(msgList);//防止线程冲突 - - - int newOrder = checkFunctionOrdFromMessages(messages);//检查消息中对方回复的消息序号,-1为没有收到 - if (newOrder != -1) {//如果有消息序号,说明有回应,复位错误计数器 - GeneralVariables.noReplyCount = 0; - } - - //更新一下通联的列表检查是不是在通联列表中,如果没有记录下来,就保存 - updateQSlRecordList(newOrder, toCallsign); - - - // 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) - // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 - // 或我是RR73(4),且对方开始呼叫别人了,解决RR73卡死的问题 - if (newOrder == 5 - || (functionOrder == 5 && newOrder == -1)// 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) - || (functionOrder == 4 && - (GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit * 2) - && (GeneralVariables.noReplyLimit > 0)) // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 - || (functionOrder ==4 && checkTargetCallMe(messages)>1) - ) { // 或我是RR73(4),且对方开始呼叫别人了 - //doComplete();//做保存的动作 - //进入到CQ状态 - resetToCQ(); - - //加入检查消息中有没有呼号我的,或关注的呼号在CQ - checkCQMeOrFollowCQMessage(messages); - setCurrentFunctionOrder(functionOrder);//设置当前消息 - mutableFunctionOrder.postValue(functionOrder); - return; - } - - - if (newOrder != -1) {//说明收到消息,且未完成通联 - //原来是newOrder == 1,但有的时候,对方直接给信号报告,也就是消息2. - if (newOrder == 1 || newOrder == 2) {//说明是别人第一次回复我 - resetTargetReport();//把信号报告复位一下 - generateFun(); - } - - functionOrder = newOrder + 1;//执行下一个序号的消息 - mutableFunctions.postValue(functionList); - mutableFunctionOrder.postValue(functionOrder); - setCurrentFunctionOrder(functionOrder);//设置当前消息 - return; - } - - - //到此位置,我还没有在6号消息状态,检查看有没有人呼叫我 - // 2022-09-22如果这时有人呼叫我,或自动跟踪状态,我就设置新的发射消息列表 - if (checkCQMeOrFollowCQMessage(messages)) { - return; - } - - - //到此位置,说明没有收到回复的消息 - //之明如果我是在CQ,那么newOrder必然是-1 - if (functionOrder == 6) {//我处于CQ状态 - checkCQMeOrFollowCQMessage(messages); - return; - } - - - //到此位置,说明没有回应,错误次数要加1,弱信号检测不记无回应 - if (!messages.get(0).isWeakSignal) { - GeneralVariables.noReplyCount++; - } - //如果超出无反应限定值,复位到CQ状态 - if ((GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit) && (GeneralVariables.noReplyLimit > 0)) { - //检查关注消息列表,如果没有新的CQ,就进入到CQ状态,如果有,就转入到呼叫新的目标。 - if (!getNewTargetCallsign(messages)) {//检查关注列表中的CQ消息,如果有新的目标,返回TRUE; - functionOrder = 6; - toCallsign.callsign = "CQ"; - } - generateFun(); - setCurrentFunctionOrder(functionOrder);//设置当前消息 - mutableToCallsign.postValue(toCallsign); - mutableFunctionOrder.postValue(functionOrder); - - } - - } - - /** - * 检查关注列表中,有没有正在CQ的消息,且不是我现在的目标呼号 - * - * @param messages 关注的消息列表 - * @return 目标呼号,没有返回NULL - */ - public boolean getNewTargetCallsign(ArrayList messages) { - if (toCallsign == null) return false; - for (int i = messages.size() - 1; i >= 0; i--) { - Ft8Message ft8Message = messages.get(i); - if (ft8Message.band != GeneralVariables.band) {//如果消息不在相同的波段内,不理会 - continue; - } - //不是CQ,不理会 - if (!ft8Message.checkIsCQ()) { - continue; - } - //不是当前的目标呼号,且之前没有通联成功过 - if ((!ft8Message.getCallsignFrom().equals(toCallsign.callsign) - && (!GeneralVariables.checkQSLCallsign(ft8Message.getCallsignFrom())))) //之前没有联通成功过 - { - functionOrder = 1; - toCallsign.callsign = ft8Message.getCallsignFrom(); - return true; - } - - - } - return false; - } - - public boolean isSynFrequency() { - return GeneralVariables.synFrequency; - } - - - public boolean isActivated() { - return activated; - } - - public void setActivated(boolean activated) { - this.activated = activated; - if (!this.activated) {//强制关闭发射 - setTransmitting(false); - } - mutableIsActivated.postValue(activated); - } - - public boolean isTransmitting() { - return isTransmitting; - } - - public void setTransmitting(boolean transmitting) { - if (GeneralVariables.myCallsign.length() < 3 && transmitting) { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); - return; - } - - if (!transmitting) {//停止发射 - if (audioTrack != null) { - if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) { - audioTrack.pause(); - } - if (onDoTransmitted != null) {//通知一下,已经不发射了 - onDoTransmitted.onAfterTransmit(getFunctionCommand(functionOrder), functionOrder); - } - } - } - - mutableIsTransmitting.postValue(transmitting); - isTransmitting = transmitting; - } - - /** - * 复位发射程序到6,时序也会改变 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - public void restTransmitting() { - if (GeneralVariables.myCallsign.length() < 3) { - return; - } - //要判断我的呼号类型,才能确定i3n3 !!! - int i3 = GenerateFT8.checkI3ByCallsign(GeneralVariables.myCallsign); - setTransmit(new TransmitCallsign(i3, 0, "CQ", UtcTimer.getNowSequential()) - , 6, ""); - - } - - /** - * 把给对方的信号记录复位成-100; - */ - public void resetTargetReport() { - receiveTargetReport = -100; - sentTargetReport = -100; - } - - /** - * 复位发射程序到6,不会改变时序 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - public void resetToCQ() { - resetTargetReport(); - if (toCallsign == null) { - //要判断我的呼号类型,才能确定i3n3 !!! - int i3 = GenerateFT8.checkI3ByCallsign(GeneralVariables.myCallsign); - setTransmit(new TransmitCallsign(i3, 0, "CQ", (UtcTimer.getNowSequential() + 1) % 2) - , 6, ""); - } else { - functionOrder = 6; - toCallsign.callsign = "CQ"; - mutableToCallsign.postValue(toCallsign);//设定呼叫的目标 - generateFun(); - } - } - - /** - * 设置发射时间延迟,这个延迟时间,也是给上一个周期解码的一个时间 - * - * @param sec 毫秒 - */ - public void setTimer_sec(int sec) { - utcTimer.setTime_sec(sec); - } - - public boolean isTransmitFreeText() { - return transmitFreeText; - } - - public void setFreeText(String freeText) { - this.freeText = freeText; - } - - public void setTransmitFreeText(boolean transmitFreeText) { - this.transmitFreeText = transmitFreeText; - if (transmitFreeText) { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.trans_free_text_mode)); - } else { - ToastMessage.show((GeneralVariables.getStringFromResource(R.string.trans_standard_messge_mode))); - } - } - - - private static class DoTransmitRunnable implements Runnable { - FT8TransmitSignal transmitSignal; - - public DoTransmitRunnable(FT8TransmitSignal transmitSignal) { - this.transmitSignal = transmitSignal; - } - - @SuppressLint("DefaultLocale") - @Override - public void run() { - //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来 - if (transmitSignal.functionOrder == 1 || transmitSignal.functionOrder == 2) {//当消息处于1或2时,说明开始了通联 - transmitSignal.messageStartTime = UtcTimer.getSystemTime(); - } - if (transmitSignal.messageStartTime == 0) {//如果起始时间没有,就取现在的 - transmitSignal.messageStartTime = UtcTimer.getSystemTime(); - } - - //用于显示将要发射的消息内容 - Ft8Message msg; - if (transmitSignal.transmitFreeText) { - msg = new Ft8Message("CQ", GeneralVariables.myCallsign, transmitSignal.freeText); - msg.i3 = 0; - msg.n3 = 0; - } else { - msg = transmitSignal.getFunctionCommand(transmitSignal.functionOrder); - } - msg.modifier = GeneralVariables.toModifier; - - if (transmitSignal.onDoTransmitted != null) { - //此处用于处理PTT等事件 - transmitSignal.onDoTransmitted.onBeforeTransmit(msg, transmitSignal.functionOrder); - } - - transmitSignal.isTransmitting = true; - transmitSignal.mutableIsTransmitting.postValue(true); - - - transmitSignal.mutableTransmittingMessage.postValue(String.format(" (%.0fHz) %s" - , GeneralVariables.getBaseFrequency() - , msg.getMessageText())); - //生成信号 -// float[] buffer=GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency()); -// if (buffer==null) { -// return; -// } - - //电台动作可能有要有个延迟时间,所以时间并不一定完全准确 - try {//给电台一个100毫秒的响应时间 - Thread.sleep(GeneralVariables.pttDelay);//给PTT指令后,电台一个响应时间,默认100毫秒 - } catch (InterruptedException e) { - e.printStackTrace(); - } - -// if (transmitSignal.onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 -// transmitSignal.onDoTransmitted.onAfterGenerate(buffer); -// } - //播放音频 - //transmitSignal.playFT8Signal(buffer); - transmitSignal.playFT8Signal(msg); - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java deleted file mode 100644 index 5b0aeb7..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * FT8通联的6步 - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.Ft8Message; - -public class FunctionOfTransmit { - private int functionOrder;//消息的序号 - private String functionMessage;//消息内容 - private boolean completed;//是否完成 - private boolean isCurrentOrder;//是不是当前要发射的消息 - private Ft8Message ft8Message; - -// /** -// * 老的发送消息方法 -// * @param functionOrder 消息序号 -// * @param functionMessage 消息内容 -// * @param completed 是否结束 -// */ -// @Deprecated -// public FunctionOfTransmit(int functionOrder, String functionMessage, boolean completed) { -// this.functionOrder = functionOrder; -// this.functionMessage = functionMessage; -// this.completed = completed; -// } - - /** - * 新版发送消息方法 - * @param functionOrder 消息序号 - * @param message FT8消息 - * @param completed 是否结束 - */ - public FunctionOfTransmit(int functionOrder, Ft8Message message, boolean completed) { - this.functionOrder = functionOrder; - ft8Message=message; - this.completed = completed; - this.functionMessage = message.getMessageText(); - } - - public int getFunctionOrder() { - return functionOrder; - } - - public void setFunctionOrder(int functionOrder) { - this.functionOrder = functionOrder; - } - - public String getFunctionMessage() { - return functionMessage; - } - - public void setFunctionMessage(String functionMessage) { - this.functionMessage = functionMessage; - } - - public boolean isCompleted() { - return completed; - } - - public void setCompleted(boolean completed) { - this.completed = completed; - } - - public boolean isCurrentOrder() { - return isCurrentOrder; - } - - public void setCurrentOrder(int currentOrder) { - isCurrentOrder = currentOrder==functionOrder; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java deleted file mode 100644 index 675252b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java +++ /dev/null @@ -1,259 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 生成FT8音频信号的类。音频数据是32位的浮点数组。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.ft8signal.FT8Package; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -public class GenerateFT8 { - private static final String TAG = "GenerateFT8"; - private static final int FTX_LDPC_K = 91; - public static final int FTX_LDPC_K_BYTES = (FTX_LDPC_K + 7) / 8; - private static final int FT8_NN = 79; - private static final float FT8_SYMBOL_PERIOD = 0.160f; - private static final float FT8_SYMBOL_BT = 2.0f; - private static final float FT8_SLOT_TIME = 15.0f; - private static final int Ft8num_samples = 15 * 12000; - private static final float M_PI = 3.14159265358979323846f; - - public static final int num_tones = FT8_NN;//符号数量:FT8是79个,FT4是105个。 - public static final float symbol_period = FT8_SYMBOL_PERIOD;//FT8_SYMBOL_PERIOD=0.160f - private static final float symbol_bt = FT8_SYMBOL_BT;//FT8_SYMBOL_BT=2.0f - private static final float slot_time = FT8_SLOT_TIME;//FT8_SLOT_TIME=15f - //public static int sample_rate = 48000;//采样率 - //public static int sample_rate = 12000;//采样率 - - - static { - System.loadLibrary("ft8cn"); - } - - - public static int checkI3ByCallsign(String callsign) { - String substring = callsign.substring(callsign.length() - 2); - if (substring.equals("/P")) { - if (callsign.length() <= 8) { - return 2;//i3=2消息 - } else { - return 4;//说明时非标准呼号 - } - } - if (substring.equals("/R")) { - if (callsign.length() <= 8) { - return 1;//i3=2消息 - } else { - return 4;//说明时非标准呼号 - } - } - if (callsign.contains("/")) {//除了/P /R以外,其余的都是非标准呼号 - return 4; - } - if (callsign.length() > 6) {//呼号大于6位,也是非标准呼号 - return 4; - } - if (callsign.length() == 0) {//没有呼号,就是自由文本 - return 0; - } - return 1; - } - - public static String byteToBinString(byte[] data) { - if (data == null) { - return ""; - } - StringBuilder string = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - string.append(String.format(",%8s", Integer.toBinaryString(data[i] & 0xff)).replace(" ", "0")); - } - return string.toString(); - } - - public static String byteToHexString(byte[] data) { - StringBuilder string = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - string.append(String.format(",%02X", data[i])); - } - return string.toString(); - } - - - /** - * 检查是不是标准呼号 - * - * @param callsign 呼号 - * @return 是不是 - */ - public static boolean checkIsStandardCallsign(String callsign) { - String temp; - if (callsign.endsWith("/P") || callsign.endsWith("/R")){ - temp=callsign.substring(0,callsign.length()-2); - }else { - temp=callsign; - } - // Log.e(TAG, "checkIsStandardCallsign: 呼号:"+temp.matches("[a-zA-Z0-9]?[a-zA-Z][0-9][a-zA-Z][a-zA-Z0-9]?[a-zA-Z]") ); - //return temp.matches("[A-Z0-9]?[A-Z][0-9][A-Z][A-Z0-9]?[A-Z]?"); - - return temp.matches("[A-Z0-9]?[A-Z0-9][0-9][A-Z][A-Z0-9]?[A-Z]?"); - - //FT8的认定:标准业余呼号由一个或两个字符的前缀组成,其中至少一个必须是字母,后跟一个十进制数字和最多三个字母的后缀。 - } - - /** - * 检查是不是信号报告 - * - * @param extraInfo 扩展消息 - * @return 是不是 - */ - private static boolean checkIsReport(String extraInfo) { - if (extraInfo.equals("73") || extraInfo.equals("RRR") - || extraInfo.equals("RR73")||extraInfo.equals("")) { - return false; - } - return !extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]"); - } - - public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate){ - return generateFt8(msg,frequency,sample_rate,true); - } - - /** - * 生成FT8信号 - * @param msg 消息 - * @param frequency 频率 - * @param sample_rate 采样率 - * @param hasModifier 是否有修饰符 - * @return - */ - public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate,boolean hasModifier) { - if (msg.callsignFrom.length()<3){ - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); - return null; - } - // 首先,将文本数据打包为二进制消息,共12个字节 - byte[] packed = new byte[FTX_LDPC_K_BYTES]; - //把"<>"去掉 - msg.callsignTo = msg.callsignTo.replace("<", "").replace(">", ""); - msg.callsignFrom = msg.callsignFrom.replace("<", "").replace(">", ""); - if (hasModifier) { - msg.modifier = GeneralVariables.toModifier;//修饰符 - }else { - msg.modifier=""; - } - //msg.callsignTo="CQ AzCz"; - - //判定用非标准呼号i3=4的条件: - //1.FROMCALL为非标准呼号 ,且 符合2或3 - //2.扩展消息时 网格、RR73,RRR,73 - //3.CQ,QRZ,DE - - - - if (msg.i3 != 0) {//目前只支持i3=1,i3=2,i3=4,i3=0 && n3=0 - if (!checkIsStandardCallsign(msg.callsignFrom) - && (!checkIsReport(msg.extraInfo) || msg.checkIsCQ())) { - msg.i3 = 4; - } else if (msg.callsignFrom.endsWith("/P")||(msg.callsignTo.endsWith("/P"))) { - msg.i3 = 2; - } else { - msg.i3 = 1; - } - } - - if (msg.i3 == 1 || msg.i3 == 2) { - packed = FT8Package.generatePack77_i1(msg); - } else if (msg.i3 == 4) {//说明是非标准呼号 - packed = FT8Package.generatePack77_i4(msg); - } else { - packFreeTextTo77(msg.getMessageText(), packed); - } - - return generateFt8ByA91(packed,frequency,sample_rate); - - /* - // 其次,将二进制消息编码为FSK音调序列,79个字节 - byte[] tones = new byte[num_tones]; // 79音调(符号)数组 - //此处是88个字节(91+7)/8,可以使用a91生成音频 - ft8_encode(packed, tones); - - // 第三,将FSK音调转换为音频信号b - - - int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 - - //float[] signal = new float[Ft8num_samples]; - float[] signal = new float[num_samples]; - - //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 - //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - signal[i] = 0; - } - - // 用79个字节符号,生成FT8音频 - synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - if (signal[i]>1.0||signal[i]<-1.0){ - Log.e(TAG, "generateFt8: "+signal[i] ); - } - } - return signal; - */ - } - - public static float[] generateFt8ByA91(byte[] a91, float frequency,int sample_rate){ - byte[] tones = new byte[num_tones]; // 79音调(符号)数组 - //此处是12个字节(91+7)/8,可以使用a91生成音频 - ft8_encode(a91, tones); - - // 第三,将FSK音调转换为音频信号b - - - int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 - - - - //int num_silence = (int) ((slot_time * sample_rate - num_samples) / 2); // 两端填充静音到15秒(15*12000-num_samples)/2(1.18秒的样本数) - //int num_total_samples = num_silence + num_samples + num_silence; // 填充信号中的样本数2.36秒+12.64秒=15秒的样本数 - - //float[] signal = new float[Ft8num_samples]; - float[] signal = new float[num_samples]; - - //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 - //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - signal[i] = 0; - } - - // 用79个字节符号,生成FT8音频 - synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); -// for (int i = 0; i < num_samples; i++)//把数据全部静音。 -// { -// if (signal[i]>1.0||signal[i]<-1.0){ -// Log.e(TAG, "generateFt8: "+signal[i] ); -// } -// } - return signal; - } - - - private static native int packFreeTextTo77(String msg, byte[] c77); - - private static native int pack77(String msg, byte[] c77); - - private static native void ft8_encode(byte[] payload, byte[] tones); - - private static native void synth_gfsk(byte[] symbols, int n_sym, float f0, - float symbol_bt, float symbol_period, - int signal_rate, float[] signal, int offset); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java deleted file mode 100644 index c519e83..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 发射的回调 - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.Ft8Message; - -public interface OnDoTransmitted { - void onBeforeTransmit(Ft8Message message,int functionOder); - void onAfterTransmit(Ft8Message message, int functionOder); - void onTransmitByWifi(Ft8Message message); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java deleted file mode 100644 index e72c664..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 发射结束后的回调 - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.log.QSLRecord; - -public interface OnTransmitSuccess { - void doAfterTransmit(QSLRecord qslRecord); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java deleted file mode 100644 index 257a3a9..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; - -/** - * 记录QSO的类,用于保存数据库。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class QSLRecord { - private long startTime;//起始时间 - private long endTime;//结束时间 - - private String myCallsign;//我的呼号 - private String myMaidenGrid;//我的网格 - private String toCallsign;//对方的呼号 - private String toMaidenGrid;//对方的网格 - private int sendReport;//对方收到我的报告(也就是我发送的信号强度) - private int receivedReport;//我收到对方的报告(也就是SNR) - private String mode="FT8"; - - private long bandFreq;//发射的波段 - private int frequency;//发射的频率 - - - public QSLRecord(long startTime, long endTime, String myCallsign, String myMaidenGrid - , String toCallsign, String toMaidenGrid, int sendReport, int receivedReport - , String mode, long bandFreq, int frequency) { - this.startTime = startTime; - this.endTime = endTime; - this.myCallsign = myCallsign; - this.myMaidenGrid = myMaidenGrid; - this.toCallsign = toCallsign; - this.toMaidenGrid = toMaidenGrid; - this.sendReport = sendReport; - this.receivedReport = receivedReport; - this.mode = mode; - this.bandFreq = bandFreq; - this.frequency = frequency; - } - - @Override - public String toString() { - return "QSLRecord{" + - "startTime=" + startTime + - ", endTime=" + endTime + - ", myCallsign='" + myCallsign + '\'' + - ", myMaidenGrid='" + myMaidenGrid + '\'' + - ", toCallsign='" + toCallsign + '\'' + - ", toMaidenGrid='" + toMaidenGrid + '\'' + - ", sendReport=" + sendReport + - ", receivedReport=" + receivedReport + - ", mode='" + mode + '\'' + - ", bandFreq=" + bandFreq + - ", frequency=" + frequency + - '}'; - } - - public long getEndTime() { - return endTime; - } - - public String getToCallsign() { - return toCallsign; - } - - public String getToMaidenGrid() { - return toMaidenGrid; - } - - public String getMode() { - return mode; - } - - public long getBandFreq() { - return bandFreq; - } - - public int getFrequency() { - return frequency; - } - - public long getStartTime() { - return startTime; - } - - public String getMyCallsign() { - return myCallsign; - } - - public String getMyMaidenGrid() { - return myMaidenGrid; - } - - public int getSendReport() { - return sendReport; - } - - public int getReceivedReport() { - return receivedReport; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java deleted file mode 100644 index 538e059..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 通联记录的列表 - * @author BGY70Z - * @date 2023-03-20 - */ - -import com.bg7yoz.ft8cn.log.QSLRecord; - -import java.util.ArrayList; - -public class QslRecordList extends ArrayList { - - /** - * 根据呼号查是否有通联记录 - * @param callsign 呼号 - * @return 记录,没有则为空 - */ - public QSLRecord getRecordByCallsign(String callsign){ - for (int i = this.size()-1; i >=0 ; i--) { - if (this.get(i).getToCallsign().equals(callsign)){ - return this.get(i); - } - } - return null; - } - - /** - * 按照呼号查找,是否有通联记录,且保存过。如果没有记录,视作没保存过。 - * @param callsign 呼号 - * @return 是否保存过 - */ - public boolean getSavedRecByCallsign(String callsign){ - QSLRecord record=getRecordByCallsign(callsign); - if (record==null){ - return false; - }else { - return record.saved; - } - } - - /** - * 添加通联过的记录,如果已经存在,就更新记录 - * @param record 通联记录 - * @return 通联记录 - */ - public QSLRecord addQSLRecord(QSLRecord record){ - if (record.getToCallsign().equals("CQ")) return null; - //清除已经保存过的通联记录 - //for (int i = this.size()-1; i >=0 ; i--) { - // if (this.get(i).getToCallsign().equals(record.getToCallsign())){ - // if (this.get(i).saved){ - // this.remove(i); - // } - // } - //} - //找一下看有没有已经在列表中,但还没有保存的记录 - QSLRecord oldRecord= getRecordByCallsign(record.getToCallsign()); - if (oldRecord==null){ - this.add(record); - return record; - }else { - oldRecord.update(record); - } - return oldRecord; - } - - /** - * 删除已经保存过的呼号 - * @param record - */ - public void deleteIfSaved(QSLRecord record){ - //清除已经保存过的通联记录 - for (int i = this.size()-1; i >=0 ; i--) { - if (this.get(i).getToCallsign().equals(record.getToCallsign())){ - if (this.get(i).saved){ - this.remove(i); - } - } - } - } - - public String toHTML(){ - StringBuilder html=new StringBuilder(); - for (int i = 0; i < this.size(); i++) { - if (i%2==0) { - html.append(""); - html.append(String.format("%s", this.get(i).toHtmlString())); - html.append("
\n\n"); - }else { - html.append(">"); - html.append(String.format("%s", this.get(i).toHtmlString())); - html.append("
\n\n"); - } - - } - return html.toString(); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java deleted file mode 100644 index fa8f803..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.bg7yoz.ft8cn.ft8transmit; -/** - * 呼叫过程所记录的呼号信息 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; - -public class TransmitCallsign { - private static final String TAG="TransmitCallsign"; - public String callsign; - public float frequency; - public int sequential; - public int snr; - public int i3; - public int n3; - public String dxcc; - public int cqZone; - public int itu; - - public TransmitCallsign(int i3,int n3,String callsign, int sequential) { - this.callsign = callsign; - this.sequential = sequential; - this.i3=i3; - this.n3=n3; - } - - public TransmitCallsign(int i3,int n3,String callsign, float frequency - , int sequential, int snr) { - this.callsign = callsign; - this.frequency = frequency; - this.sequential = sequential; - this.snr = snr; - this.i3=i3; - this.n3=n3; - - } - - /** - * 当目标呼号为空,或CQ,说明没有目标呼号 - * @return 是否有目标呼号 - */ - public boolean haveTargetCallsign(){ - if (callsign==null){ - return false; - } - return !callsign.equals("CQ"); - } - - @SuppressLint("DefaultLocale") - public String getSnr(){ - if (snr>0){ - return String.format("+%d",snr); - }else { - return String.format("%d",snr); - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java deleted file mode 100644 index 98da410..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.bg7yoz.ft8cn.grid_tracker; -/** - * 网格追踪中每个连线的窗口界面。包含各类型分区图标。与我有关的通联,文字是红色的。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.graphics.Paint; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.OverlayWithIW; -import org.osmdroid.views.overlay.infowindow.InfoWindow; - -public class GridInfoWindow extends InfoWindow { - public static final int UNDEFINED_RES_ID = 0; - - private final TextView titleView; - private final TextView descriptionView; - private final TextView subDescriptionView; - - - @SuppressLint("UseCompatLoadingForDrawables") - public GridInfoWindow(int layoutResId, MapView mapView, Ft8Message msg) { - super(layoutResId, mapView); - //setResIds(mapView.getContext()); - titleView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_title); - descriptionView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_description); - subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_subdescription); - ImageView fromDxccImage = (ImageView) this.mView.findViewById(R.id.track_from_dxcc_image); - ImageView fromItuImage = (ImageView) this.mView.findViewById(R.id.track_from_itu_image); - ImageView fromCqImage = (ImageView) this.mView.findViewById(R.id.track_from_cq_image); - ImageView toDxccImage = (ImageView) this.mView.findViewById(R.id.track_to_dxcc_image); - ImageView toItuImage = (ImageView) this.mView.findViewById(R.id.track_to_itu_image); - ImageView toCqImage = (ImageView) this.mView.findViewById(R.id.track_to_cq_image); - ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerGridInfoConstraintLayout); - - if (!msg.fromDxcc) fromDxccImage.setVisibility(View.GONE); - if (!msg.fromItu) fromItuImage.setVisibility(View.GONE); - if (!msg.fromCq) fromCqImage.setVisibility(View.GONE); - if (!msg.toDxcc) toDxccImage.setVisibility(View.GONE); - if (!msg.toItu) toItuImage.setVisibility(View.GONE); - if (!msg.toCq) toCqImage.setVisibility(View.GONE); - - - //查是不是在本波段内通联成功过的呼号 - if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//如果在数据库中,划线 - titleView.setPaintFlags( - titleView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else {//如果不在数据库中,去掉划线 - titleView.setPaintFlags( - titleView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); - } - boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom()); - - //是否有与我呼号有关的消息 - if (msg.inMyCall()) { - layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); - titleView.setTextColor(mapView.getResources().getColor( - R.color.message_in_my_call_text_color)); - } else if (otherBandIsQso) { - //设置在别的波段通联过的消息颜色 - titleView.setTextColor(mapView.getResources().getColor( - R.color.fromcall_is_qso_text_color)); - } else { - titleView.setTextColor(mapView.getResources().getColor( - R.color.message_text_color)); - } - - - this.mView.setOnTouchListener(new View.OnTouchListener() { - public boolean onTouch(View v, MotionEvent e) { - if (e.getAction() == 1) { - GridInfoWindow.this.close(); - } - return true; - } - }); - } - - - @Override - public void onOpen(Object item) { - OverlayWithIW overlay = (OverlayWithIW) item; - String title = overlay.getTitle(); - if (title == null) { - title = ""; - } - - if (this.mView == null) { - Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); - } else { - titleView.setText(title); - String snippet = overlay.getSnippet(); - //Spanned snippetHtml = Html.fromHtml(snippet); - descriptionView.setText(snippet); - String subDesc = overlay.getSubDescription(); - subDescriptionView.setText(subDesc); - - } - } - - @Override - public void onClose() { - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java deleted file mode 100644 index 1b352be..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.bg7yoz.ft8cn.grid_tracker; -/** - * 网格追踪中Marker(网格)的消息窗口。包括各分区的图标,点击呼叫的按钮。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.graphics.Paint; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.OverlayWithIW; -import org.osmdroid.views.overlay.infowindow.InfoWindow; - -public class GridMarkerInfoWindow extends InfoWindow { - public static final int UNDEFINED_RES_ID = 0; - - private final TextView titleView; - private final TextView descriptionView; - private final TextView subDescriptionView; - private final MainViewModel mainViewModel; - private Ft8Message msg; - - - @SuppressLint("UseCompatLoadingForDrawables") - public GridMarkerInfoWindow(MainViewModel mainViewModel,int layoutResId, MapView mapView, Ft8Message msg) { - super(layoutResId, mapView); - this.mainViewModel=mainViewModel; - this.msg=msg; - //setResIds(mapView.getContext()); - titleView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_title); - descriptionView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_description); - subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_subdescription); - ImageView fromDxccImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_dxcc_image); - ImageView fromItuImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_itu_image); - ImageView fromCqImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_cq_image); - if (!msg.fromDxcc) fromDxccImage.setVisibility(View.GONE); - if (!msg.fromItu) fromItuImage.setVisibility(View.GONE); - if (!msg.fromCq) fromCqImage.setVisibility(View.GONE); - - ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerMarkerConstraintLayout); - if (msg.fromCq||msg.fromItu||msg.fromDxcc){//如果是没有通联过的区域,把颜色改成红色 - layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - (R.string.tracker_new_zone_found)),msg.getMessageText())); - } - - - - //查是不是在本波段内通联成功过的呼号 - if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//如果在数据库中,划线 - titleView.setPaintFlags( - titleView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else {//如果不在数据库中,去掉划线 - titleView.setPaintFlags( - titleView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); - } - boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom()); - - //是否有与我呼号有关的消息 - if (msg.inMyCall()) { - layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); - titleView.setTextColor(mapView.getResources().getColor( - R.color.message_in_my_call_text_color)); - } else if (otherBandIsQso) { - //设置在别的波段通联过的消息颜色 - titleView.setTextColor(mapView.getResources().getColor( - R.color.fromcall_is_qso_text_color)); - } else { - titleView.setTextColor(mapView.getResources().getColor( - R.color.message_text_color)); - } - - - - - - - ImageButton imageButton=(ImageButton) this.mView.findViewById(R.id.callThisImageButton); - if (GeneralVariables.myCallsign.equals(msg.getCallsignFrom())){ - imageButton.setVisibility(View.GONE); - } - - imageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - doCallNow(); - } - }); - this.mView.setOnTouchListener(new View.OnTouchListener() { - public boolean onTouch(View v, MotionEvent e) { - if (e.getAction() == 1) { - GridMarkerInfoWindow.this.close(); - } - return true; - } - }); - } - /** - * 马上对发起者呼叫 - * - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - private void doCallNow() { - mainViewModel.addFollowCallsign(msg.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(msg);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(msg.getFromCallTransmitCallsign() - , 1, msg.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - - - @Override - public void onOpen(Object item) { - OverlayWithIW overlay = (OverlayWithIW) item; - String title = overlay.getTitle(); - if (title == null) { - title = ""; - } - - if (this.mView == null) { - Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); - } else { - titleView.setText(title); - String snippet = overlay.getSnippet(); - //Spanned snippetHtml = Html.fromHtml(snippet); - descriptionView.setText(snippet); - String subDesc = overlay.getSubDescription(); - subDescriptionView.setText(subDesc); - - } - } - - @Override - public void onClose() { - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java deleted file mode 100644 index 75b694f..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java +++ /dev/null @@ -1,951 +0,0 @@ -package com.bg7yoz.ft8cn.grid_tracker; -/** - * OsmMapView中画通联线、画网格等操作。地图是sqlite模式,采用离线方式(nightUSGS4Layer)。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import static java.lang.Math.PI; -import static java.lang.Math.asin; -import static java.lang.Math.atan; -import static java.lang.Math.cos; -import static java.lang.Math.floor; -import static java.lang.Math.sin; -import static java.lang.Math.tan; - -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.log.QSLRecordStr; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.google.android.gms.maps.model.LatLng; - -import org.osmdroid.tileprovider.IRegisterReceiver; -import org.osmdroid.tileprovider.modules.IArchiveFile; -import org.osmdroid.tileprovider.modules.OfflineTileProvider; -import org.osmdroid.tileprovider.tilesource.FileBasedTileSource; -import org.osmdroid.tileprovider.tilesource.TileSourceFactory; -import org.osmdroid.tileprovider.util.SimpleRegisterReceiver; -import org.osmdroid.util.BoundingBox; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.CustomZoomButtonsDisplay; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.Marker; -import org.osmdroid.views.overlay.Polygon; -import org.osmdroid.views.overlay.Polyline; -import org.osmdroid.views.overlay.milestones.MilestoneLineDisplayer; -import org.osmdroid.views.overlay.milestones.MilestoneLister; -import org.osmdroid.views.overlay.milestones.MilestoneManager; -import org.osmdroid.views.overlay.milestones.MilestoneMeterDistanceSliceLister; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public class GridOsmMapView { - private static final String TAG = "GridOsmMapView"; - - public enum GridMode {//网格的模式 - QSX, QSO, QSL - } - - public enum ShowTipsMode { - ALL, NEW, NONE - } - - private final MainViewModel mainViewModel; - //public static int COLOR_QSX = 0x7f0000ff;//红色50%,未通联过 - //public static int COLOR_QSO = 0x7fffff00;//黄色50%,通联过 - //public static int COLOR_QSL = 0x7fff0000;//红色50%,确认过 - private boolean showCQ = true; - private boolean showQSX = false; - - public final MapView gridMapView; - private final Context context; - // public ItemizedIconOverlay markerOverlay; - // private final ArrayList markerItems = new ArrayList<>(); - private final ArrayList gridLines = new ArrayList<>(); - private GridPolyLine selectedLine = null; - private static final int TIME_OUT = 3; - private int selectLineTimeOut = TIME_OUT;//被选择的画线,停留的周期数 - private final ArrayList gridPolygons = new ArrayList<>(); - private final ArrayList gridMarkers = new ArrayList<>(); - - private ShowTipsMode showTipsMode = ShowTipsMode.NEW; - - public GridOsmMapView(Context context, MapView gridMapView, MainViewModel mainViewModel) { - this.gridMapView = gridMapView; - this.context = context; - this.mainViewModel = mainViewModel; - } - - - public void initMap(String grid, boolean offset) { - mapViewOtherData(gridMapView);//设置内部源 - gridMapView.setMultiTouchControls(true); - gridMapView.setBuiltInZoomControls(true);//显示缩放按钮 - gridMapView.getZoomController().getDisplay().setPositions(true - , CustomZoomButtonsDisplay.HorizontalPosition.RIGHT - , CustomZoomButtonsDisplay.VerticalPosition.BOTTOM); - gridMapView.setTilesScaledToDpi(true); - - gridMapView.setMaxZoomLevel(6.0); - gridMapView.setMinZoomLevel(1.0); - gridMapView.getController().setZoom(1.6); - - gridMapView.setUseDataConnection(true); - gridMapView.setMultiTouchControls(true); - gridMapView.getOverlayManager().getTilesOverlay().setEnabled(true); - gridMapView.setSelected(true); - setGrayLine(); - - //addMarkerOverlay();//添加Marker图层 - - - //[A-Ra-r]{2}[0-9]{2}[A-Xa-x]{2},六位梅登海德正则 - // [A-Ra-r]{2}[0-9]{2},四位梅登海德正则 - LatLng latLng = MaidenheadGrid.gridToLatLng(grid);//做一下判断是不是网格 - if (latLng != null) { - if (offset) { - gridMapView.getController().setCenter(new GeoPoint(latLng.latitude - , latLng.longitude - 90f)); - } else { - gridMapView.getController().setCenter(new GeoPoint(latLng.latitude - , latLng.longitude)); - } - } - } - - /** - * 缩放到线路的范围之内 - * - * @param line 线 - */ - public void zoomToLineBound(GridPolyLine line) { - BoundingBox boundingBox = new BoundingBox(); - selectedLine = line; - selectLineTimeOut = TIME_OUT; - line.getOutlinePaint().setColor(gridMapView.getResources().getColor( - R.color.tracker_select_line_color)); - line.getOutlinePaint().setStrokeWidth(6); - //mOutlinePaint = getStrokePaint(0xffFF1E27, 3); - - GeoPoint eastNorthPoint = new GeoPoint(line.getActualPoints().get(0).getLatitude() - , line.getActualPoints().get(0).getLongitude()); - GeoPoint westSouthPoint = new GeoPoint(line.getActualPoints().get(1).getLatitude() - , line.getActualPoints().get(1).getLongitude()); - - if (Math.abs(westSouthPoint.getLongitude() - eastNorthPoint.getLongitude()) > 180) { - if (eastNorthPoint.getLongitude() > westSouthPoint.getLongitude()) { - double temp = westSouthPoint.getLongitude(); - westSouthPoint.setLongitude(eastNorthPoint.getLongitude()); - eastNorthPoint.setLongitude(temp); - - } - } else { - if (eastNorthPoint.getLongitude() < westSouthPoint.getLongitude()) { - double temp = westSouthPoint.getLongitude(); - westSouthPoint.setLongitude(eastNorthPoint.getLongitude()); - eastNorthPoint.setLongitude(temp); - - } - } - if (eastNorthPoint.getLatitude() < westSouthPoint.getLatitude()) { - double temp = westSouthPoint.getLatitude(); - westSouthPoint.setLatitude(eastNorthPoint.getLatitude()); - eastNorthPoint.setLatitude(temp); - } - - boundingBox.set(eastNorthPoint.getLatitude(), eastNorthPoint.getLongitude() - , westSouthPoint.getLatitude(), westSouthPoint.getLongitude()); - - gridMapView.zoomToBoundingBox(boundingBox, true, 100); - } - - - /** - * 显示CQ的位置 - * - * @param marker CQ的标记 - * @param offset 是否偏移 - */ - public void gotoCqGrid(GridMarker marker, boolean offset) { - GeoPoint geoPoint = new GeoPoint(marker.getPosition()); - if (offset) { - geoPoint.setLongitude(geoPoint.getLongitude() - 40f); - } - gridMapView.getController().animateTo(geoPoint, 2.5, 500L); - } - - - public synchronized GridMarker addGridMarker(String grid, Ft8Message msg) { - //todo 对于4.0的CQ消息,是没有网格信息的,可以以国家的地理位置代替 - if (LatLng2GeoPoint(MaidenheadGrid.gridToLatLng(grid)) == null) return null; - GridMarker marker = new GridMarker(context, mainViewModel, gridMapView, grid, msg); - gridMarkers.add(marker); - return marker; - } - - /** - * 清除标记marker - */ - public synchronized void clearMarkers() { - for (GridMarker marker : gridMarkers) { - marker.closeInfoWindow(); - gridMapView.getOverlays().remove(marker); - } - gridMarkers.clear(); - gridMapView.invalidate(); - } - - public GridPolyLine getSelectedLine() { - return selectedLine; - } - - public void clearSelectedLines() { - if (selectedLine != null) { - selectedLine.closeInfoWindow(); - gridMapView.getOverlays().remove(selectedLine); - selectedLine = null; - - } - } - - /** - * 清除线条 - */ - public synchronized void clearLines() { - - boolean isOpening = false; - - if (selectedLine != null) { - selectLineTimeOut--; - isOpening = selectedLine.isInfoWindowOpen(); - selectedLine.closeInfoWindow(); - gridMapView.getOverlays().remove(selectedLine); - } - for (GridPolyLine line : gridLines) { - line.closeInfoWindow(); - gridMapView.getOverlays().remove(line); - } - gridLines.clear(); - if (selectedLine != null && selectLineTimeOut > 0) { - gridMapView.getOverlays().add(selectedLine); - if (isOpening) selectedLine.showInfoWindow(); - } - gridMapView.invalidate(); - } - - - /** - * 清除网格瓦片 - */ - public synchronized void clearGridPolygon() { - for (GridPolygon polygon : gridPolygons) { - gridMapView.getOverlays().remove(polygon); - } - gridPolygons.clear(); - gridMapView.invalidate(); - } - - /** - * 清除全部图层 - */ - public void clearAll() { - clearMarkers(); - clearLines(); - clearGridPolygon(); - } - - /** - * 按照网格,查找网格图层,如果没有返回null - * - * @param grid 网格 - * @return 图层 - */ - public GridPolygon getGridPolygon(String grid) { - synchronized (gridPolygons) { - for (GridPolygon polygon : gridPolygons) { - if (polygon.grid.equals(grid)) return polygon; - } - } - return null; - } - - /** - * 标记、更新新发生消息的网格 - * - * @param grid 网格 - * @param msg 消息内容 - * @param subDetail 细节 - * @return 网格对象 - */ - public GridPolygon upgradeGridInfo(String grid, String msg, String subDetail) { - GridPolygon gridPolygon = getGridPolygon(grid); - if (gridPolygon == null) { - gridPolygon = addGridPolygon(grid, GridMode.QSX); - } - gridPolygon.setSnippet(msg); - gridPolygon.setSubDescription(subDetail); - //gridPolygon.showInfoWindow(); - return gridPolygon; - } - - /** - * 标记、更新新发生消息的网格 - * - * @param recordStr 历史记录 - * @return 网格对象 - */ - public GridPolygon upgradeGridInfo(QSLRecordStr recordStr) { - GridPolygon gridPolygon = getGridPolygon(recordStr.getGridsquare()); - if (gridPolygon == null) { - if (recordStr.isQSL) { - gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSL); - } else { - gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSO); - } - } - - gridPolygon.setSnippet(String.format(String.format("%s %s", - String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) - , recordStr.getFreq()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) - , recordStr.getBand())))); - - gridPolygon.setSubDescription(String.format("%s\n%s\n%s %s\n%s %s", - String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) - , recordStr.getTime_on()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) - , recordStr.getTime_off()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) - , recordStr.getRst_rcvd()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) - , recordStr.getRst_sent()), - - String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) - , recordStr.getMode()), - recordStr.getComment() - )); - gridPolygon.setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容 - gridPolygon.setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, gridMapView)); - return gridPolygon; - } - - /** - * 更新地图 - */ - public void mapUpdate(){ - gridMapView.invalidate(); - } - - /** - * 升级网格状态,如果没有,说明是新的,就添加网格。返回false。如果有,返回true。 - * - * @param grid 网格 - * @param gridMode 模式 - * @return 发现 - */ - public boolean upgradeGridMode(String grid, GridMode gridMode) { - GridPolygon polygon = getGridPolygon(grid); - if (polygon != null) { - polygon.upgradeGridMode(gridMode); - return true; - } else { - addGridPolygon(grid, gridMode); - return false; - } - } - - /** - * 添加网格图层 - * - * @param grid 网格 - * @param gridMode 网格类型 - * @return 返回一个网格图层对象 - */ - public synchronized GridPolygon addGridPolygon(String grid, GridMode gridMode) { - if (gridMapView == null) return null; - if (gridMapView.getRepository()==null) return null; - try {//当日志量过多时,会出现闪退的问题,在此处做一个异常捕获,防止闪退 - - GridPolygon polygon = new GridPolygon(context, gridMapView, grid, gridMode); - gridPolygons.add(polygon); - gridMapView.getOverlays().add(polygon); - return polygon; - - } catch (Exception e) { - //throw new RuntimeException(e); - } - return null; - } - - /** - * 查找有没有符合的CQ Marker - * - * @param message 消息 - * @return Marker - */ - public GridMarker getMarker(Ft8Message message) { - for (GridMarker marker : gridMarkers) { - if (marker.msg == message) { - return marker; - } - } - return null; - } - - /** - * 查找有没有符合消息的线 - * - * @param message 消息 - * @return 线 - */ - public GridPolyLine getLine(Ft8Message message) { - for (GridPolyLine line : gridLines) { - if (line.msg == message) { - return line; - } - } - return null; - } - - /** - * 在两个网格之间画线。 - * - * @param message 消息 - * @param db 数据库 - */ - public synchronized GridPolyLine drawLine(Ft8Message message, DatabaseOpr db) { - LatLng fromLatLng = MaidenheadGrid.gridToLatLng(message.getMaidenheadGrid(db)); - LatLng toLatLng = MaidenheadGrid.gridToLatLng(message.getToMaidenheadGrid(db)); - if (fromLatLng == null) { - fromLatLng = message.fromLatLng; - } - - if (toLatLng == null) { - toLatLng = message.toLatLng; - } - if (fromLatLng == null || toLatLng == null) { - return null; - } - final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, message); - gridLines.add(line); - return line; - } - - public synchronized GridPolyLine drawLine(QSLRecordStr recordStr) { - LatLng fromLatLng = MaidenheadGrid.gridToLatLng(recordStr.getGridsquare()); - LatLng toLatLng = MaidenheadGrid.gridToLatLng(recordStr.getMy_gridsquare()); - if (fromLatLng == null) { - //todo 把呼号转为国家的经纬度 - return null; - } - - if (toLatLng == null) { - //todo 把呼号转为国家的经纬度 - return null; - } - final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, recordStr); - return line; - } - - /** - * 设定地图的离线来源 - * - * @param mapView osmMap - */ - public void mapViewOtherData(MapView mapView) { - //可以根据时间不同,显示不同的地图 - String strFilepath = getAssetsCacheFile(context, context.getString(R.string.map_name)); - File exitFile = new File(strFilepath); - if (!exitFile.exists()) { - mapView.setTileSource(TileSourceFactory.USGS_SAT); - } else { - OfflineTileProvider tileProvider = new OfflineTileProvider( - (IRegisterReceiver) new SimpleRegisterReceiver(context), new File[]{exitFile}); - mapView.setTileProvider(tileProvider); - String source = ""; - IArchiveFile[] archives = tileProvider.getArchives(); - if (archives.length > 0) { - Set tileSources = archives[0].getTileSources(); - if (!tileSources.isEmpty()) { - source = tileSources.iterator().next(); - mapView.setTileSource(FileBasedTileSource.getSource(source)); - } else { - mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); - } - } else - mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); - mapView.invalidate(); - - } - } - - /** - * 获取Assets目录,这里面保存着地图文件 - * - * @param context context - * @param fileName 地图文件名,sqlite格式 - * @return 包含全路径的文件名 - */ - public String getAssetsCacheFile(Context context, String fileName) { - File cacheFile = new File(context.getCacheDir(), fileName); - try { - InputStream inputStream = context.getAssets().open(fileName); - try { - FileOutputStream outputStream = new FileOutputStream(cacheFile); - try { - byte[] buf = new byte[1024]; - int len; - while ((len = inputStream.read(buf)) > 0) { - outputStream.write(buf, 0, len); - } - } finally { - outputStream.close(); - } - } finally { - inputStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - return cacheFile.getAbsolutePath(); - } - - public static GeoPoint LatLng2GeoPoint(LatLng latLng) { - if (latLng == null) return null; - return new GeoPoint(latLng.latitude, latLng.longitude); - } - - public static ArrayList LatLngs2GeoPoints(LatLng[] latLngs) { - ArrayList geoPoints = new ArrayList<>(); - if (latLngs != null) { - for (int i = 0; i < latLngs.length; i++) { - geoPoints.add(LatLng2GeoPoint(latLngs[i])); - } - } - return geoPoints; - } - - public static class GridPolyLine extends Polyline { - //public String fromGrid; - //public String toGrid; - public Ft8Message msg; - public QSLRecordStr recorder; - //public boolean marked = false; - - @SuppressLint("DefaultLocale") - public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, QSLRecordStr recordStr) { - super(mapView); - this.recorder = recordStr; - setSnippet(String.format(String.format("%s %s", - String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) - , recordStr.getFreq()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) - , recordStr.getBand())))); - - setSubDescription(String.format("%s\n%s\n%s %s\n%s %s", - String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) - , recordStr.getTime_on()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) - , recordStr.getTime_off()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) - , recordStr.getRst_rcvd()), - String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) - , recordStr.getRst_sent()), - - String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) - , recordStr.getMode()), - recordStr.getComment() - )); - setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容 - this.mOutlinePaint = getStrokePaint( - mapView.getResources().getColor( - R.color.tracker_history_line_color), 3); - List pts = new ArrayList<>(); - pts.add(GridOsmMapView.LatLng2GeoPoint(fromLatLng)); - pts.add(GridOsmMapView.LatLng2GeoPoint(toLatLng)); - - - setPoints(pts); - setGeodesic(true); - setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, mapView)); - mapView.getOverlayManager().add(this); - } - - @SuppressLint("DefaultLocale") - public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, Ft8Message msg) { - super(mapView); - this.msg = msg; - - setSnippet(String.format("%s<--%s", msg.toWhere, msg.fromWhere));//表示距离 - setSubDescription(String.format("%dBm , %.1f ms , %s" - , msg.snr, msg.time_sec - , MaidenheadGrid.getDistLatLngStr(fromLatLng, toLatLng))); - setTitle(msg.getMessageText());//显示消息内容 - if (msg.inMyCall()) { - this.mOutlinePaint = getStrokePaint( - mapView.getResources().getColor( - R.color.tracker_in_my_line_color), 3); - } else { - this.mOutlinePaint = getStrokePaint(mapView.getResources().getColor( - R.color.tracker_line_color), 3); - } - - List pts = new ArrayList<>(); - pts.add(GridOsmMapView.LatLng2GeoPoint(fromLatLng)); - pts.add(GridOsmMapView.LatLng2GeoPoint(toLatLng)); - - - setPoints(pts); - setGeodesic(true); - setInfoWindow(new GridInfoWindow(R.layout.tracker_grid_info_win, mapView, msg)); - mapView.getOverlayManager().add(this); - //showInfoWindow(); - - final float lineLen = (float) getDistance(); - final float pointLen = lineLen / 10f > 200000 ? 200000f : lineLen / 10f; - - final List managers = new ArrayList<>(); - - final MilestoneMeterDistanceSliceLister slicerForPath = new MilestoneMeterDistanceSliceLister(); - managers.add(getAnimatedPathManager(slicerForPath)); - - setMilestoneManagers(managers); - - - //设置方向动画 - final ValueAnimator percentageCompletion = ValueAnimator.ofFloat(0, 1); // 10 kilometers - - percentageCompletion.setRepeatCount(ValueAnimator.INFINITE); - percentageCompletion.setDuration(1000); // 1 seconds - percentageCompletion.setStartDelay(0); // 1 second - - percentageCompletion.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - double dist = ((float) animation.getAnimatedValue()) * lineLen; - double distStart = dist - pointLen; - if (distStart < 0) distStart = 0; - slicerForPath.setMeterDistanceSlice(distStart, dist); - mapView.invalidate(); - } - }); - percentageCompletion.start(); - } - - /** - * 线条的动画点,设置:绿色,10f宽度 - */ - private MilestoneManager getAnimatedPathManager(final MilestoneLister pMilestoneLister) { - final Paint slicePaint = getStrokePaint(Color.GREEN, 15f); - return new MilestoneManager(pMilestoneLister, new MilestoneLineDisplayer(slicePaint)); - } - - private Paint getStrokePaint(final int pColor, final float pWidth) { - Paint paint = new Paint(); - paint.setStrokeWidth(pWidth); - paint.setStyle(Paint.Style.STROKE); - paint.setAntiAlias(true); - paint.setColor(pColor); - //paint.setStrokeCap(Paint.Cap.ROUND); - paint.setPathEffect(new DashPathEffect(new float[]{20, 10}, 0)); - return paint; - } - - public void showNewInfo() { - if (msg != null) { - if ((msg.fromDxcc || msg.fromItu || msg.fromCq) - && !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { - showInfoWindow(); - } - } - if (recorder != null) { - showInfoWindow(); - } - - } - } - - public static class GridPolygon extends Polygon { - public String grid; - public GridMode gridMode; - private final Context context; - //private BasicInfoWindow infoWindow; - //public String details; - - public GridPolygon(Context context, MapView mapView, String grid, GridMode gridMode) { - super(mapView); - this.grid = grid; - this.gridMode = gridMode; - this.context = context; - - setTitle(grid); - setStrokeWidth(3f); - setStrokeColor(this.context.getColor(R.color.osm_grid_out_line_color)); - - updateGridMode(); - - ArrayList pts = LatLngs2GeoPoints(MaidenheadGrid.gridToPolygon(grid)); - setPoints(pts); - - setVisible(true); - - } - - public synchronized void updateGridMode() { - synchronized (this) {//防止闪退 - switch (gridMode) { - case QSL: - this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qsl_color)); - //setFillColor(this.context.getColor(R.color.tracker_sample_qsl_color)); - break; - case QSO: - this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qso_color)); - //setFillColor(this.context.getColor(R.color.tracker_sample_qso_color)); - break; - case QSX: - this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qsx_color)); - //setFillColor(this.context.getColor(R.color.tracker_sample_qsx_color)); - break; - } - } - } - - public synchronized void upgradeGridMode(GridMode mode) { - if (mode.ordinal() > gridMode.ordinal()) { - gridMode = mode; - updateGridMode(); - } - } - } - - public static class GridMarker extends Marker { - public String grid; - private final Context context; - private final Ft8Message msg; - - @SuppressLint({"UseCompatLoadingForDrawables", "DefaultLocale"}) - public GridMarker(Context context, MainViewModel mainViewModel, MapView mapView - , String grid, Ft8Message msg) { - super(mapView); - this.grid = grid; - this.context = context; - this.msg = msg; - - this.setPosition(LatLng2GeoPoint(MaidenheadGrid.gridToLatLng(grid))); - this.setAnchor(ANCHOR_CENTER, ANCHOR_BOTTOM); - this.setInfoWindow(new GridMarkerInfoWindow(mainViewModel - , R.layout.tracker_cq_marker_info_win, mapView, msg)); - setSnippet(String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec)); - setSubDescription(String.format("%s , %s" - , MaidenheadGrid.getDistStr(grid, GeneralVariables.getMyMaidenheadGrid()) - , msg.fromWhere));//表示距离 - setTitle(msg.getMessageText());//显示消息内容 - - - @SuppressLint("UseCompatLoadingForDrawables") - Drawable d; - if (GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { - d = context.getDrawable(R.drawable.ic_baseline_cq_qso_24).mutate(); - d.setColorFilter(context.getColor(R.color.tracker_cq_marker_is_qso_color) - , PorterDuff.Mode.SRC_ATOP); - - } else { - d = context.getDrawable(R.drawable.ic_baseline_cq_24).mutate(); - } - if (GeneralVariables.checkQSLCallsign_OtherBand(msg.callsignFrom)) { - d.setColorFilter(context.getColor(R.color.tracker_cq_marker_other_is_qso_color) - , PorterDuff.Mode.SRC_ATOP); - } - setIcon(d); - - //this.showInfoWindow(); - mapView.getOverlays().add(this); - } - - public void showNewInfo() { - if ((msg.fromDxcc || msg.fromItu || msg.fromCq || (msg.checkIsCQ())) - && !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { - showInfoWindow(); - } - } - } - - - /** - * 显示提示,根据显示模式来显示。 - */ - public void showInfoWindows() { - setShowTipsMode(showTipsMode); - } - - /** - * 显示全部提示 - */ - public void showAllInfoWindows() { - if (showQSX) { - for (GridPolyLine line : gridLines) { - line.showInfoWindow(); - } - } - if (showCQ) { - for (GridMarker marker : gridMarkers) { - marker.showInfoWindow(); - } - } - } - - /** - * 只显示新的提示 - */ - public void showNewInfoWindows() { - if (showQSX) { - for (GridPolyLine line : gridLines) { - line.showNewInfo(); - } - } - if (showCQ) { - for (GridMarker marker : gridMarkers) { - marker.showNewInfo(); - } - } - } - - /** - * 关闭全部提示窗口 - */ - public void hideInfoWindows() { - for (GridPolygon polygon : gridPolygons - ) { - polygon.closeInfoWindow(); - } - for (GridPolyLine line : gridLines) { - line.closeInfoWindow(); - } - for (GridMarker marker : gridMarkers) { - marker.closeInfoWindow(); - } - gridMapView.invalidate(); - } - - - public void setShowCQ(boolean showCQ) { - this.showCQ = showCQ; - showInfoWindows(); - } - - public void setShowQSX(boolean showQSX) { - this.showQSX = showQSX; - showInfoWindows(); - } - - public void setShowTipsMode(ShowTipsMode showTipsMode) { - this.showTipsMode = showTipsMode; - hideInfoWindows(); - switch (this.showTipsMode) { - case ALL: - showAllInfoWindows(); - break; - case NEW: - showNewInfoWindows(); - break; - case NONE: - break; - } - } - - - private static double[] computeDayNightTerminator(long t) { - // The nice thing about the java time standard is that converting it - // to a julian date is trivial - unlike the gyrations the original - // matlab code had to go through to convert the y/n/d/h/m/s parameters - final double julianDate1970 = t / (double) (1000 * 60 * 60 * 24); - // convert from the unix epoch to the astronomical epoch - // (noon on January 1, 4713 BC, GMT/UT) (the .5 is noon versus midnight) - final double juliandate = julianDate1970 + 2440587.500000; - final double K = PI / 180; - // here be dragons! - final double T = (juliandate - 2451545.0) / 36525; - double L = 280.46645 + 36000.76983 * T + 0.0003032 * T * T; - L = L % 360; - if (L < 0) - L = L + 360; - double M = 357.52910 + 35999.05030 * T - 0.0001559 * T * T - - 0.00000048 * T * T * T; - M = M % 360; - if (M < 0) - M = M + 360; - final double C = (1.914600 - 0.004817 * T - 0.000014 * T * T) * sin(K * M) + - (0.019993 - 0.000101 * T) * sin(K * 2 * M) + - 0.000290 * sin(K * 3 * M); - final double theta = L + C; - final double LS = L; - final double LM = 218.3165 + 481267.8813 * T; - final double eps0 = 23.0 + 26.0 / 60.0 + 21.448 / 3600.0 - - (46.8150 * T + - 0.00059 * T * T - 0.001813 * T * T * T) / 3600; - final double omega = 125.04452 - 1934.136261 * T + 0.0020708 * T * T + - T * T * - T / 450000; - final double deltaEps = - (9.20 * cos(K * omega) + 0.57 * cos(K * 2 * LS) + - 0.10 * cos(K * 2 * LM) - 0.09 * cos(K * 2 * omega)) / 3600; - final double eps = eps0 + deltaEps + 0.00256 * - cos(K * (125.04 - 1934.136 * T)); - final double lambda = theta - 0.00569 - 0.00478 * sin(K * (125.04 - - 1934.136 * - T)); - final double delta = asin(sin(K * eps) * sin(K * lambda)); - final double dec = delta / K; - final double tau = (juliandate - floor(juliandate)) * 360; - double[] coords = new double[361]; - for (int i = 0; i < 361; i++) - coords[i] = atan(cos((i - 180 + tau) * K) / tan(dec * K)) / K + 90; - return coords; - } - - /** - * 根据当前时间画灰线 - */ - public void setGrayLine() { - double[] lats = computeDayNightTerminator(System.currentTimeMillis()); - LatLng[] grayLine = new LatLng[lats.length * 3]; - for (int i = 0; i < lats.length; i++) { - grayLine[i] = new LatLng((lats[i] - 90), i); - grayLine[lats.length + i] = new LatLng((lats[i] - 90), i); - grayLine[lats.length * 2 + i] = new LatLng((lats[i] - 90), i); - } - - Polyline line = new Polyline(gridMapView); - line.setWidth(15f); - line.setColor(context.getColor(R.color.tracker_gray_line_color)); - - List pts = new ArrayList<>(); - for (int i = 0; i < grayLine.length; i++) { - pts.add(GridOsmMapView.LatLng2GeoPoint(grayLine[i])); - } - line.setInfoWindow(null); - line.setPoints(pts); - gridMapView.getOverlays().add(line); - - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java deleted file mode 100644 index 3c9209b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.bg7yoz.ft8cn.grid_tracker; -/** - * 通联日志的提示窗口。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.graphics.Paint; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.OverlayWithIW; -import org.osmdroid.views.overlay.infowindow.InfoWindow; - -public class GridRecordInfoWindow extends InfoWindow { - public static final int UNDEFINED_RES_ID = 0; - // static int mTitleId = 0; -// static int mDescriptionId = 0; -// static int mSubDescriptionId = 0; -// static int fromDxccImageId = 0; -// static int fromItuImageId = 0; -// static int fromCqImageId = 0; -// static int toDxccImageId = 0; -// static int toItuImageId = 0; -// static int toCqImageId = 0; - private final TextView titleView; - private final TextView descriptionView; - private final TextView subDescriptionView; - - - @SuppressLint("UseCompatLoadingForDrawables") - public GridRecordInfoWindow(int layoutResId, MapView mapView) { - super(layoutResId, mapView); - //setResIds(mapView.getContext()); - titleView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_title); - descriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_description); - subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_subdescription); - - ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerGridRecInfoConstraintLayout); - - - this.mView.setOnTouchListener(new View.OnTouchListener() { - public boolean onTouch(View v, MotionEvent e) { - if (e.getAction() == 1) { - GridRecordInfoWindow.this.close(); - } - return true; - } - }); - } - - - @Override - public void onOpen(Object item) { - OverlayWithIW overlay = (OverlayWithIW) item; - String title = overlay.getTitle(); - if (title == null) { - title = ""; - } - - if (this.mView == null) { - Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); - } else { - titleView.setText(title); - String snippet = overlay.getSnippet(); - //Spanned snippetHtml = Html.fromHtml(snippet); - descriptionView.setText(snippet); - String subDesc = overlay.getSubDescription(); - subDescriptionView.setText(subDesc); - - } - } - - @Override - public void onClose() { - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java deleted file mode 100644 index da2b4bc..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java +++ /dev/null @@ -1,895 +0,0 @@ -package com.bg7yoz.ft8cn.grid_tracker; -/** - * 网格追踪的主窗口。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.view.WindowManager; -import android.view.animation.AnimationUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.core.content.ContextCompat; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.database.OnAfterQueryConfig; -import com.bg7yoz.ft8cn.databinding.ActivityGridTrackerMainBinding; -import com.bg7yoz.ft8cn.floatview.FloatView; -import com.bg7yoz.ft8cn.floatview.FloatViewButton; -import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; -import com.bg7yoz.ft8cn.log.OnQueryQSLRecordCallsign; -import com.bg7yoz.ft8cn.log.QSLRecordStr; -import com.bg7yoz.ft8cn.timer.UtcTimer; -import com.bg7yoz.ft8cn.ui.CallingListAdapter; -import com.bg7yoz.ft8cn.ui.FreqDialog; -import com.bg7yoz.ft8cn.ui.SetVolumeDialog; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class GridTrackerMainActivity extends AppCompatActivity { - private static final String TAG = "GridTrackerMainActivity"; - private static final String DataConfigShowMode = "tracker_show_mode"; - private static final String DataConfigShowQsx = "tracker_show_qsx"; - private static final String DataConfigShowCQ = "tracker_show_cq"; - - private MainViewModel mainViewModel; - private ActivityGridTrackerMainBinding binding; - private FloatView floatView; - private FloatViewButton transButton; - private GridOsmMapView gridOsmMapView; - - private RecyclerView callMessagesRecyclerView; - private CallingListAdapter callingListAdapter; - private boolean messageListIsClose = false; - private boolean configBarIsClose = false; - private QSLRecordStr qlsRecorder = null;//用于历史显示消息 - private MutableLiveData> qslRecordList = new MutableLiveData<>(); - - - @SuppressLint("NotifyDataSetChanged") - protected void doAfterCreate() { - //设置消息列表 - callingListAdapter.notifyDataSetChanged(); - callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); - - setTipsRadioGroupClickerListener();//显示模式Group radio动作 - setShowTipsSwitchClickerListener();//显示提示开关动作 - readConfig(); - - //读取调用本activity的参数,如果不为空,说明要画参数中的消息 - //画在日志界面中被选择的消息 - Intent intentGet = getIntent(); - qlsRecorder = (QSLRecordStr) intentGet.getSerializableExtra("qslList"); - if (qlsRecorder != null) { - GridOsmMapView.GridPolyLine line = drawMessage(qlsRecorder);//在地图上画每一个消息 - if (line != null) { - line.showInfoWindow(); - } - } - //画日志界面查询出的全部消息 - String queryKey = intentGet.getStringExtra("qslAll"); - int queryFilter = intentGet.getIntExtra("queryFilter", 0); - if (queryKey != null) { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tracker_query_qso_info)); - mainViewModel.databaseOpr.getQSLRecordByCallsign(true, 0, queryKey, queryFilter - , new OnQueryQSLRecordCallsign() { - @Override - public void afterQuery(ArrayList records) { - qslRecordList.postValue(records); - } - }); - } - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - //禁止休眠 - getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - , WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - //设置深色模式 - getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES); - - //全屏 - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN - , WindowManager.LayoutParams.FLAG_FULLSCREEN); - - - mainViewModel = MainViewModel.getInstance(this); - binding = ActivityGridTrackerMainBinding.inflate(getLayoutInflater()); - - gridOsmMapView = new GridOsmMapView(getBaseContext(), binding.osmMap, mainViewModel); - - - callMessagesRecyclerView = binding.callMessagesRecyclerView; - callingListAdapter = new CallingListAdapter(this, mainViewModel - , mainViewModel.ft8Messages, CallingListAdapter.ShowMode.TRACKER); - callMessagesRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - callMessagesRecyclerView.setAdapter(callingListAdapter); - - - callingListAdapter.setOnItemClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - int position = (int) view.getTag(); - if (position == -1) { - return; - } - if (position > mainViewModel.ft8Messages.size() - 1) { - return; - } - Ft8Message msg = mainViewModel.ft8Messages.get(position); - - if (msg.checkIsCQ()) { - GridOsmMapView.GridMarker marker = gridOsmMapView.getMarker(msg); - if (marker == null) marker = gridOsmMapView.addGridMarker( - msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg); - if (marker != null) { - gridOsmMapView.hideInfoWindows(); - gridOsmMapView.gotoCqGrid(marker, true); - marker.showInfoWindow(); - } - } else { - gridOsmMapView.hideInfoWindows(); - gridOsmMapView.clearSelectedLines(); - GridOsmMapView.GridPolyLine line; - line = gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr); - if (line != null) { - gridOsmMapView.zoomToLineBound(line); - line.showInfoWindow(); - closeMessages(); - } - } - } - }); - //设置消息列表滑动,用于快速呼叫 - initRecyclerViewAction(); - - //观察解码数量 - mainViewModel.mutable_Decoded_Counter.observe(this, new Observer() { - @SuppressLint({"DefaultLocale", "NotifyDataSetChanged"}) - @Override - public void onChanged(Integer integer) { - } - }); - mainViewModel.mutableIsDecoding.observe(this, new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - gridOsmMapView.clearLines(); - gridOsmMapView.clearMarkers(); - } - } - }); - mainViewModel.mutableFt8MessageList.observe(this, new Observer>() { - @SuppressLint("NotifyDataSetChanged") - @Override - public void onChanged(ArrayList messages) { - if (mainViewModel.currentMessages == null) return; - ArrayList tempMsg = new ArrayList<>(mainViewModel.currentMessages); - callingListAdapter.notifyDataSetChanged(); - if (callMessagesRecyclerView.computeVerticalScrollRange() - - callMessagesRecyclerView.computeVerticalScrollExtent() - - callMessagesRecyclerView.computeVerticalScrollOffset() < 500) { - callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); - } - - binding.gridMessageTextView.setText(String.format("%s %s" - , String.format(GeneralVariables.getStringFromResource( - R.string.tracker_decoded_new) - , mainViewModel.currentDecodeCount), String.format( - getString(R.string.decoding_takes_milliseconds) - , mainViewModel.ft8SignalListener.decodeTimeSec.getValue()))); - - //画电台之间的连线 - //对CQ的电台打点 - for (Ft8Message msg : tempMsg) { - drawMessage(msg);//在地图上画每一个消息 - } - gridOsmMapView.showInfoWindows(); - //} - } - }); - - - //观察DEBUG信息 - GeneralVariables.mutableDebugMessage.observe(this, new Observer() { - @Override - public void onChanged(String s) { - if (s.length() > 1) { - binding.trackerDebugLayout.setVisibility(View.VISIBLE); - } else { - binding.trackerDebugLayout.setVisibility(View.GONE); - } - binding.debugMessageTextView.setText(s); - } - }); - //设置发射消息框的动画 - binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this - , R.anim.view_blink)); - //观察发射的状态 - mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, - new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - binding.transmittingLayout.setVisibility(View.VISIBLE); - } else { - binding.transmittingLayout.setVisibility(View.GONE); - } - } - }); - - //观察发射内容的变化 - mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this, - new Observer() { - @Override - public void onChanged(String s) { - binding.transmittingMessageTextView.setText(s); - } - }); - - - //观察时钟的变化,显示进度条 - mainViewModel.timerSec.observe(this, new Observer() { - @Override - public void onChanged(Long aLong) { - if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential() - && mainViewModel.ft8TransmitSignal.isActivated()) { - binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color)); - } else { - binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color)); - } - binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15)); - } - }); - - //添加浮动按钮 - InitFloatView(); - - - //gridOsmMapView.initMap(GeneralVariables.getMyMaidenhead4Grid(), true); - - //把呼号与网格对应关系中的网格提取出来 - for (Map.Entry entry : GeneralVariables.callsignAndGrids.entrySet()) { - gridOsmMapView.upgradeGridMode(entry.getValue(), GridOsmMapView.GridMode.QSX); - } - - //观察呼号与网格的对应关系表的变化,如果有新增的,就添加 - GeneralVariables.mutableNewGrid.observe(this, new Observer() { - @Override - public void onChanged(String s) { - if (!gridOsmMapView.upgradeGridMode(s, GridOsmMapView.GridMode.QSX)) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - R.string.grid_tracker_new_grid), s)); - } - } - }); - - //获取曾经通联过的网格 - mainViewModel.databaseOpr.getQsoGridQuery(new DatabaseOpr.OnGetQsoGrids() { - @Override - public void onAfterQuery(HashMap grids) { - for (Map.Entry entry : grids.entrySet()) { - gridOsmMapView.upgradeGridMode(entry.getKey() - , entry.getValue() ? GridOsmMapView.GridMode.QSL : GridOsmMapView.GridMode.QSO); - } - } - }); - - //关闭消息按钮 - binding.closeMessageImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - closeMessages(); - } - }); - //打开消息按钮 - binding.openMessagesImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - openMessage(); - } - }); - gridOsmMapView.initMap(GeneralVariables.getMyMaidenhead4Grid(), true); - - qslRecordList.observe(this, new Observer>() { - @Override - public void onChanged(ArrayList qslRecordStrs) { - for (QSLRecordStr record : qslRecordStrs) { - drawMessage(record);//在地图上画每一个消息 - } - gridOsmMapView.mapUpdate(); - } - }); - - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - closeMessages(); - closeConfigBar(); - doAfterCreate(); - } - }, 1000); - - setContentView(binding.getRoot()); - } - - - /** - * 在地图上画消息,包括收发消息和CQ消息 - * - * @param msg 消息 - */ - @SuppressLint("DefaultLocale") - private void drawMessage(Ft8Message msg) { - gridOsmMapView.upgradeGridInfo( - msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg.getMessageText() - , String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec)); - gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr); - if (msg.checkIsCQ()) { - gridOsmMapView.addGridMarker( - msg.getMaidenheadGrid(mainViewModel.databaseOpr) - , msg); - } - } - - private GridOsmMapView.GridPolyLine drawMessage(QSLRecordStr recordStr) { - gridOsmMapView.gridMapView.post(new Runnable() { - @Override - public void run() { - gridOsmMapView.upgradeGridInfo(recordStr); - gridOsmMapView.drawLine(recordStr); - } - }); - - return null; - } - - private void setShowTipsSwitchClickerListener() { - - binding.trackerShowQsxSwitch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - gridOsmMapView.hideInfoWindows(); - if (binding.trackerShowQsxSwitch.isChecked()) { - binding.trackerShowQsxSwitch.setText(GeneralVariables.getStringFromResource( - R.string.tracker_show_qsx_tips)); - } else { - binding.trackerShowQsxSwitch.setText(GeneralVariables.getStringFromResource( - R.string.tracker_hide_qsx_tips)); - } - gridOsmMapView.setShowQSX(binding.trackerShowQsxSwitch.isChecked()); - writeConfig(DataConfigShowQsx, binding.trackerShowQsxSwitch.isChecked() ? "1" : "0"); - } - }); - - binding.trackerShowCqSwitch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - gridOsmMapView.hideInfoWindows(); - if (binding.trackerShowCqSwitch.isChecked()) { - binding.trackerShowCqSwitch.setText(GeneralVariables.getStringFromResource( - R.string.tracker_show_cq_tips)); - } else { - binding.trackerShowCqSwitch.setText(GeneralVariables.getStringFromResource( - R.string.tracker_hide_cq_tips)); - } - gridOsmMapView.setShowCQ(binding.trackerShowCqSwitch.isChecked()); - writeConfig(DataConfigShowCQ, binding.trackerShowCqSwitch.isChecked() ? "1" : "0"); - - } - }); - } - - /** - * 设置显示模式动作 - */ - private void setTipsRadioGroupClickerListener() { - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (binding.tipsAllRadioButton.isChecked()) { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.ALL); - writeConfig(DataConfigShowMode, "0"); - } - if (binding.tipsNewRadioButton.isChecked()) { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NEW); - writeConfig(DataConfigShowMode, "1"); - } - if (binding.tipsNoneRadioButton.isChecked()) { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NONE); - binding.trackerShowQsxSwitch.setVisibility(View.GONE); - binding.trackerShowCqSwitch.setVisibility(View.GONE); - writeConfig(DataConfigShowMode, "2"); - } else { - binding.trackerShowQsxSwitch.setVisibility(View.VISIBLE); - binding.trackerShowCqSwitch.setVisibility(View.VISIBLE); - } - } - }; - binding.tipsAllRadioButton.setOnClickListener(listener); - binding.tipsNewRadioButton.setOnClickListener(listener); - binding.tipsNoneRadioButton.setOnClickListener(listener); - - View.OnClickListener barListener = new View.OnClickListener() { - @Override - public void onClick(View view) { - closeConfigBar(); - } - }; - binding.closeTIpsImageView.setOnClickListener(barListener); - binding.trackerInfoModeTextView.setOnClickListener(barListener); - - } - - private void openConfigBar() { - if (!configBarIsClose) return; - configBarIsClose = false; - ObjectAnimator openConfigAnimator = ObjectAnimator.ofFloat(binding.trackerConfigLayout - , "translationY", 0); - //openConfigAnimator.setDuration(500); - openConfigAnimator.setFloatValues(binding.trackerConfigLayout.getHeight() + 10, 0); - openConfigAnimator.start(); - } - - private void closeConfigBar() { - if (configBarIsClose) return; - configBarIsClose = true; - ObjectAnimator openConfigAnimator = ObjectAnimator.ofFloat(binding.trackerConfigLayout - , "translationY", 0); - //openConfigAnimator.setDuration(500); - openConfigAnimator.setFloatValues(0, binding.trackerConfigLayout.getHeight() + 100); - openConfigAnimator.start(); - } - - /** - * 关闭消息栏 - */ - private void openMessage() { - if (!messageListIsClose) return; - messageListIsClose = false; - ObjectAnimator openMessageAnimator = ObjectAnimator.ofFloat(binding.callingListConstraintLayout - , "translationX", 0); - //openMessageAnimator.setDuration(500); - openMessageAnimator.setFloatValues(-binding.callingListConstraintLayout.getWidth() - 10, 0); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(openMessageAnimator); - animatorSet.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - binding.openMessagesImageButton.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - binding.closeMessageImageButton.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - - animatorSet.start(); - } - - /** - * 动画关闭消息栏 - */ - private void closeMessages() { - if (messageListIsClose) return; - messageListIsClose = true; - ObjectAnimator closeMessageAnimator = ObjectAnimator.ofFloat(binding.callingListConstraintLayout - , "translationX", 0); - //closeMessageAnimator.setDuration(500); - closeMessageAnimator.setFloatValues(0, -binding.callingListConstraintLayout.getWidth() - 10); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(closeMessageAnimator); - animatorSet.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animator) { - binding.closeMessageImageButton.setVisibility(View.GONE); - } - - @Override - public void onAnimationEnd(Animator animator) { - binding.openMessagesImageButton.setVisibility(View.VISIBLE); - } - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - }); - - animatorSet.start(); - } - - - /** - * 添加浮动按钮 - */ - - private void InitFloatView() { - floatView = new FloatView(this, 32); - - binding.trackConstraint.addView(floatView); - floatView.setButtonMargin(0); - floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT); - - floatView.setButtonBackgroundResourceId(R.drawable.float_button_style); - - transButton = floatView.addButton(R.id.grid_tracker_trans, "grid_tracker_trans" - , R.drawable.ic_baseline_cancel_schedule_send_off - , new View.OnClickListener() { - @Override - public void onClick(View view) { - //如果 - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.restTransmitting(); - } - mainViewModel.ft8TransmitSignal.setActivated(!mainViewModel.ft8TransmitSignal.isActivated()); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - }); - - - //动态添加按钮,建议使用静态的ID,静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置 - - floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - new FreqDialog(binding.trackConstraint.getContext(), mainViewModel).show(); - } - }); - - floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - new SetVolumeDialog(binding.trackConstraint.getContext(), mainViewModel).show(); - } - }); - floatView.addButton(R.id.grid_tracker_config, "grid_tracker_config" - , R.drawable.ic_baseline_tracker_settings_24 - , new View.OnClickListener() { - @Override - public void onClick(View view) { - if (configBarIsClose) { - openConfigBar(); - } else { - closeConfigBar(); - } - } - }); - - - //显示当前目标呼号 - mainViewModel.ft8TransmitSignal.mutableToCallsign.observe(this, new Observer() { - @Override - public void onChanged(TransmitCallsign transmitCallsign) { - binding.trackerTargetTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.target_callsign) - , transmitCallsign.callsign)); - } - }); - - //观察发射状态按钮的变化 - Observer transmittingObserver = new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (mainViewModel.ft8TransmitSignal.isActivated()) { - binding.trackerTargetTextView.setVisibility(View.VISIBLE); - } else { - binding.trackerTargetTextView.setVisibility(View.GONE); - } - - if (mainViewModel.ft8TransmitSignal.isTransmitting()) { - transButton.setImageResource(R.drawable.ic_baseline_send_red_48); - transButton.setAnimation(AnimationUtils.loadAnimation(getBaseContext(), R.anim.view_blink)); - } else { - //录音对象也要处于启动状态才可以有发射的状态 - if (mainViewModel.ft8TransmitSignal.isActivated() && mainViewModel.hamRecorder.isRunning()) { - transButton.setImageResource(R.drawable.ic_baseline_send_white_48); - } else { - transButton.setImageResource(R.drawable.ic_baseline_cancel_schedule_send_off); - } - transButton.setAnimation(null); - } - - } - }; - //显示发射状态 - mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, transmittingObserver); - mainViewModel.ft8TransmitSignal.mutableIsActivated.observe(this, transmittingObserver); - - floatView.initLocation(); - } - - /** - * 把配置信息写到数据库 - * - * @param KeyName 关键词 - * @param Value 值 - */ - private void writeConfig(String KeyName, String Value) { - mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); - } - - private void readConfig() { - OnAfterQueryConfig queryConfig = new OnAfterQueryConfig() { - @Override - public void doOnBeforeQueryConfig(String KeyName) { - - } - - @Override - public void doOnAfterQueryConfig(String KeyName, String Value) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (KeyName.equalsIgnoreCase(DataConfigShowMode)) { - if (Value.equals("1")) { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NEW); - binding.tipsNewRadioButton.setChecked(true); - binding.tipsNewRadioButton.callOnClick(); - } else if (Value.equals("2")) { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NONE); - binding.tipsNoneRadioButton.setChecked(true); - binding.tipsNoneRadioButton.callOnClick(); - } else { - gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.ALL); - binding.tipsAllRadioButton.setChecked(true); - binding.tipsAllRadioButton.callOnClick(); - } - //tipsAllRadioButton - } - if (KeyName.equalsIgnoreCase(DataConfigShowQsx)) { - gridOsmMapView.setShowQSX(Value.equals("1")); - binding.trackerShowQsxSwitch.setChecked(Value.equals("1")); - binding.trackerShowQsxSwitch.callOnClick(); - } - if (KeyName.equalsIgnoreCase(DataConfigShowCQ)) { - gridOsmMapView.setShowCQ(Value.equals("1")); - binding.trackerShowCqSwitch.setChecked(Value.equals("1")); - binding.trackerShowCqSwitch.callOnClick(); - } - } - }); - } - }; - mainViewModel.databaseOpr.getConfigByKey(DataConfigShowMode, queryConfig); - mainViewModel.databaseOpr.getConfigByKey(DataConfigShowQsx, queryConfig); - mainViewModel.databaseOpr.getConfigByKey(DataConfigShowCQ, queryConfig); - } - - /** - * 马上对发起者呼叫 - * - * @param message 消息 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - private void doCallNow(Ft8Message message) { - mainViewModel.addFollowCallsign(message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() - , 1, message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - - - /** - * 设置列表滑动动作 - */ - private void initRecyclerViewAction() { - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG - , ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder - , @NonNull RecyclerView.ViewHolder target) { - return false; - } - - //@RequiresApi(api = Build.VERSION_CODES.N) - @SuppressLint("NotifyDataSetChanged") - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - if (direction == ItemTouchHelper.START) { - Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); - if (message != null) { - //呼叫的目标不能是自己 - if (!message.getCallsignFrom().equals("<...>") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) - && !(message.i3 == 0 && message.n3 == 0)) { - doCallNow(message); - } - } - callingListAdapter.notifyDataSetChanged(); - //callingListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); - } - if (direction == ItemTouchHelper.END) {//删除 - callingListAdapter.deleteMessage(viewHolder.getAdapterPosition()); - callingListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); - } - } - - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); - //制作呼叫背景的图标显示 - final Drawable callIcon = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_baseline_send_red_48); - final Drawable delIcon = ContextCompat.getDrawable(getBaseContext(), R.drawable.log_item_delete_icon); - final Drawable background = new ColorDrawable(Color.LTGRAY); - - if (message == null) { - return; - } - if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 - return; - } - Drawable icon; - if (dX > 0) { - icon = delIcon; - } else { - icon = callIcon; - } - View itemView = viewHolder.itemView; - int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - int iconLeft, iconRight, iconTop, iconBottom; - int backTop, backBottom, backLeft, backRight; - backTop = itemView.getTop(); - backBottom = itemView.getBottom(); - iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - iconBottom = iconTop + icon.getIntrinsicHeight(); - if (dX > 0) { - backLeft = itemView.getLeft(); - backRight = itemView.getLeft() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconLeft = itemView.getLeft() + iconMargin; - iconRight = iconLeft + icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else if (dX < 0) { - backRight = itemView.getRight(); - backLeft = itemView.getRight() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconRight = itemView.getRight() - iconMargin; - iconLeft = iconRight - icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else { - background.setBounds(0, 0, 0, 0); - icon.setBounds(0, 0, 0, 0); - } - background.draw(c); - icon.draw(c); - - } - }).attachToRecyclerView(binding.callMessagesRecyclerView); - } - - - /** - * 菜单选项 - * - * @param item 菜单 - * @return 是否选择 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public boolean onContextItemSelected(@NonNull MenuItem item) { - //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); - int position = (int) item.getActionView().getTag(); - Ft8Message ft8Message = callingListAdapter.getMessageByPosition(position); - if (ft8Message == null) return super.onContextItemSelected(item); - - GeneralVariables.resetLaunchSupervision();//复位自动监管 - switch (item.getItemId()) { - case 1://时序与发送者相反!!! - Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - } - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() - , 1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - break; - - case 3: - Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); - doCallNow(ft8Message); - break; - - case 4://回复 - Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() - , -1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - break; - - - } - - return super.onContextItemSelected(item); - } - - - @Override - protected void onResume() { - super.onResume(); - binding.osmMap.onResume(); - } - - @Override - protected void onPause() { - super.onPause(); - binding.osmMap.onPause(); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java deleted file mode 100644 index 9d4a309..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.bg7yoz.ft8cn.html; -/** - * Http服务内容的出框架。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.database.Cursor; - -import com.bg7yoz.ft8cn.BuildConfig; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -public class HtmlContext { - private static final String HTML_HEAD = " FT8CN\n" + - " \n" + - " "+ - "\n" + - "\n" + - "\n"; - private static final String HTML_TITLE = "
" + - "Welcome to FT8CN "+ BuildConfig.VERSION_NAME+"
" - +GeneralVariables.getStringFromResource(R.string.html_return) - +"
\n"; - private static final String HTML_FOOTER = "" + - "
BG7YOZ
" + - ""+GeneralVariables.getStringFromResource(R.string.html_return)+"
\n"; - - private static String HTML_BODY(String context) { - return "" + HTML_TITLE + "
"+context+"\n
" + HTML_FOOTER + ""; - } - - public static String HTML_STRING(String context) { - return HTML_HEAD + HTML_BODY(context); - } - - - public static String DEFAULT_HTML() { - return HTML_STRING("" + - "" + - "" + - "" + - "" + - "" + - "" + - - "" + - "" + - "" + - //"" + - - "" + - "" + - - "" + - - "" + - "" + - - "
" - + GeneralVariables.getStringFromResource(R.string.html_track_operation_information) +"
" - +GeneralVariables.getStringFromResource(R.string.html_track_callsign_hash_table) - +"
" - +GeneralVariables.getStringFromResource(R.string.html_trace_parsed_messages)+"
" - +GeneralVariables.getStringFromResource(R.string.html_trace_callsign_and_grid_correspondence_table) - +"
" - +GeneralVariables.getStringFromResource(R.string.html_query_swl_message) - +"
" - +GeneralVariables.getStringFromResource(R.string.html_query_qso_swl) - +"
" - +GeneralVariables.getStringFromResource(R.string.html_query_configuration_information) - +"
" - +GeneralVariables.getStringFromResource(R.string.html_query_all_table)+"
" - +GeneralVariables.getStringFromResource(R.string.html_manage_tracking_callsign)+"
" - // +GeneralVariables.getStringFromResource(R.string.html_manage_communication_callsigns)+"
" - +GeneralVariables.getStringFromResource(R.string.html_show_communication_callsigns)+"
" - +GeneralVariables.getStringFromResource(R.string.html_callsign_qth)+"
"//查询日志 - +GeneralVariables.getStringFromResource(R.string.html_query_logs)+"
" - +GeneralVariables.getStringFromResource(R.string.html_export_log) - +""+GeneralVariables.getStringFromResource(R.string.html_to_the_third_party)+"
" - +GeneralVariables.getStringFromResource(R.string.html_import_log) - +""+GeneralVariables.getStringFromResource(R.string.html_from_jtdx_lotw)+"
"); - } - - /** - * 生成表格的内容 - * @param sb html - * @param s 内容 - * @return html - */ - public static StringBuilder tableCell(StringBuilder sb, String... s){ - for (String c: s) { - sb.append(String.format("%s",c)); - } - sb.append("\n"); - return sb; - } - - /** - * 生成表格标题 - * @param sb html - * @param s 标题 - * @return html - */ - public static StringBuilder tableCellHeader(StringBuilder sb, String... s){ - for (String c: s) { - sb.append(String.format("%s",c)); - } - sb.append("\n"); - return sb; - } - @SuppressLint("DefaultLocale") - public static StringBuilder tableKeyRow(StringBuilder sb , Boolean darkMode, String key, int value){ - sb.append(String.format("%s%d\n" - ,darkMode ? "class=\"bbb\"" : "" - ,key,value)); - return sb; - } - - public static StringBuilder tableKeyRow(StringBuilder sb ,Boolean darkMode,String key,String value){ - sb.append(String.format("%s%s\n" - ,darkMode ? "class=\"bbb\"" : "" - ,key,value)); - return sb; - } - - public static StringBuilder tableRowBegin(StringBuilder sb){ - return tableRowBegin(sb,false,false); - } - public static StringBuilder tableRowBegin(StringBuilder sb,Boolean alignCenter,boolean darkMode){ - sb.append(String.format("" - ,alignCenter?"align=center":"" - ,darkMode ? "class=\"bbb\"":"")); - return sb; - } - public static StringBuilder tableRowEnd(StringBuilder sb){ - sb.append(""); - return sb; - } - - public static StringBuilder tableBegin(StringBuilder sb){ - return tableBegin(sb,false,1,true); - } - public static StringBuilder tableBegin(StringBuilder sb,boolean hasBorder,boolean fullWidth){ - return tableBegin(sb,hasBorder,1,fullWidth); - } - @SuppressLint("DefaultLocale") - public static StringBuilder tableBegin(StringBuilder sb, boolean hasBorder, int cellpadding,boolean fullWidth){ - sb.append(String.format("" - ,hasBorder ? "border=\"1\"" :"" - ,cellpadding - ,fullWidth ? "width=\"100%\"":"")); - return sb; - } - - public static StringBuilder tableEnd(StringBuilder sb){ - sb.append("
"); - return sb; - } - public static String ListTableContext(Cursor cursor,boolean fullWidth){ - return ListTableContext(cursor,false,0,fullWidth); - } - public static String ListTableContext(Cursor cursor,boolean hasBorder,int cellpadding ,boolean fullWidth) { - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result,hasBorder,cellpadding,fullWidth).append("\n"); - - //写字段名 - HtmlContext.tableRowBegin(result); - for (int i = 0; i < cursor.getColumnCount(); i++) { - HtmlContext.tableCellHeader(result,cursor.getColumnName(i)); - } - HtmlContext.tableRowEnd(result).append("\n"); - int order=0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result,false,order % 2 !=0); - for (int i = 0; i < cursor.getColumnCount(); i++) { - HtmlContext.tableCell(result,(cursor.getString(i)!=null) ? cursor.getString(i) :""); - } - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - HtmlContext.tableEnd(result).append("\n"); - cursor.close(); - return result.toString(); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java deleted file mode 100644 index 860dbb2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java +++ /dev/null @@ -1,1976 +0,0 @@ -package com.bg7yoz.ft8cn.html; -/** - * Http服务的具体内容。数据库访问不需要异步方式。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import static com.bg7yoz.ft8cn.html.HtmlContext.HTML_STRING; - -import android.annotation.SuppressLint; -import android.database.Cursor; -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.connector.CableSerialPort; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.database.AfterInsertQSLData; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.database.RigNameList; -import com.bg7yoz.ft8cn.log.LogFileImport; -import com.bg7yoz.ft8cn.log.QSLRecord; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import fi.iki.elonen.NanoHTTPD; - -@SuppressWarnings("ConstantConditions") -public class LogHttpServer extends NanoHTTPD { - private final MainViewModel mainViewModel; - public static int DEFAULT_PORT = 7050; - private static final String TAG = "LOG HTTP"; - - private ImportTaskList importTaskList = new ImportTaskList();//导如日志的任务列表 - - - public LogHttpServer(MainViewModel viewModel, int port) { - super(port); - this.mainViewModel = viewModel; - - } - - @Override - public Response serve(IHTTPSession session) { - String[] uriList = session.getUri().split("/"); - String uri = ""; - String msg; - Log.i(TAG, "serve uri: " + session.getUri()); - - if (uriList.length >= 2) { - uri = uriList[1]; - } - - if (uri.equalsIgnoreCase("CONFIG")) {//查配置信息 - msg = HTML_STRING(getConfig()); - } else if (uri.equalsIgnoreCase("showQSLCallsigns")) {//显示通联过的呼号,包括最后的时间 - msg = HTML_STRING(showQslCallsigns(session)); - } else if (uri.equalsIgnoreCase("DEBUG")) {//查通联过的呼号 - msg = HTML_STRING(showDebug()); - } else if (uri.equalsIgnoreCase("SHOWHASH")) {//查通呼号的哈希表 - msg = HTML_STRING(showCallsignHash()); - } else if (uri.equalsIgnoreCase("NEWMESSAGE")) {//查本周期通联消息表 - msg = HTML_STRING(getNewMessages()); - } else if (uri.equalsIgnoreCase("MESSAGE")) {//查保存的SWL通联消息表 - return getMessages(session); - } else if (uri.equalsIgnoreCase("QSOSWLMSG")) {//查SWL QSO通联消息表 - return getSWLQsoMessages(session); - } else if (uri.equalsIgnoreCase("QSOLogs")) {//查QSO日志 - return getQsoLogs(session); - } else if (uri.equalsIgnoreCase("CALLSIGNGRID")) {//查呼号与网格的对应关系 - msg = HTML_STRING(showCallGridList()); - } else if (uri.equalsIgnoreCase("GETCALLSIGNQTH")) { - msg = HTML_STRING(getCallsignQTH(session)); - } else if (uri.equalsIgnoreCase("ALLTABLE")) {//查所有的表 - msg = HTML_STRING(getAllTableName()); - } else if (uri.equalsIgnoreCase("FOLLOWCALLSIGNS")) {//查关注的呼号 - msg = HTML_STRING(getFollowCallsigns()); - } else if (uri.equalsIgnoreCase("DELFOLLOW")) {//删除关注的呼号 - if (uriList.length >= 3) { - deleteFollowCallSign(uriList[2].replace("_", "/")); - } - msg = HTML_STRING(getFollowCallsigns()); - } else if (uri.equalsIgnoreCase("DELQSL")) { - if (uriList.length >= 3) { - deleteQSLByMonth(uriList[2].replace("_", "/")); - } - msg = HTML_STRING(showQSLTable()); - } else if (uri.equalsIgnoreCase("QSLCALLSIGNS")) {//查通联过的呼号 - msg = HTML_STRING(getQSLCallsigns()); - } else if (uri.equalsIgnoreCase("QSLTABLE")) { - msg = HTML_STRING(showQSLTable()); - } else if (uri.equalsIgnoreCase("IMPORTLOG")) { - msg = HTML_STRING(showImportLog()); - } else if (uri.equalsIgnoreCase("GETIMPORTTASK")) {//这个是用户实时获取导入状态的URI - msg = HTML_STRING(makeGetImportTaskHTML(session)); - } else if (uri.equalsIgnoreCase("CANCELTASK")) {//这个是用户取消导入的URI - msg = HTML_STRING(doCancelImport(session)); - } else if (uri.equalsIgnoreCase("IMPORTLOGDATA")) { - msg = HTML_STRING(doImportLogFile(session)); - } else if (uri.equalsIgnoreCase("SHOWALLQSL")) { - msg = HTML_STRING(showAllQSL()); - } else if (uri.equalsIgnoreCase("SHOWQSL")) { - msg = HTML_STRING(showQSLByMonth(uriList[2])); - } else if (uri.equalsIgnoreCase("DELQSLCALLSIGN")) {//删除通联过的呼号 - if (uriList.length >= 3) { - deleteQSLCallSign(uriList[2].replace("_", "/")); - } - msg = HTML_STRING(getQSLCallsigns()); - } else { - msg = HtmlContext.DEFAULT_HTML(); - } - //return newFixedLengthResponse(msg); - - try { - Response response; - if (uri.equalsIgnoreCase("DOWNALLQSL")) {//下载日志 - msg = downAllQSl(); - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); - response.addHeader("Content-Disposition", "attachment;filename=All_log.adi"); - } else if (uri.equalsIgnoreCase("DOWNQSL")) { - if (uriList.length >= 3) { - msg = downQSLByMonth(uriList[2], true); - } else { - msg = HtmlContext.DEFAULT_HTML(); - } - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); - response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); - - } else if (uri.equalsIgnoreCase("DOWNQSLNOQSL")) { - if (uriList.length >= 3) { - msg = downQSLByMonth(uriList[2], false); - } else { - msg = HtmlContext.DEFAULT_HTML(); - } - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); - response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); - - } else { - response = newFixedLengthResponse(msg); - } - return response;// - } catch (Exception exception) { - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, exception.getMessage()); - } - } - - - @SuppressLint("DefaultLocale") - private String doImportLogFile(IHTTPSession session) { - //判断是不是POST日志文件 - if (session.getMethod().equals(Method.POST) - || session.getMethod().equals(Method.PUT)) { - Map files = new HashMap<>(); - //Map header = session.getHeaders(); - try { - session.parseBody(files); - - Log.e(TAG, "doImportLogFile: information:" + files.toString()); - String param = files.get("file1");//这个是post或put文件的key - - ImportTaskList.ImportTask task = importTaskList.addTask(param.hashCode());//生成一个新的任务 - - LogFileImport logFileImport = new LogFileImport(task, param); - - - //把提交的数据放到一个独立的线程运行,防止WEB页面停留太久 - new Thread(new Runnable() { - @Override - public void run() { - doImportADI(task, logFileImport); - } - }).start(); - - //重定向,跳转到实时导入信息界面 - return String.format("\n" - , param.hashCode()); - - } catch (IOException | ResponseException e) { - e.printStackTrace(); - return String.format(GeneralVariables.getStringFromResource(R.string.html_import_failed) - , e.getMessage()); - } - } - return GeneralVariables.getStringFromResource(R.string.html_illegal_command); - } - - - private String makeGetImportTaskHTML(IHTTPSession session) { - String script = ""; - script = "\n\n"; - Map pars = session.getParms(); - if (pars.get("session") != null) { - String s = Objects.requireNonNull(pars.get("session")); - int id = Integer.parseInt(s); - if (!importTaskList.checkTaskIsRunning(id)) {//如果任务停止,就没有必要刷新了 - script = ""; - } - return script + importTaskList.getTaskHTML(id); - } - - return script; - } - - @SuppressLint("DefaultLocale") - private String doCancelImport(IHTTPSession session) { - Map pars = session.getParms(); - Log.e(TAG, "doCancelImport: " + pars.toString()); - if (pars.get("session") != null) { - String s = Objects.requireNonNull(pars.get("session")); - int id = Integer.parseInt(s); - importTaskList.cancelTask(id); - return String.format("\n" - , id); - } - return ""; - } - - @SuppressLint("DefaultLocale") - private void doImportADI(ImportTaskList.ImportTask task, LogFileImport logFileImport) { - task.setStatus(ImportTaskList.ImportState.IMPORTING); - ArrayList> recordList = logFileImport.getLogRecords();//以正则表达式:[<][Ee][Oo][Rr][>]分行 - task.importedCount = 0; - task.count = recordList.size();//总行数 - for (HashMap record : recordList) { - if (task.status == ImportTaskList.ImportState.CANCELED) break;//检查是不是取消导入 - - QSLRecord qslRecord = new QSLRecord(record); - task.processCount++; - if (mainViewModel.databaseOpr.doInsertQSLData(qslRecord, new AfterInsertQSLData() { - @Override - public void doAfterInsert(boolean isInvalid, boolean isNewQSL) { - if (isInvalid) { - task.invalidCount++; - return; - } - if (isNewQSL) { - task.newCount++; - } else { - task.updateCount++; - } - } - })) { - task.importedCount++; - } - } - - - //此处是显示错误的数据 - StringBuilder temp = new StringBuilder(); - if (logFileImport.getErrorCount() > 0) { - temp.append(""); - temp.append(String.format("\n", logFileImport.getErrorCount())); - for (int key : logFileImport.getErrorLines().keySet()) { - temp.append(String.format("\n" - , key, logFileImport.getErrorLines().get(key))); - } - - temp.append("
%d malformed logs
%d
%s
"); - } - - task.errorMsg = temp.toString(); - if (task.status!= ImportTaskList.ImportState.CANCELED) { - task.setStatus(ImportTaskList.ImportState.FINISHED); - } - mainViewModel.databaseOpr.getQslDxccToMap();//更新一下已经通联的分区 - } - - - /** - * 获取配置信息 - * - * @return config表内容 - */ - private String getConfig() { - Cursor cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select KeyName,Value from config", null); - return HtmlContext.ListTableContext(cursor, true, 4, false); - } - - /** - * 获取通联过的呼号,包括:呼号、最后时间、频段,波长、网格 - * - * @return config表内容 - */ - private String showQslCallsigns(IHTTPSession session) { - String callsign = ""; - //读取查询的参数 - Map pars = session.getParms(); - if (pars.get("callsign") != null) { - callsign = Objects.requireNonNull(pars.get("callsign")); - } - String where = String.format("%%%s%%", callsign); - - String html = String.format("

%s" + - "

\n" - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , callsign - , GeneralVariables.getStringFromResource(R.string.html_message_query)); - - Cursor cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select q.[call] as callsign ,q.gridsquare,q.band||\"(\"||q.freq||\")\" as band \n" + - ",q.qso_date||\"-\"||q.time_on as last_time from QSLTable q \n" + - "inner join QSLTable q2 ON q.id =q2.id \n" + - "where q.[call] like ?\n" + - "group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.time_on,q.band\n" + - "HAVING q.qso_date||q.time_on =MAX(q2.qso_date||q2.time_on) \n", new String[]{where}); - return html + HtmlContext.ListTableContext(cursor, true, 3, false); - - } - - /** - * 获取全部的表名 - * - * @return html - */ - private String getAllTableName() { - Cursor cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select * from sqlite_master where type='table'", null); - return HtmlContext.ListTableContext(cursor, true, 4, true); - } - - @SuppressLint({"Range", "DefaultLocale"}) - private String getCallsignQTH(IHTTPSession session) { - String callsign = ""; - String grid = ""; - //读取查询的参数 - Map pars = session.getParms(); - - if (pars.get("callsign") != null) { - callsign = Objects.requireNonNull(pars.get("callsign")); - } - if (pars.get("grid") != null) { - grid = Objects.requireNonNull(pars.get("grid")); - } - String whereCallsign = String.format("%%%s%%", callsign.toUpperCase()); - String whereGrid = String.format("%%%s%%", grid.toUpperCase()); - Cursor cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select callsign ,grid,updateTime from CallsignQTH where (callsign like ?) and (grid like ?)" - , new String[]{whereCallsign, whereGrid}); - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result, true, 2, false).append("\n"); - - result.append(String.format("
%s
\n" + - "%s\n" + - "


\n" - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , callsign - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) - , grid - , GeneralVariables.getStringFromResource(R.string.html_message_query))); - //写字段名 - HtmlContext.tableRowBegin(result).append("\n"); - HtmlContext.tableCellHeader(result - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) - , GeneralVariables.getStringFromResource(R.string.html_distance) - , GeneralVariables.getStringFromResource(R.string.html_update_time) - ).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - - @SuppressLint("SimpleDateFormat") SimpleDateFormat formatTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0); - Date date = new Date(cursor.getLong(cursor.getColumnIndex("updateTime"))); - - HtmlContext.tableCell(result - , cursor.getString(cursor.getColumnIndex("callsign")) - , cursor.getString(cursor.getColumnIndex("grid")) - , MaidenheadGrid.getDistStr(GeneralVariables.getMyMaidenhead4Grid() - , cursor.getString(cursor.getColumnIndex("grid"))) - , formatTime.format(date) - ).append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - HtmlContext.tableEnd(result).append("
\n"); - result.append(String.format("%d", order)); - cursor.close(); - return result.toString(); - } - - - /** - * 获取关注的呼号 - * - * @return HTML - */ - private String getFollowCallsigns() { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from followCallsigns", null); - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result, true, 3, false).append("\n"); - - //写字段名 - HtmlContext.tableRowBegin(result).append("\n"); - for (int i = 0; i < cursor.getColumnCount(); i++) { - HtmlContext.tableCellHeader(result - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); - } - HtmlContext.tableRowEnd(result).append("\n"); - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); - for (int i = 0; i < cursor.getColumnCount(); i++) { - HtmlContext.tableCell(result - , cursor.getString(i) - , String.format("%s" - , cursor.getString(i).replace("/", "_") - , GeneralVariables.getStringFromResource(R.string.html_delete)) - ).append("\n"); - } - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - HtmlContext.tableEnd(result).append("\n"); - cursor.close(); - return result.toString(); - } - - /** - * 删除关注的呼号 - * - * @param callsign 关注的呼号 - */ - private void deleteFollowCallSign(String callsign) { - mainViewModel.databaseOpr.getDb().execSQL("delete from followCallsigns where callsign=?", new String[]{callsign}); - } - - private void deleteQSLByMonth(String month) { - mainViewModel.databaseOpr.getDb().execSQL("delete from QSLTable where SUBSTR(qso_date,1,6)=? \n" - , new String[]{month}); - } - - - /** - * 查询通联过的呼号 - * - * @return HTML - */ - @SuppressLint("Range") - private String getQSLCallsigns() { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select * from QslCallsigns order by ID desc", null); - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - - //写字段名 - HtmlContext.tableRowBegin(result).append("\n"); - HtmlContext.tableCellHeader(result - , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time) - , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time) - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) - , GeneralVariables.getStringFromResource(R.string.html_qsl_band) - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) - , GeneralVariables.getStringFromResource(R.string.html_qsl_manual_confirmation) - , GeneralVariables.getStringFromResource(R.string.html_qsl_lotw_confirmation) - , GeneralVariables.getStringFromResource(R.string.html_qsl_data_source) - , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0); - HtmlContext.tableCell(result - , cursor.getString(cursor.getColumnIndex("startTime")) - , cursor.getString(cursor.getColumnIndex("finishTime")) - , cursor.getString(cursor.getColumnIndex("callsign")) - , cursor.getString(cursor.getColumnIndex("mode")) - , cursor.getString(cursor.getColumnIndex("grid")) - , cursor.getString(cursor.getColumnIndex("band")) - , cursor.getString(cursor.getColumnIndex("band_i")) + "Hz" - , (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) - ? "" : "×" - , (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) - ? "" : "×" - , (cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1) - ? GeneralVariables.getStringFromResource(R.string.html_qsl_import_data_from_external) - : GeneralVariables.getStringFromResource(R.string.html_qsl_native_data) - , String.format("%s" - , cursor.getString(cursor.getColumnIndex("ID")) - , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - HtmlContext.tableEnd(result).append("\n"); - cursor.close(); - return result.toString(); - } - - private void deleteQSLCallSign(String callsign) { - mainViewModel.databaseOpr.getDb().execSQL("delete from QslCallsigns where id=?", new String[]{callsign}); - } - - /** - * 显示呼号与网格的对应关系 - * - * @return html - */ - @SuppressLint("DefaultLocale") - private String showCallGridList() { - StringBuilder result = new StringBuilder(); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_callsign_grid_total) - , GeneralVariables.callsignAndGrids.size())); - HtmlContext.tableBegin(result, true, 1, false); - HtmlContext.tableRowBegin(result); - result.append(String.format("%s", GeneralVariables.getStringFromResource(R.string.html_callsign))); - result.append(String.format("%s", GeneralVariables.getStringFromResource(R.string.html_qsl_grid))); - HtmlContext.tableRowEnd(result).append("\n"); - - result.append(GeneralVariables.getCallsignAndGridToHTML()); - HtmlContext.tableEnd(result).append("
\n"); - - return result.toString(); - } - - /** - * 显示调试信息 - * - * @return html - */ - @SuppressLint("DefaultLocale") - private String showDebug() { - StringBuilder result = new StringBuilder(); - result.append(""); - - HtmlContext.tableBegin(result, true, 5, false).append("\n"); - - HtmlContext.tableRowBegin(result); - HtmlContext.tableCellHeader(result - , GeneralVariables.getStringFromResource(R.string.html_variable) - , GeneralVariables.getStringFromResource(R.string.html_value)).append("\n"); - - HtmlContext.tableKeyRow(result, false, "UTC" - , UtcTimer.getTimeStr(mainViewModel.timerSec.getValue())); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_my_callsign) - , GeneralVariables.myCallsign); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_my_grid) - , GeneralVariables.getMyMaidenheadGrid()); - - HtmlContext.tableKeyRow(result, true//消息最大缓存条数 - , GeneralVariables.getStringFromResource(R.string.html_max_message_cache) - , String.format("%d", GeneralVariables.MESSAGE_COUNT)); - - HtmlContext.tableKeyRow(result, false//音量大小 - , GeneralVariables.getStringFromResource(R.string.signal_strength) - , String.format("%.0f%%\n", GeneralVariables.volumePercent * 100f)); - - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_audio_bits) - , GeneralVariables.audioOutput32Bit ? - GeneralVariables.getStringFromResource(R.string.audio32_bit) - : GeneralVariables.getStringFromResource(R.string.audio16_bit)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_audio_rate) - , String.format("%dHz", GeneralVariables.audioSampleRate)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_decodes_in_this_cycle) - , String.format("%d", mainViewModel.currentDecodeCount)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.decode_mode_text) - , GeneralVariables.deepDecodeMode - ? GeneralVariables.getStringFromResource(R.string.deep_mode) - : GeneralVariables.getStringFromResource(R.string.fast_mode)); - - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_total_number_of_decodes) - , String.format("%d", mainViewModel.ft8Messages.size())); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_in_recording_state) - , Boolean.TRUE.equals(mainViewModel.mutableIsRecording.getValue()) - ? GeneralVariables.getStringFromResource(R.string.html_recording) - : GeneralVariables.getStringFromResource(R.string.html_no_recording)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_time_consuming_for_this_decoding) - , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_average_delay_time_of_this_cycle) - , String.format(GeneralVariables.getStringFromResource(R.string.html_seconds) - , mainViewModel.mutableTimerOffset.getValue())); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_sound_frequency) - , String.format("%.0fHz", GeneralVariables.getBaseFrequency())); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_transmission_delay_time) - , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.transmitDelay)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_launch_supervision) - , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.launchSupervision)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_automatic_program_run_time) - , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.launchSupervisionCount())); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_no_reply_limit) - , GeneralVariables.noReplyLimit); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_no_reply_count) - , GeneralVariables.noReplyCount); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.follow_cq) - , String.valueOf(GeneralVariables.autoFollowCQ)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.auto_call_follow) - , String.valueOf(GeneralVariables.autoCallFollow)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_target_callsign) - , (mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue() != null) - ? mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue().callsign : ""); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_sequential) - , mainViewModel.ft8TransmitSignal.sequential); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.synFrequency) - , String.valueOf(GeneralVariables.synFrequency)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.tran_delay) - , GeneralVariables.transmitDelay); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.ptt_delay) - , GeneralVariables.pttDelay); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.rig_name) - , RigNameList.getInstance( - GeneralVariables.getMainContext()).getRigNameByIndex(GeneralVariables.modelNo).modelName); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_mark_message) - , String.format("%s", mainViewModel.markMessage - ? GeneralVariables.getStringFromResource(R.string.html_marking_message) - : GeneralVariables.getStringFromResource(R.string.html_do_not_mark_message))); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_operation_mode) - , ControlMode.getControlModeStr(GeneralVariables.controlMode)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_civ_address) - , String.format("0x%2X", GeneralVariables.civAddress)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_baud_rate) - , GeneralVariables.baudRate); - - result.append(""); - result.append(""); - result.append(GeneralVariables.getStringFromResource(R.string.html_available_serial_ports)); - result.append("\n"); - result.append(""); - if (mainViewModel.mutableSerialPorts != null) { - if (mainViewModel.mutableSerialPorts.getValue().size() == 0) { - result.append("-"); - } - } - for (CableSerialPort.SerialPort serialPort : Objects.requireNonNull(mainViewModel.mutableSerialPorts.getValue())) { - result.append(serialPort.information()).append("
\n"); - } - result.append(""); - result.append("\n"); - - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_instruction_set) - , (mainViewModel.baseRig != null) ? mainViewModel.baseRig.getName() : "-"); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_connect_mode) - , (GeneralVariables.controlMode == ControlMode.VOX) ? "-" - : ConnectMode.getModeStr(GeneralVariables.connectMode)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_baud_rate) - , GeneralVariables.baudRate); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band) - , GeneralVariables.getBandString()); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_radio_frequency) - , (mainViewModel.baseRig != null) - ? BaseRigOperation.getFrequencyStr(mainViewModel.baseRig.getFreq()) : "-"); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.html_flex_max_rf_power) - , String.format("%d W", GeneralVariables.flexMaxRfPower)); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.html_atu_tune_power) - , String.format("%d W", GeneralVariables.flexMaxTunePower)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.be_excluded_callsigns) - , GeneralVariables.getExcludeCallsigns()); - - HtmlContext.tableKeyRow(result, true - , GeneralVariables.getStringFromResource(R.string.config_save_swl) - , String.valueOf(GeneralVariables.saveSWLMessage)); - - HtmlContext.tableKeyRow(result, false - , GeneralVariables.getStringFromResource(R.string.config_save_swl_qso) - , String.valueOf(GeneralVariables.saveSWL_QSO)); - - HtmlContext.tableEnd(result).append("
\n"); - - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - result.append(String.format("%s\n" - , String.format(GeneralVariables.getStringFromResource(R.string.html_successful_callsign) - , GeneralVariables.getBandString()))); - - result.append(""); - for (int i = 0; i < GeneralVariables.QSL_Callsign_list.size(); i++) { - result.append(GeneralVariables.QSL_Callsign_list.get(i)); - result.append(", "); - if (((i + 1) % 10) == 0) { - result.append("\n"); - } - } - result.append("\n"); - HtmlContext.tableEnd(result).append("
\n"); - - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - result.append(String.format("%s\n" - , GeneralVariables.getStringFromResource(R.string.html_tracking_callsign))); - - result.append(""); - for (int i = 0; i < GeneralVariables.followCallsign.size(); i++) { - result.append(GeneralVariables.followCallsign.get(i)); - result.append(", "); - if (((i + 1) % 10) == 0) { - result.append("\n"); - } - } - result.append("\n"); - HtmlContext.tableEnd(result).append("\n"); - - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - result.append(String.format("%s\n" - , GeneralVariables.getStringFromResource(R.string.html_tracking_qso_information))); - - result.append(""); - result.append(GeneralVariables.qslRecordList.toHTML()); - result.append("\n"); - HtmlContext.tableEnd(result).append("\n"); - - return result.toString(); - } - - /** - * 显示呼号的HASH - * - * @return html - */ - private String showCallsignHash() { - StringBuilder result = new StringBuilder(); - result.append(""); - HtmlContext.tableBegin(result, true, 3, false).append("\n"); - HtmlContext.tableRowBegin(result); - //表头 - HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_callsign) - , GeneralVariables.getStringFromResource(R.string.html_hash_value)); - - HtmlContext.tableRowEnd(result).append("\n"); - - - int order = 0; - for (Map.Entry entry : Ft8Message.hashList.entrySet()) { - HtmlContext.tableRowBegin(result, false, (order / 3) % 2 != 0); - - HtmlContext.tableCell(result, entry.getValue()); - HtmlContext.tableCell(result, String.format(" 0x%x ", entry.getKey())); - HtmlContext.tableRowEnd(result).append("\n"); - - order++; - } - HtmlContext.tableEnd(result).append("\n"); - - return result.toString(); - } - - @SuppressLint("Range") - private Response exportSWLMessage(String exportFile, String callsign, String start_date, String end_date) { - Response response; - StringBuilder fileName = new StringBuilder(); - fileName.append("message"); - if (callsign.length() > 0) { - fileName.append("_"); - fileName.append(callsign.replace("/", "_") - .replace("\\", "_") - .replace(":", "_") - .replace("?", "_") - .replace("*", "_") - .replace("|", "_") - .replace("\"", "_") - .replace("'", "_") - .replace("<", "_") - .replace(".", "_") - .replace(">", "_")); - } - if (start_date.length() > 0) { - fileName.append(String.format("_%s", start_date)); - } - if (end_date.length() > 0) { - fileName.append(String.format("_%s", end_date)); - } - fileName.append(".").append(exportFile); - - - Cursor cursor; - StringBuilder dateSql = new StringBuilder(); - if (!start_date.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)>=\"%s\") " - , start_date.replace("-", ""))); - } - if (!end_date.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)<=\"%s\") " - , end_date.replace("-", ""))); - } - String whereStr = String.format("%%%s%%", callsign); - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + - dateSql + - " order by ID " - , new String[]{whereStr, whereStr}); - - StringBuilder result = new StringBuilder(); - - String formatStr; - if (exportFile.equalsIgnoreCase("CSV")) { - formatStr = "%s,%.3f,Rx,%s,%d,%.1f,%d,%s\n"; - } else { - formatStr = "%s %12.3f Rx %s %6d %4.1f %4d %s\n"; - } - - while (cursor.moveToNext()) { - String utcTime = cursor.getString(cursor.getColumnIndex("UTC")); - int dB = cursor.getInt(cursor.getColumnIndex("SNR")); - float dt = cursor.getFloat(cursor.getColumnIndex("TIME_SEC")); - int freq = cursor.getInt(cursor.getColumnIndex("FREQ")); - String callTo = cursor.getString(cursor.getColumnIndex("CALL_TO")); - String protocol = cursor.getString(cursor.getColumnIndex("Protocol")); - String callFrom = cursor.getString(cursor.getColumnIndex("CALL_FROM")); - String extra = cursor.getString(cursor.getColumnIndex("EXTRAL")); - long band = cursor.getLong(cursor.getColumnIndex("BAND")); - - result.append(String.format(formatStr - , utcTime, (band / 1000f / 1000f), protocol, dB, dt, freq, String.format("%s %s %s", callTo, callFrom, extra))); - } - cursor.close(); - - - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", result.toString()); - response.addHeader("Content-Disposition" - , String.format("attachment;filename=%s", fileName)); - - return response; - } - - /** - * 查SWL消息表 - * - * @return html - */ - @SuppressLint({"DefaultLocale", "Range"}) - private Response getMessages(IHTTPSession session) { - int pageSize = 100; - String callsign = ""; - - - StringBuilder result = new StringBuilder(); - String startDate = ""; - String endDate = ""; - String exportFile = ""; - - //读取查询的参数 - Map pars = session.getParms(); - int pageIndex = 1; - if (pars.get("page") != null) { - pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); - } - if (pars.get("pageSize") != null) { - pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); - } - if (pars.get("callsign") != null) { - callsign = Objects.requireNonNull(pars.get("callsign")); - } - if (pars.get("start_date") != null) { - startDate = Objects.requireNonNull(pars.get("start_date")); - } - if (pars.get("end_date") != null) { - endDate = Objects.requireNonNull(pars.get("end_date")); - } - String whereStr = String.format("%%%s%%", callsign); - - if (pars.get("exportFile") != null) { - exportFile = Objects.requireNonNull(pars.get("exportFile")); - } - - //导出到文件中 - if (exportFile.equalsIgnoreCase("CSV") - || exportFile.equalsIgnoreCase("TXT")) { - return exportSWLMessage(exportFile, callsign, startDate, endDate); - } - - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - HtmlContext.tableRowBegin(result).append(""); - - - result.append(String.format("%s" + - " , %s
" - , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_csv) - , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_text))); - - Cursor cursor; - StringBuilder dateSql = new StringBuilder(); - if (!startDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)>=\"%s\") " - , startDate.replace("-", ""))); - } - if (!endDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)<=\"%s\") " - , endDate.replace("-", ""))); - } - //计算总的记录数 - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select count(*) as rc from SWLMessages " + - "where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?))" + dateSql - , new String[]{whereStr, whereStr}); - cursor.moveToFirst(); - int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); - if (pageIndex > pageCount) pageIndex = pageCount; - cursor.close(); - - //查询、每页消息数设定 - result.append(String.format("
%s , %s" + - "" +//页码及页大小 - "
\n%s " +//呼号 - "  
\n" + - "
\n%s " +//起始时间 - " \n%s 
" //结束时间 - - , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) - , GeneralVariables.getStringFromResource(R.string.html_message_page_size) - , pageSize - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , callsign - , GeneralVariables.getStringFromResource(R.string.html_message_query) - , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) - , startDate - , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) - , endDate)); - - - //定位页,第一页、上一页、下一页,最后一页 - result.append(String.format("|<" + - "  <<" + - "" + - ">>" + - "  >|
\n" - , 1, pageSize, callsign, startDate, endDate - , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate - , pageIndex - , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate - , pageCount, pageSize, callsign, startDate, endDate)); - result.append(""); - HtmlContext.tableRowEnd(result); - HtmlContext.tableEnd(result).append("\n"); - - - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - String.format( - "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + - dateSql + - " order by ID LIMIT(%d),%d " - , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); - - //result.append("\n"); - HtmlContext.tableBegin(result, false, true).append("\n"); - HtmlContext.tableRowBegin(result).append("\n"); - HtmlContext.tableCellHeader(result, "No."); - HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_protocol)); - HtmlContext.tableCellHeader(result, "i3.n3", "UTC", "dB", "Δt"); - HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_qsl_freq)); - HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.message)); - HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)).append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - - - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0); - - int i3 = cursor.getInt(cursor.getColumnIndex("I3")); - int n3 = cursor.getInt(cursor.getColumnIndex("N3")); - String utcTime = cursor.getString(cursor.getColumnIndex("UTC")); - int dB = cursor.getInt(cursor.getColumnIndex("SNR")); - float dt = cursor.getFloat(cursor.getColumnIndex("TIME_SEC")); - int freq = cursor.getInt(cursor.getColumnIndex("FREQ")); - String protocol = cursor.getString(cursor.getColumnIndex("Protocol")); - String callTo = cursor.getString(cursor.getColumnIndex("CALL_TO")); - String callFrom = cursor.getString(cursor.getColumnIndex("CALL_FROM")); - String extra = cursor.getString(cursor.getColumnIndex("EXTRAL")); - long band = cursor.getLong(cursor.getColumnIndex("BAND")); - - HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); - HtmlContext.tableCell(result, protocol, Ft8Message.getCommandInfoByI3N3(i3, n3)); - HtmlContext.tableCell(result, utcTime); - HtmlContext.tableCell(result, String.format("%d", dB)); - HtmlContext.tableCell(result, String.format("%.1f", dt)); - HtmlContext.tableCell(result, String.format("%dHz", freq)); - HtmlContext.tableCell(result, String.format("" + - "%s  " + - "%s  %s", pageSize, callTo.replace("<", "") - .replace(">", "") - , callTo.replace("<", "<") - .replace(">", ">") - , pageSize, callFrom.replace("<", "") - .replace(">", "") - , callFrom.replace("<", "<") - .replace(">", ">"), extra)); - HtmlContext.tableCell(result, BaseRigOperation.getFrequencyStr(band)).append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - - order++; - } - cursor.close(); - HtmlContext.tableEnd(result).append("
\n"); - //result.append("

"); - - - return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); - //return result.toString(); - - } - - - /** - * 把swo的QSO日志导出到文件 - * - * @param exportFile 文件名 - * @param callsign 呼号 - * @param start_date 起始日期 - * @param end_date 结束日期 - * @return 数据 - */ - @SuppressLint("Range") - private Response exportSWLQSOMessage(String exportFile, String callsign, String start_date, String end_date) { - Response response; - StringBuilder fileName = new StringBuilder(); - fileName.append("swl_qso"); - if (callsign.length() > 0) { - fileName.append("_"); - fileName.append(callsign.replace("/", "_") - .replace("\\", "_") - .replace(":", "_") - .replace("?", "_") - .replace("*", "_") - .replace("|", "_") - .replace("\"", "_") - .replace("'", "_") - .replace("<", "_") - .replace(".", "_") - .replace(">", "_")); - } - if (start_date.length() > 0) { - fileName.append(String.format("_%s", start_date)); - } - if (end_date.length() > 0) { - fileName.append(String.format("_%s", end_date)); - } - fileName.append(".").append(exportFile); - - - Cursor cursor; - StringBuilder dateSql = new StringBuilder(); - if (!start_date.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " - , start_date.replace("-", ""))); - } - if (!end_date.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " - , end_date.replace("-", ""))); - } - String whereStr = String.format("%%%s%%", callsign); - - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - String.format( - "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - dateSql + - " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); - - - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" - , downQSLTable(cursor, true)); - response.addHeader("Content-Disposition" - , String.format("attachment;filename=%s", fileName)); - - return response; - } - - - /** - * 查询SWL日志 - * - * @param session 会话 - * @return html - */ - @SuppressLint({"DefaultLocale", "Range"}) - private Response getSWLQsoMessages(IHTTPSession session) { - int pageSize = 100; - String callsign = ""; - - - StringBuilder result = new StringBuilder(); - String startDate = ""; - String endDate = ""; - String exportFile = ""; - - //读取查询的参数 - Map pars = session.getParms(); - int pageIndex = 1; - if (pars.get("page") != null) { - pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); - } - if (pars.get("pageSize") != null) { - pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); - } - if (pars.get("callsign") != null) { - callsign = Objects.requireNonNull(pars.get("callsign")); - } - if (pars.get("start_date") != null) { - startDate = Objects.requireNonNull(pars.get("start_date")); - } - if (pars.get("end_date") != null) { - endDate = Objects.requireNonNull(pars.get("end_date")); - } - String whereStr = String.format("%%%s%%", callsign); - - if (pars.get("exportFile") != null) { - exportFile = Objects.requireNonNull(pars.get("exportFile")); - } - - //导出到文件中 - if (exportFile.equalsIgnoreCase("ADI")) { - return exportSWLQSOMessage(exportFile, callsign, startDate, endDate); - } - - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - HtmlContext.tableRowBegin(result).append("\n"); - result.append(""); - - result.append(String.format("%s" - , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_adi))); - - Cursor cursor; - StringBuilder dateSql = new StringBuilder(); - if (!startDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " - , startDate.replace("-", ""))); - } - if (!endDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " - , endDate.replace("-", ""))); - } - //计算总的记录数 - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select count(*) as rc from SWLQSOTable " + - "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql - , new String[]{whereStr, whereStr}); - cursor.moveToFirst(); - int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); - if (pageIndex > pageCount) pageIndex = pageCount; - cursor.close(); - - //查询、每页消息数设定 - result.append(String.format("
%s , %s" + - "" +//页码及页大小 - "
\n%s " +//呼号 - "  
\n" + - "
\n%s " +//起始时间 - " \n%s 
" //结束时间 - - , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) - , GeneralVariables.getStringFromResource(R.string.html_message_page_size) - , pageSize - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , callsign - , GeneralVariables.getStringFromResource(R.string.html_message_query) - , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) - , startDate - , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) - , endDate)); - - - //定位页,第一页、上一页、下一页,最后一页 - result.append(String.format("|<" + - "  <<" + - "" + - ">>" + - "  >|
\n" - - , 1, pageSize, callsign, startDate, endDate - , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate - , pageIndex - , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate - , pageCount, pageSize, callsign, startDate, endDate)); - - result.append(""); - HtmlContext.tableRowEnd(result); - HtmlContext.tableEnd(result).append("\n"); - - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - String.format( - "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - dateSql + - " order by qso_date desc,time_on desc LIMIT(%d),%d " - , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); - - HtmlContext.tableBegin(result, false, true).append("\n"); - - HtmlContext.tableRowBegin(result); - HtmlContext.tableCellHeader(result, "No." - , GeneralVariables.getStringFromResource(R.string.html_callsign)//"call" - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"gridsquare" - , GeneralVariables.getStringFromResource(R.string.html_qsl_mode)//"mode" - , GeneralVariables.getStringFromResource(R.string.html_rst_sent)//"rst_sent" - , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd)//"rst_rcvd" - , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" - , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" - , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off - , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" - , GeneralVariables.getStringFromResource(R.string.html_qsl_band)//"band" - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq)//"freq" - , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" - , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") - .append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); - - String call = cursor.getString(cursor.getColumnIndex("call")); - String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); - String mode = cursor.getString(cursor.getColumnIndex("mode")); - String rst_sent = cursor.getString(cursor.getColumnIndex("rst_sent")); - String rst_rcvd = cursor.getString(cursor.getColumnIndex("rst_rcvd")); - String qso_date = cursor.getString(cursor.getColumnIndex("qso_date")); - String time_on = cursor.getString(cursor.getColumnIndex("time_on")); - String qso_date_off = cursor.getString(cursor.getColumnIndex("qso_date_off")); - String time_off = cursor.getString(cursor.getColumnIndex("time_off")); - String band = cursor.getString(cursor.getColumnIndex("band")); - String freq = cursor.getString(cursor.getColumnIndex("freq")); - String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); - String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); - String comment = cursor.getString(cursor.getColumnIndex("comment")); - - - //生成数据表的一行 - HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); - HtmlContext.tableCell(result, String.format("%s" - , pageSize, call.replace("<", "") - .replace(">", "") - , call.replace("<", "<") - .replace(">", ">"))); - HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare); - HtmlContext.tableCell(result, mode, rst_sent, rst_rcvd, qso_date, time_on, qso_date_off, time_off); - HtmlContext.tableCell(result, band, freq); - HtmlContext.tableCell(result, String.format("%s" - , pageSize - , station_callsign.replace("<", "") - .replace(">", "") - , station_callsign.replace("<", "<") - .replace(">", ">"))); - HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare - , comment).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - cursor.close(); - HtmlContext.tableEnd(result).append("
\n"); - - return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); - } - - - /** - * 把QSO日志导出到文件 - * - * @param exportFile 文件名 - * @param callsign 呼号 - * @param start_date 起始日期 - * @param end_date 结束日期 - * @return 数据 - */ - @SuppressLint("Range") - private Response exportQSOLogs(String exportFile, String callsign, String start_date, String end_date, String extWhere) { - Response response; - StringBuilder fileName = new StringBuilder(); - fileName.append("qso_log"); - if (callsign.length() > 0) { - fileName.append("_"); - fileName.append(callsign.replace("/", "_") - .replace("\\", "_") - .replace(":", "_") - .replace("?", "_") - .replace("*", "_") - .replace("|", "_") - .replace("\"", "_") - .replace("'", "_") - .replace("<", "_") - .replace(".", "_") - .replace(">", "_")); - } - if (start_date.length() > 0) { - fileName.append(String.format("_%s", start_date)); - } - if (end_date.length() > 0) { - fileName.append(String.format("_%s", end_date)); - } - fileName.append(".").append(exportFile); - - - Cursor cursor; - String whereStr = String.format("%%%s%%", callsign); - - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - String.format( - "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - extWhere + - " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); - - - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" - , downQSLTable(cursor, false)); - response.addHeader("Content-Disposition" - , String.format("attachment;filename=%s", fileName)); - - return response; - } - - /** - * 查询QSO日志 - * - * @param session 会话 - * @return html - */ - @SuppressLint({"DefaultLocale", "Range"}) - private Response getQsoLogs(IHTTPSession session) { - int pageSize = 100; - String callsign = ""; - - - StringBuilder result = new StringBuilder(); - String startDate = ""; - String endDate = ""; - String exportFile = ""; - String qIsQSL = ""; - String qIsImported = ""; - - //读取查询的参数 - Map pars = session.getParms(); - int pageIndex = 1; - if (pars.get("page") != null) { - pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); - } - if (pars.get("pageSize") != null) { - pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); - } - if (pars.get("callsign") != null) { - callsign = Objects.requireNonNull(pars.get("callsign")); - } - if (pars.get("start_date") != null) { - startDate = Objects.requireNonNull(pars.get("start_date")); - } - if (pars.get("end_date") != null) { - endDate = Objects.requireNonNull(pars.get("end_date")); - } - String whereStr = String.format("%%%s%%", callsign); - - if (pars.get("exportFile") != null) { - exportFile = Objects.requireNonNull(pars.get("exportFile")); - } - if (pars.get("QSL") != null) { - qIsQSL = Objects.requireNonNull(pars.get("QSL")); - } - if (pars.get("Imported") != null) { - qIsImported = Objects.requireNonNull(pars.get("Imported")); - } - - result.append("
\n"); - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - HtmlContext.tableRowBegin(result).append(""); - result.append(String.format("%s\n" - , callsign, startDate, endDate, qIsQSL, qIsImported - , GeneralVariables.getStringFromResource(R.string.html_export_adi))); - - Cursor cursor; - StringBuilder dateSql = new StringBuilder(); - if (!startDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " - , startDate.replace("-", ""))); - } - if (!endDate.equals("")) { - dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " - , endDate.replace("-", ""))); - } - if (!qIsQSL.equals("")) { - dateSql.append(String.format(" AND ((isQSl = %s) %s (isLotW_QSL = %s))" - , qIsQSL, qIsQSL.equals("1") ? "OR" : "AND", qIsQSL)); - } - if (!qIsImported.equals("")) { - dateSql.append(String.format(" AND (isLotW_import = %s)", qIsImported)); - } - - - //导出到文件中 - if (exportFile.equalsIgnoreCase("ADI")) { - return exportQSOLogs(exportFile, callsign, startDate, endDate, dateSql.toString()); - } - - //计算总的记录数 - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select count(*) as rc from QSLTable " + - "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql - , new String[]{whereStr, whereStr}); - cursor.moveToFirst(); - int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); - if (pageIndex > pageCount) pageIndex = pageCount; - cursor.close(); - - //查询、每页消息数设定 - result.append(""); - HtmlContext.tableRowEnd(result); - HtmlContext.tableRowBegin(result).append("\n"); - - result.append(String.format("%s , %s" + - "" +//页码及页大小 - "  
\n" + - "
\n%s " +//呼号 - "\n%s " +//起始时间 - "\n%s \n" +//结束时间 - " \n" + - " \n" + - "
\n" + - - "
\n" + - " QSL" + - "
\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n" + - "
" + - "
\n" + - "
\n" + - "
\n" + - " " + GeneralVariables.getStringFromResource(R.string.html_qso_source) + "" + - "
\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
" + - "
" + - "
" + - "\n" + - " "); - HtmlContext.tableRowEnd(result); - HtmlContext.tableEnd(result); - result.append(""); - //"
" - - - , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) - , GeneralVariables.getStringFromResource(R.string.html_message_page_size) - , pageSize - , GeneralVariables.getStringFromResource(R.string.html_message_query) - - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , callsign - , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) - , startDate - , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) - , endDate - - , qIsQSL.equals("") ? "checked=\"true\"" : "" - , qIsQSL.equals("0") ? "checked=\"true\"" : "" - , qIsQSL.equals("1") ? "checked=\"true\"" : "" - - - , qIsImported.equals("") ? "checked=\"true\"" : "" - , qIsImported.equals("0") ? "checked=\"true\"" : "" - , qIsImported.equals("1") ? "checked=\"true\"" : "" - )); - - - //定位页,第一页、上一页、下一页,最后一页 - result.append(String.format("|<" + - "  <<" + - "" + - ">>\n" + - "  >|\n" - - , 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported - , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported - , pageIndex - , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported - , pageCount, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported)); - result.append("
\n" - - - cursor = mainViewModel.databaseOpr.getDb().rawQuery( - String.format( - "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - dateSql + - " order by qso_date desc,time_on desc LIMIT(%d),%d " - , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); - - HtmlContext.tableBegin(result, false, true).append("\n"); - - //表头 - HtmlContext.tableRowBegin(result).append("\n"); - HtmlContext.tableCellHeader(result, "No.", "QSL" - , GeneralVariables.getStringFromResource(R.string.html_qso_source) - , GeneralVariables.getStringFromResource(R.string.html_callsign) - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) - , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) - , GeneralVariables.getStringFromResource(R.string.html_rst_sent) - , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd) - , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" - , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" - , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off - , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" - , GeneralVariables.getStringFromResource(R.string.html_qsl_band) - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) - , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" - , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" - , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") - .append("\n"); - HtmlContext.tableRowEnd(result).append("\n"); - - //表内容 - int order = 0; - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); - - String call = cursor.getString(cursor.getColumnIndex("call")); - boolean isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; - boolean isLotW_Import = cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1; - boolean isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; - String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); - String mode = cursor.getString(cursor.getColumnIndex("mode")); - String rst_sent = cursor.getString(cursor.getColumnIndex("rst_sent")); - String rst_rcvd = cursor.getString(cursor.getColumnIndex("rst_rcvd")); - String qso_date = cursor.getString(cursor.getColumnIndex("qso_date")); - String time_on = cursor.getString(cursor.getColumnIndex("time_on")); - String qso_date_off = cursor.getString(cursor.getColumnIndex("qso_date_off")); - String time_off = cursor.getString(cursor.getColumnIndex("time_off")); - String band = cursor.getString(cursor.getColumnIndex("band")); - String freq = cursor.getString(cursor.getColumnIndex("freq")); - String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); - String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); - String comment = cursor.getString(cursor.getColumnIndex("comment")); - - - HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); - HtmlContext.tableCell(result, (isQSL || isLotW_QSL) ? "" : ""); - HtmlContext.tableCell(result, isLotW_Import ? - String.format("%s" - , GeneralVariables.getStringFromResource(R.string.html_qso_external)) - : String.format("%s" - , GeneralVariables.getStringFromResource(R.string.html_qso_raw)));//是否是导入的 - HtmlContext.tableCell(result, String.format("%s" - , pageSize - , call.replace("<", "") - .replace(">", "") - , call.replace("<", "<") - .replace(">", ">"))); - HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare, mode, rst_sent, rst_rcvd - , qso_date, time_on, qso_date_off, time_off, band, freq); - HtmlContext.tableCell(result, String.format("%s" - , pageSize - , station_callsign.replace("<", "") - .replace(">", "") - , station_callsign.replace("<", "<") - .replace(">", ">"))); - HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare - , comment).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - - order++; - } - cursor.close(); - HtmlContext.tableEnd(result).append("
\n"); - - return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); - } - - /** - * 获取全部通联日志 - * - * @return HTML - */ - private String showAllQSL() { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select * from QSLTable order by ID DESC ", null); - return HtmlContext.ListTableContext(cursor, true); - } - - /** - * 按月获取日志 - * - * @param month 月份yyyymm - * @return HTML - */ - private String showQSLByMonth(String month) { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select * from QSLTable WHERE SUBSTR(qso_date,1,?)=? \n" + - "order by ID DESC ", new String[]{String.valueOf(month.length()), month}); - return HtmlContext.ListTableContext(cursor, true); - } - - /** - * 查最新解码的消息 - * - * @return html - */ - private String getNewMessages() { - StringBuilder result = new StringBuilder(); - result.append(""); - HtmlContext.tableBegin(result, false, true).append("\n"); - - HtmlContext.tableRowBegin(result); - HtmlContext.tableCellHeader(result, "UTC", "dB", "Δt" - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) - , GeneralVariables.getStringFromResource(R.string.message) - , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)); - HtmlContext.tableRowEnd(result).append("\n"); - - int order = 0; - if (mainViewModel.currentMessages != null) { - for (Ft8Message message : mainViewModel.currentMessages) { - HtmlContext.tableRowBegin(result, true, order % 2 != 0) - .append("\n").append(message.toHtml()); - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - } - HtmlContext.tableEnd(result).append("
\n"); - return result.toString(); - } - - /** - * 显示导入FT8CN日志文件的HTML - * - * @return HTML - */ - private String showImportLog() { - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result, false, 0, true).append("\n"); - HtmlContext.tableRowBegin(result, false, true); - HtmlContext.tableCell(result, String.format("%s%s%s" - , GeneralVariables.getStringFromResource(R.string.html_please_select) - , GeneralVariables.getStringFromResource(R.string.html_adi_format) - , GeneralVariables.getStringFromResource(R.string.html_file_in_other_format))); - - HtmlContext.tableRowEnd(result).append("\n"); - HtmlContext.tableRowBegin(result).append("\n"); - - result.append("
\n" + - " \n" + - " \n" + - "
"); - HtmlContext.tableRowEnd(result).append("\n"); - HtmlContext.tableEnd(result).append("\n"); - return result.toString(); - } - - @SuppressLint("Range") - private String showQSLTable() { - - StringBuilder result = new StringBuilder(); - HtmlContext.tableBegin(result, false, 1, true).append("\n"); - HtmlContext.tableRowBegin(result).append("\n"); - - HtmlContext.tableCellHeader(result - , GeneralVariables.getStringFromResource(R.string.html_time) - , GeneralVariables.getStringFromResource(R.string.html_total) - , GeneralVariables.getStringFromResource(R.string.html_operation) - , GeneralVariables.getStringFromResource(R.string.html_operation) - , GeneralVariables.getStringFromResource(R.string.html_operation) - ).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select count(*) as b from QSLTable" - , null); - cursor.moveToFirst(); - - result.append(String.format("%s" - , GeneralVariables.getStringFromResource(R.string.html_all_logs))); - result.append(String.format("%s", cursor.getString(cursor.getColumnIndex("b")))); - result.append(String.format("%s" - , GeneralVariables.getStringFromResource(R.string.html_download))); - result.append(""); - cursor.close(); - - cursor = mainViewModel.databaseOpr.getDb().rawQuery("select count(*) as b from QSLTable\n" + - "WHERE SUBSTR(qso_date,1,8)=?", new String[]{UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime())}); - cursor.moveToFirst(); - - HtmlContext.tableRowBegin(result, true, true).append("\n"); - HtmlContext.tableCell(result, String.format("%s" - , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) - , GeneralVariables.getStringFromResource(R.string.html_today_log))); - HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); - HtmlContext.tableCell(result, String.format("%s" - , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) - , GeneralVariables.getStringFromResource(R.string.html_download_all))); - HtmlContext.tableCell(result, String.format("%s" - , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) - , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); - HtmlContext.tableCell(result, String.format("%s" - , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) - , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - - cursor.close(); - - int order = 1; - cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select SUBSTR(qso_date,1,6) as a,count(*) as b from QSLTable\n" + - "group by SUBSTR(qso_date,1,6)", null); - while (cursor.moveToNext()) { - HtmlContext.tableRowBegin(result, true, order % 2 == 0); - - HtmlContext.tableCell(result, String.format("%s" - , cursor.getString(cursor.getColumnIndex("a")) - , cursor.getString(cursor.getColumnIndex("a")))); - - HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); - HtmlContext.tableCell(result, String.format("%s" - , cursor.getString(cursor.getColumnIndex("a")) - , GeneralVariables.getStringFromResource(R.string.html_download_all))); - HtmlContext.tableCell(result, String.format("%s" - , cursor.getString(cursor.getColumnIndex("a")) - , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); - HtmlContext.tableCell(result, String.format("%s" - , cursor.getString(cursor.getColumnIndex("a")) - , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); - - HtmlContext.tableRowEnd(result).append("\n"); - order++; - } - HtmlContext.tableEnd(result).append("\n"); - cursor.close(); - return result.toString(); - } - - private String downQSLByMonth(String month, boolean downall) { - Cursor cursor; - if (downall) { - cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable \n" + - "WHERE (SUBSTR(qso_date,1,?)=?)" - , new String[]{String.valueOf(month.length()), month}); - } else { - cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable \n" + - "WHERE (SUBSTR(qso_date,1,?)=?)and(isLotW_QSL=0 and isQSL=0)" - , new String[]{String.valueOf(month.length()), month}); - - } - return downQSLTable(cursor, false); - } - - /** - * 下载全部日志 - * - * @return String - */ - private String downAllQSl() { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable", null); - return downQSLTable(cursor, false); - } - - /** - * 生成QSL记录文本 - * - * @return 日志内容 - */ - @SuppressLint({"Range", "DefaultLocale"}) - private String downQSLTable(Cursor cursor, boolean isSWL) { - StringBuilder logStr = new StringBuilder(); - - logStr.append("FT8CN ADIF Export\n"); - while (cursor.moveToNext()) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("call")).length() - , cursor.getString(cursor.getColumnIndex("call")))); - if (!isSWL) { - if (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) { - logStr.append("Y "); - } else { - logStr.append("N "); - } - if (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) { - logStr.append("Y "); - } else { - logStr.append("N "); - } - } else { - logStr.append("Y "); - } - if (cursor.getString(cursor.getColumnIndex("gridsquare")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("gridsquare")).length() - , cursor.getString(cursor.getColumnIndex("gridsquare")))); - } - - if (cursor.getString(cursor.getColumnIndex("mode")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("mode")).length() - , cursor.getString(cursor.getColumnIndex("mode")))); - } - - if (cursor.getString(cursor.getColumnIndex("rst_sent")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("rst_sent")).length() - , cursor.getString(cursor.getColumnIndex("rst_sent")))); - } - - if (cursor.getString(cursor.getColumnIndex("rst_rcvd")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("rst_rcvd")).length() - , cursor.getString(cursor.getColumnIndex("rst_rcvd")))); - } - - if (cursor.getString(cursor.getColumnIndex("qso_date")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("qso_date")).length() - , cursor.getString(cursor.getColumnIndex("qso_date")))); - } - - if (cursor.getString(cursor.getColumnIndex("time_on")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("time_on")).length() - , cursor.getString(cursor.getColumnIndex("time_on")))); - } - - if (cursor.getString(cursor.getColumnIndex("qso_date_off")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("qso_date_off")).length() - , cursor.getString(cursor.getColumnIndex("qso_date_off")))); - } - - if (cursor.getString(cursor.getColumnIndex("time_off")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("time_off")).length() - , cursor.getString(cursor.getColumnIndex("time_off")))); - } - - if (cursor.getString(cursor.getColumnIndex("band")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("band")).length() - , cursor.getString(cursor.getColumnIndex("band")))); - } - - if (cursor.getString(cursor.getColumnIndex("freq")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("freq")).length() - , cursor.getString(cursor.getColumnIndex("freq")))); - } - - if (cursor.getString(cursor.getColumnIndex("station_callsign")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("station_callsign")).length() - , cursor.getString(cursor.getColumnIndex("station_callsign")))); - } - - if (cursor.getString(cursor.getColumnIndex("my_gridsquare")) != null) { - logStr.append(String.format("%s " - , cursor.getString(cursor.getColumnIndex("my_gridsquare")).length() - , cursor.getString(cursor.getColumnIndex("my_gridsquare")))); - } - - String comment = cursor.getString(cursor.getColumnIndex("comment")); - - //Distance: 99 km - //在写库的时候,一定要加" km" - logStr.append(String.format("%s \n" - , comment.length() - , comment)); - } - - cursor.close(); - return logStr.toString(); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java deleted file mode 100644 index c4d5070..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java +++ /dev/null @@ -1,1033 +0,0 @@ -package com.bg7yoz.ft8cn.icom; - -/** - * ICom各数据包的解包和封包。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class IComPacketTypes { - private static final String TAG = "IComPacketTypes"; - - public static final int TX_BUFFER_SIZE = 0xf0; - /** - * 各类型包的长度 - */ - public static final int CONTROL_SIZE = 0x10; - public static final int WATCHDOG_SIZE = 0x14; - public static final int PING_SIZE = 0x15; - public static final int OPENCLOSE_SIZE = 0x16; - public static final int RETRANSMIT_RANGE_SIZE = 0x18;//重新传输,范围 - public static final int TOKEN_SIZE = 0x40; - public static final int STATUS_SIZE = 0x50; - public static final int LOGIN_RESPONSE_SIZE = 0x60; - public static final int LOGIN_SIZE = 0x80; - public static final int CONNINFO_SIZE = 0x90; - public static final int CAPABILITIES_SIZE = 0x42;//功能包 - public static final int RADIO_CAP_SIZE = 0x66; - public static final int CAP_CAPABILITIES_SIZE = 0xA8;//0x42+0x66 - public static final int AUDIO_HEAD_SIZE=0x18;//音频数据包的头是0x10+0x08,后面再跟音频数据 - - - public static final short CMD_NULL = 0x00;//空指令 - public static final short CMD_RETRANSMIT = 0x01;//请求重新发送,seq是重新发送的序号 - public static final short CMD_ARE_YOU_THERE = 0x03;//询问是否在?seq值必须为0 - public static final short CMD_I_AM_HERE = 0x04;//回答是否在? - public static final short CMD_DISCONNECT = 0x05;//断开 - public static final short CMD_ARE_YOU_READY = 0x06;//询问电台是否准备好了,seq=1 - public static final short CMD_I_AM_READY = 0x06;//电台回复已经准备好 - public static final short CMD_PING = 0x07;//ping ,seq有自己的序列 - - public static final byte TOKEN_TYPE_DELETE = 0x01;//令牌删除包 - public static final byte TOKEN_TYPE_CONFIRM = 0x02;//令牌确认包 - public static final byte TOKEN_TYPE_DISCONNECT = 0x04;//断开CI-V和音频流 - public static final byte TOKEN_TYPE_RENEWAL = 0x05;//令牌续订 - - - public static final long PING_PERIOD_MS = 500;//ping时钟的周期 - public static final long ARE_YOU_THERE_PERIOD_MS = 500;//查找电台的时钟周期 - public static final long IDLE_PERIOD_MS = 100;//空包时钟的周期 - public static final long TOKEN_RENEWAL_PERIOD_MS = 60000;//令牌旭东的时钟周期 - public static final long PURGE_MILLISECONDS = 10000;//数据缓冲区的时间最长是10秒钟 - public static final long OPEN_CLOSE_PERIOD_MS = 500;//civ指令定时发送open指令,确保端口打开 - public static final long WATCH_DOG_PERIOD_MS = 500;//监视数据接收状况的看门狗 - public static final long WATCH_DOG_ALERT_MS = 2000;//触发数据接收状况报警的阈值 - public static final long METER_TIMER_PERIOD_MS = 500;//检查meter的时钟周期 - - public static final int AUDIO_SAMPLE_RATE = 12000;//音频的采样率 - - public static final short CODEC_ALL_SUPPORTED = 0x018b; - public static final short CODEC_ONLY_24K = 0x0100; - public static final short CODEC_ONLY_12K = 0x0080; - public static final short CODEC_ONLY_441K = 0x0040;//44.1k - public static final short CODEC_ONLY_2205K = 0x0020;//22.05k - public static final short CODEC_ONLY_11025K = 0x0010;//11.025k - public static final short CODEC_ONLY_48K = 0x0008; - public static final short CODEC_ONLY_32K = 0x0004; - public static final short CODEC_ONLY_16K = 0x0002; - public static final short CODEC_ONLY_8K = 0x0001; - - public static class IcomCodecType { - public static final byte ULAW_1CH_8BIT = 0x01; - public static final byte LPCM_1CH_8BIT = 0x02; - public static final byte LPCM_1CH_16BIT = 0x04;//FT8CN推荐值 - public static final byte PCM_2CH_8BIT = 0x08; - public static final byte LPCM_2CH_16BIT = 0x10; - public static final byte ULAW_2CH_8BIT = 0x20; - public static final byte OPUS_CH1 = 0x40; - public static final byte OPUS_CH2 = (byte) 0x80; - } - - - /** - * 控制指令数据包。用于简单通信和重传请求的不带内容的数据包,0x10 - */ - public static class ControlPacket { - /** - * 把控制数据包转换成数据流 - * - * @return 数据流 - */ - public static byte[] toBytes(short type, short seq, int sentId, int rcvdId) { - byte[] packet = new byte[CONTROL_SIZE]; - System.arraycopy(intToBigEndian(CONTROL_SIZE), 0, packet, 0, 4); - System.arraycopy(shortToBigEndian(type), 0, packet, 4, 2); - System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); - System.arraycopy(intToBigEndian(sentId), 0, packet, 8, 4); - System.arraycopy(intToBigEndian(rcvdId), 0, packet, 12, 4); - return packet; - } - - public static byte[] idlePacketData(short seq, int sendid, int rcvdId) { - return toBytes(CMD_NULL, seq, sendid, rcvdId); - } - - public static boolean isControlPacket(byte[] data) {//0x10 - return data.length == CONTROL_SIZE && readIntBigEndianData(data, 0x00) == CONTROL_SIZE; - } - - public static short getType(byte[] data) { - if (data.length < CONTROL_SIZE) return 0; - return readShortBigEndianData(data, 0x04); - } - - public static short getSeq(byte[] data) { - if (data.length < CONTROL_SIZE) return 0; - return readShortBigEndianData(data, 0x06); - } - - public static int getSentId(byte[] data) { - if (data.length < CONTROL_SIZE) return 0; - return readIntBigEndianData(data, 0x08); - } - - public static int getRcvdId(byte[] data) { - if (data.length < CONTROL_SIZE) return 0; - return readIntBigEndianData(data, 0x0c); - } - public static void setRcvdId(byte[] data,int rcvdId){ - System.arraycopy(intToBigEndian(rcvdId),0x00,data,0x0c,4); - } - } - - public static class AudioPacket{ - /** - * quint32 len; // 0x00 - * quint16 type; // 0x04 - * quint16 seq; // 0x06 - * quint32 sentid; // 0x08 - * quint32 rcvdid; // 0x0c - * - * - * //接收的时候,ident=0x8116 ,8106,8006 - * quint16 ident; // 0x10 发射的时候: 当datalen=0xa0时,ident=0x9781,否则ident=0x0080; - * quint16 sendseq; // 0x12 - * quint16 unused; // 0x14 - * quint16 datalen; // 0x16 - */ - public static boolean isAudioPacket(byte[] data){ - if (data.length 0) { - return count; - } else { - return count; - } - } - - /** - * 获取radioCap数据包,也许radioCap的数量不止一个,所以要用index来指定哪一个数据包 - * - * @param data 数据包 - * @param index 数据包的索引,以0为起点 - * @return radioCap数据包 - */ - public static byte[] getRadioCapPacket(byte[] data, int index) { - if (data.length < (CAPABILITIES_SIZE + RADIO_CAP_SIZE * (index + 1))) - return null;//如果小于最小长度,就返回空 - byte[] packet = new byte[0x66]; - System.arraycopy(data, CAPABILITIES_SIZE + RADIO_CAP_SIZE * index, packet, 0, RADIO_CAP_SIZE); - return packet; - } - } - - /** - * 电台参数数据包(0x66长度),它是在Capabilities(0x42长度)包的后面,如果是一个,总数据包的长度是0xA8, - */ - public static class RadioCapPacket { - /* - union { - struct { - quint8 unusede[7]; // 0x00 - quint16 commoncap; // 0x07 - quint8 unused; // 0x09 - quint8 macaddress[6]; // 0x0a - }; - quint8 guid[GUIDLEN]; // 0x0 - }; - char name[32]; // 0x10 - char audio[32]; // 0x30 - quint16 conntype; // 0x50 - char civ; // 0x52 - quint16 rxsample; // 0x53 - quint16 txsample; // 0x55 - quint8 enablea; // 0x57 - quint8 enableb; // 0x58 - quint8 enablec; // 0x59 - quint32 baudrate; // 0x5a - quint16 capf; // 0x5e - char unusedi; // 0x60 - quint16 capg; // 0x61 - char unusedj[3]; // 0x63 - */ - - /** - * 获取电台名称 - * - * @param data 0x66数据包 - * @return 名称 - */ - public static String getRigName(byte[] data) { - byte[] rigName = new byte[32]; - System.arraycopy(data, 0x10, rigName, 0, 32); - return new String(rigName).trim(); - } - - public static String getAudioName(byte[] data) { - byte[] audioName = new byte[32]; - System.arraycopy(data, 0x30, audioName, 0, 32); - return new String(audioName).trim(); - } - - public static byte getCivAddress(byte[] data) { - return data[0x52]; - } - - public static short getRxSupportSample(byte[] data) { - return readShortData(data, 0x53); - } - - public static short getTxSupportSample(byte[] data) { - return readShortData(data, 0x55); - } - - public static boolean getSupportTX(byte[] data) { - return data[0x57] == 0x01; - } - - } - - /** - * TOKEN(0x40)数据包。 - * 发送时,requestType=0x02是令牌确认,发送0x01是删除令牌。 - * 接收时,requestType=0x05,且requestReply=0x02&&type=0x01说明是电台令牌续订操作,这时要判断response的值。 - * response=0x00 00 00 00说明时续订成功,response=0xff ff ff ff说明时拒绝续订,此时应记录下RemoteId、 - * Token,tokRequest的值,关闭各端口,重新开始登录操作 - */ - public static class TokenPacket { - /* - public int len = TOKEN_SIZE; // 0x00 (int32) - public short type; // 0x04(int16) - public short seq; // 0x06(int16) - public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 - public int rcvdId; // 0x0c(int32) - public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 - public short payloadSize = TOKEN_SIZE - 0x10;// 0x12(int16) 负载长度,是包长度-包头16字节 - public byte requestReply; // 0x14(int8) - public byte requestType; // 0x15(int8) - public short innerSeq; // 0x16(int16) - public byte[] unusedB = new byte[2];// 0x18(char[2] - public short tokRequest; // 0x1a(int16) - public int token; // 0x1c(int32) - public short tokRequest; // 0x20 - public byte[] unusedG = new byte[5]; // 0x22 - public short commonCap;//0x27 - public byte unusedH;//0x29 - public byte[] macAddress=new byte[6];//0x2a - public byte[] guid=new byte[16];//0x20 - public int response;//0x30 - public byte[] unusedE=new byte[12];//0x34 - */ - - /** - * 生成Token数据包 - * - * @param seq 序列号 - * @param localSID 主机的ID - * @param remoteSID 电台的ID - * @param requestType 发送0x02是令牌确认,发送0x01是删除令牌。如果接收0x05,且requestReply=0x02&&type=0x01 - * 说明是电台令牌续订成功 - * @param authInnerSendSeq 内部序列号 - * @param tokRequest 主机的TOKEN - * @param token 电台的TOKEN - * @return 数组 - */ - public static byte[] getTokenPacketData( - short seq, int localSID, int remoteSID, byte requestType - , short authInnerSendSeq, short tokRequest, int token) { - byte[] packet = new byte[TOKEN_SIZE]; - System.arraycopy(intToBigEndian(TOKEN_SIZE), 0, packet, 0, 4); //len int32 0x00 - //System.arraycopy(shortToBigEndian((short) 0), 0, packet, 4, 2);//type int16 0x04 - System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); //seq int16 0x06 - System.arraycopy(intToBigEndian(localSID), 0, packet, 8, 4); //localId int32 0x08 - System.arraycopy(intToBigEndian(remoteSID), 0, packet, 12, 4); //remoteId int32 0x0c - //System.arraycopy({0x00,0x00}, 0, packet, 16, 2); //unusedA byte[2] 0x10 - System.arraycopy(shortToByte((short) (TOKEN_SIZE - 0x10)) - , 0, packet, 18, 2); //payloadSize int16 0x12 - packet[20] = 0x01; //requestReply byte 0x14 - packet[21] = requestType; //requestType byte 0x15 - System.arraycopy(shortToByte(authInnerSendSeq), 0, packet, 22, 2);//innerSeq int16 0x16 - //System.arraycopy(unusedB, 0, packet, 24, 2); //unusedB byte[2] 0x18 - System.arraycopy(shortToByte(tokRequest), 0, packet, 26, 2);//tokRequest int16 0x1a - System.arraycopy(intToByte(token), 0, packet, 28, 4); //token int32 0x1c - //后面其余的部分为用0填充 - return packet; - } - - public static byte[] getRenewalPacketData( - short seq, int localSID, int remoteSID - , short authInnerSendSeq, short tokRequest, int token - ) { - return getTokenPacketData(seq, localSID, remoteSID, TOKEN_TYPE_RENEWAL, authInnerSendSeq, tokRequest, token); - } - - /** - * 检查是不是令牌续订成功,条件:requestType=0x05,且requestReply=0x02&&type=0x01&&response=0x00 - * - * @param data 数据包 - * @return 是否续订成功 - */ - public static boolean TokenRenewalOK(byte[] data) { - byte requestType = data[0x15]; - byte requestReply = data[0x14]; - short type = readShortBigEndianData(data, 0x04); - int response = readIntData(data, 0x30); - return requestType == 0x05 && requestReply == 0x02 && type == 0x01 && response == 0x00; - } - - public static byte getRequestType(byte[] data) { - return data[21];//0x15 - } - - public static byte getRequestReply(byte[] data) { - return data[20];//0x14 - } - - public static int getResponse(byte[] data) { - return readIntData(data, 0x30); - } - - public static short getTokRequest(byte[] data) { - return readShortData(data, 26);//0x1a - } - - public static int getToken(byte[] data) { - return readIntData(data, 28);//0x1c - } - } - - - /** - * 登录回复包,0x60包,长度96; - */ - public static class LoginResponsePacket { - /* - public int len;// 0x00 (int32) - public short type; // 0x04(int16) - public short seq; // 0x06(int16) - public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 - public int rcvdId; // 0x0c(int32) - public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 - public short payloadSize;// 0x12(int16) 负载长度,是包长度-包头16字节 - public byte requestReply; // 0x14(int8) - public byte requestType; // 0x15(int8) - public short innerSeq; // 0x16(int16) - public byte[] unusedB = new byte[2];// 0x18(char[2] - public short tokRequest; // 0x1a(int16) - public int token; // 0x1c(int32) - public short authStartId; // 0x20 - public byte[] unusedD = new byte[14]; // 0x22 - public int error; // 0x30 - public byte[] unusedE = new byte[12]; // 0x34 - public byte[] connection = new byte[16]; // 0x40 - public byte[] unusedF = new byte[16]; // 0x50 - */ - - public static boolean authIsOK(byte[] data) { - return readIntData(data, 0x30) == 0x00; - } - - public static int errorNum(byte[] data) { - return readIntData(data, 0x30); - } - - public static int getToken(byte[] data) { - return readIntData(data, 0x1c); - } - - public static String getConnection(byte[] data) { - byte[] connection = new byte[16]; - System.arraycopy(data, 0x40, connection, 0, 16); - return new String(connection).trim(); - } - - } - - - /** - * 登录用数据包,0x80包,长度128 - */ - public static class LoginPacket { - /* - public int len = LOGIN_SIZE; // 0x00 (int32) - public short type; // 0x04(int16) - public short seq; // 0x06(int16) - public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 - public int rcvdId; // 0x0c(int32) - public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 - public short payloadSize = LOGIN_SIZE - 0x10;// 0x12(int16) 负载长度,是包长度-包头16字节 - public byte requestReply; // 0x14(int8) - public byte requestType; // 0x15(int8) - public short innerSeq; // 0x16(int16) - public byte[] unusedB = new byte[2];// 0x18(char[2] - public short tokRequest; // 0x1a(int16) - public int token; // 0x1c(int32) - public byte[] unusedC = new byte[32]; // 0x20(char[32]) - private byte[] userName = new byte[16]; // 0x40(char[16]) - private byte[] password = new byte[16]; // 0x50(char[16]) - private final byte[] name = new byte[16];// 0x60(char[16]) - public byte[] unusedF = new byte[16]; // 0x70(char[16]) - */ - - - /** - * 生成Login(0x80)数据包 - * - * @param seq 序号 - * @param localSID 主机ID - * @param remoteSID 电台ID - * @param authInnerSendSeq 内部序号 - * @param tokRequest 我的TOKEN - * @param userName 用户名(明文) - * @param password 密码(明文) - * @param name 终端的名称 - * @return 数据包 - */ - public static byte[] loginPacketData(short seq, int localSID, int remoteSID, short authInnerSendSeq - , short tokRequest, int token, String userName, String password, String name) { - byte[] packet = new byte[LOGIN_SIZE]; - System.arraycopy(intToBigEndian(LOGIN_SIZE), 0, packet, 0, 4); //len int32 0x00 - System.arraycopy(shortToBigEndian((short) 0), 0, packet, 4, 2);//type int16 0x04 - System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); //seq int 16 0x06 - System.arraycopy(intToBigEndian(localSID), 0, packet, 8, 4); //localId int32 0x08 - System.arraycopy(intToBigEndian(remoteSID), 0, packet, 12, 4); //remoteId int32 0x0c - //System.arraycopy({0x00,0x00}, 0, packet, 16, 2); //unusedA byte[2] 0x10 - System.arraycopy(shortToByte((short) (LOGIN_SIZE - 0x10)) - , 0, packet, 18, 2);//payloadSize,int16,大端模式,是包长度-包头16字节 0x12 - packet[20] = 0x01;//requestReply byte 0x14 - packet[21] = 0x00;//requestType byte 0x15 - System.arraycopy(shortToByte(authInnerSendSeq), 0, packet, 22, 2);//innerSeq int16 x016 - //System.arraycopy(unusedB, 0, packet, 24, 2); //unusedB byte[2] 0x18 - System.arraycopy(shortToByte(tokRequest), 0, packet, 26, 2);//tokRequest int16 0x1a,我的token - System.arraycopy(intToByte(token), 0, packet, 28, 4);//token int32 0x1c,电台的token - //System.arraycopy(unusedC, 0, packet, 32, 32); //unusedC byte[32] 0x0x20 - System.arraycopy(passCode(userName), 0, packet, 64, 16);//userName byte[16] 0x40,用户名密文 - System.arraycopy(passCode(password), 0, packet, 80, 16);//password byte[16] 0x50,密码密文 - System.arraycopy(stringToByte(name, 16), 0, packet, 96, 16);//name byte[16] 0x60,主机的名称 - //System.arraycopy(unusedF, 0, packet, 112, 16); //unusedF byte[16] 0x70 - return packet; - } - - } - - - /** - * 用户名、密码加密算法,最长只支持到16位。 - * - * @param pass 用户名或密码 - * @return 密文,最长16位 - */ - public static byte[] passCode(String pass) { - byte[] sequence =//字典 - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0x47, 0x5d, 0x4c, 0x42, 0x66, 0x20, 0x23, 0x46, 0x4e, 0x57, 0x45, 0x3d, 0x67, 0x76, 0x60, 0x41, 0x62, 0x39, 0x59, 0x2d, 0x68, 0x7e, - 0x7c, 0x65, 0x7d, 0x49, 0x29, 0x72, 0x73, 0x78, 0x21, 0x6e, 0x5a, 0x5e, 0x4a, 0x3e, 0x71, 0x2c, 0x2a, 0x54, 0x3c, 0x3a, 0x63, 0x4f, - 0x43, 0x75, 0x27, 0x79, 0x5b, 0x35, 0x70, 0x48, 0x6b, 0x56, 0x6f, 0x34, 0x32, 0x6c, 0x30, 0x61, 0x6d, 0x7b, 0x2f, 0x4b, 0x64, 0x38, - 0x2b, 0x2e, 0x50, 0x40, 0x3f, 0x55, 0x33, 0x37, 0x25, 0x77, 0x24, 0x26, 0x74, 0x6a, 0x28, 0x53, 0x4d, 0x69, 0x22, 0x5c, 0x44, 0x31, - 0x36, 0x58, 0x3b, 0x7a, 0x51, 0x5f, 0x52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - - }; - - byte[] passBytes = pass.getBytes(); - byte[] enCodePass = new byte[16];//密文不能超过16字节 - - for (int i = 0; i < passBytes.length && i < 16; i++) { - int p = (passBytes[i] + i) & 0xff;//防止出现负数 - if (p > 126) { - p = 32 + p % 127; - } - enCodePass[i] = sequence[p]; - } - return enCodePass; - } - - /** - * 把字符串转指定长度的byte数组 - * - * @param s 字符串 - * @param len 长度 - * @return 数组 - */ - public static byte[] stringToByte(String s, int len) { - byte[] buf = new byte[len]; - byte[] temp = s.getBytes(); - for (int i = 0; i < temp.length; i++) { - if (i > len) break; - buf[i] = temp[i]; - } - return buf; - } - - /** - * 把int转为小端模式,(高位在结尾) - * - * @param n int - * @return 数组 - */ - public static byte[] intToBigEndian(int n) { - byte[] b = new byte[4]; - b[0] = (byte) (n & 0xff); - b[1] = (byte) (n >> 8 & 0xff); - b[2] = (byte) (n >> 16 & 0xff); - b[3] = (byte) (n >> 24 & 0xff); - return b; - } - - /** - * int32转换成4字节数组 - * - * @param n int32 - * @return 数组 - */ - public static byte[] intToByte(int n) { - byte[] b = new byte[4]; - b[3] = (byte) (n & 0xff); - b[2] = (byte) (n >> 8 & 0xff); - b[1] = (byte) (n >> 16 & 0xff); - b[0] = (byte) (n >> 24 & 0xff); - return b; - } - - /** - * 从流数据中读取小端模式的Int32 - * - * @param data 流数据 - * @param start 起始点 - * @return Int32 - */ - public static int readIntBigEndianData(byte[] data, int start) { - if (data.length - start < 4) return 0; - return (int) data[start] & 0xff - | ((int) data[start + 1] & 0xff) << 8 - | ((int) data[start + 2] & 0xff) << 16 - | ((int) data[start + 3] & 0xff) << 24; - } - - public static int readIntData(byte[] data, int start) { - if (data.length - start < 4) return 0; - return (int) data[start + 3] & 0xff - | ((int) data[start + 2] & 0xff) << 8 - | ((int) data[start + 1] & 0xff) << 16 - | ((int) data[start] & 0xff) << 24; - } - - /** - * 从流数据中读取小端模式的Short - * - * @param data 流数据 - * @param start 起始点 - * @return Int16 - */ - public static short readShortBigEndianData(byte[] data, int start) { - if (data.length - start < 2) return 0; - return (short) ((short) data[start] & 0xff - | ((short) data[start + 1] & 0xff) << 8); - } - - /** - * 把字节转换成short,不做小端转换!! - * - * @param data 字节数据 - * @return short - */ - public static short readShortData(byte[] data, int start) { - if (data.length - start < 2) return 0; - return (short) ((short) data[start + 1] & 0xff - | ((short) data[start] & 0xff) << 8); - } - - /** - * 把short转为小端模式,(高位在结尾) - * - * @param n short - * @return 数组 - */ - public static byte[] shortToBigEndian(short n) { - byte[] b = new byte[2]; - b[0] = (byte) (n & 0xff); - b[1] = (byte) (n >> 8 & 0xff); - return b; - } - - /** - * 把short转成byte数组 - * - * @param n short - * @return 数组 - */ - public static byte[] shortToByte(short n) { - byte[] b = new byte[2]; - b[1] = (byte) (n & 0xff); - b[0] = (byte) (n >> 8 & 0xff); - return b; - } - - /** - * 显示16进制内容 - * - * @param data 数组 - * @return 内容 - */ - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%02x ", data[i] & 0xff)); - } - return s.toString(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java deleted file mode 100644 index 27e3fdc..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * WIFI模式下电台操作。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.media.AudioAttributes; -import android.media.AudioFormat; -import android.media.AudioTrack; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.icom.IcomUdpBase.IcomUdpStyle; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.io.IOException; - -public class IComWifiRig { - public interface OnIComDataEvents{ - void onReceivedCivData(byte[] data); - void onReceivedWaveData(byte[] data); - } - private IcomControlUdp controlUdp; - private AudioTrack audioTrack = null; - private final String ip; - private final int port; - private final String userName; - private final String password; - public boolean opened=false; - public boolean isPttOn=false; - - private OnIComDataEvents onIComDataEvents; - - - - public IComWifiRig(String ip, int port, String userName, String password) { - this.ip = ip; - this.port = port; - this.userName = userName; - this.password = password; - } - - - public void start(){ - opened=true; - openAudio();//打开音频 - controlUdp=new IcomControlUdp(userName,password,ip,port); - //设置事件,这里可以处理电台状态,和接收电台送来的音频数据 - controlUdp.setOnStreamEvents(new IcomUdpBase.OnStreamEvents() { - @Override - public void OnReceivedIAmHere(byte[] data) { - - } - - @Override - public void OnReceivedCivData(byte[] data) { - if (onIComDataEvents!=null){ - onIComDataEvents.onReceivedCivData(data); - } - } - - @Override - public void OnReceivedAudioData(byte[] audioData) { - if (onIComDataEvents!=null){ - onIComDataEvents.onReceivedWaveData(audioData); - } - if (audioTrack!=null){ - // if (!isPttOn) {//如果ptt没有按下 - audioTrack.write(audioData, 0, audioData.length - , AudioTrack.WRITE_NON_BLOCKING); - // } - } - } - - @Override - public void OnUdpSendIOException(IcomUdpStyle style,IOException e) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - R.string.network_exception),IcomUdpBase.getUdpStyle(style),e.getMessage())); - close(); - } - - @Override - public void OnLoginResponse(boolean authIsOK) { - if (authIsOK){ - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.login_succeed)); - }else { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.loging_failed)); - controlUdp.closeAll(); - } - } - - }); - controlUdp.openStream();//打开端口 - controlUdp.startAreYouThereTimer();//开始连接电台 - } - - public void setPttOn(boolean on){//打开PTT - isPttOn=on; - controlUdp.civUdp.sendPttAction(on); - controlUdp.audioUdp.isPttOn=on; - } - public void sendCivData(byte[] data){ - controlUdp.civUdp.sendCivData(data); - } - - public void sendWaveData(float[] data){//发送音频数据到电台 - controlUdp.sendWaveData(data); - } - - /** - * 关闭各种连接,以及音频 - */ - public void close(){ - opened=false; - controlUdp.closeAll(); - closeAudio(); - //controlUdp.closeStream(); - } - /** - * 打开音频,流方式。当收到音频流的时候,播放数据 - */ - public void openAudio() { - if (audioTrack!=null) closeAudio(); - - AudioAttributes attributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) - .build(); - AudioFormat myFormat = new AudioFormat.Builder().setSampleRate(IComPacketTypes.AUDIO_SAMPLE_RATE) - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); - int mySession = 0; - audioTrack = new AudioTrack(attributes, myFormat - , IComPacketTypes.AUDIO_SAMPLE_RATE/4, AudioTrack.MODE_STREAM - , mySession); - audioTrack.play(); - } - /** - * 关闭音频 - */ - public void closeAudio() { - if (audioTrack != null) { - audioTrack.stop(); - audioTrack = null; - } - } - - public void setOnIComDataEvents(OnIComDataEvents onIComDataEvents) { - this.onIComDataEvents = onIComDataEvents; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java deleted file mode 100644 index d0f1242..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * 处理ICom的音频流。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; - -import java.net.DatagramPacket; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; - -public class IcomAudioUdp extends IcomUdpBase { - private static final String TAG = "IcomAudioUdp"; - - public IcomAudioUdp() { - udpStyle = IcomUdpStyle.AudioUdp; - } - private final ExecutorService doTXThreadPool =Executors.newCachedThreadPool(); - private final DoTXAudioRunnable doTXAudioRunnable=new DoTXAudioRunnable(this); - - @Override - public void onDataReceived(DatagramPacket packet, byte[] data) { - super.onDataReceived(packet, data); - - if (!IComPacketTypes.AudioPacket.isAudioPacket(data)) return; - byte[] audioData = IComPacketTypes.AudioPacket.getAudioData(data); - if (onStreamEvents != null) { - onStreamEvents.OnReceivedAudioData(audioData); - } - } - - - public void sendTxAudioData(float[] audioData) { - if (audioData==null) return; - - short[] temp=new short[audioData.length]; - //要做一下浮点到16位int的转换 - for (int i = 0; i < audioData.length; i++) { - float x = audioData[i]; - if (x > 1.0) - x = 1.0f; - else if (x < -1.0) - x = -1.0f; - temp[i] = (short) (x * 32767.0); - } - doTXAudioRunnable.audioData=temp; - doTXThreadPool.execute(doTXAudioRunnable); - } - private static class DoTXAudioRunnable implements Runnable{ - IcomAudioUdp icomAudioUdp; - short[] audioData; - - public DoTXAudioRunnable(IcomAudioUdp icomAudioUdp) { - this.icomAudioUdp = icomAudioUdp; - } - - @Override - public void run() { - if (audioData==null) return; - - final int partialLen = IComPacketTypes.TX_BUFFER_SIZE * 2;//数据包的长度 - //要转换一下到BYTE,小端模式 - - //byte[] data = new byte[audioData.length * 2 + partialLen * 4];//多出一点空声音放在前后各20ms*2共80ms - //先播放,是给出空的声音,for i 循环,做了一个判断,是给前面的空声音,for j循环,做得判断,是让后面发送空声音 - byte[] audioPacket = new byte[partialLen]; - for (int i = 0; i < (audioData.length / IComPacketTypes.TX_BUFFER_SIZE) + 8; i++) {//多出6个周期,前面3个,后面3个多 - if (!icomAudioUdp.isPttOn) break; - long now = System.currentTimeMillis() - 1;//获取当前时间 - - icomAudioUdp.sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket - , (short) 0, icomAudioUdp.localId, icomAudioUdp.remoteId, icomAudioUdp.innerSeq)); - icomAudioUdp.innerSeq++; - - Arrays.fill(audioPacket,(byte)0x00); - if (i>=3) {//让前两个空数据发送出去 - for (int j = 0; j < IComPacketTypes.TX_BUFFER_SIZE; j++) { - if ((i-3) * IComPacketTypes.TX_BUFFER_SIZE + j < audioData.length) { - System.arraycopy(IComPacketTypes.shortToBigEndian((short) - (audioData[(i-3) * IComPacketTypes.TX_BUFFER_SIZE + j] - * GeneralVariables.volumePercent))//乘以信号量的比率 - , 0, audioPacket, j * 2, 2); - } - } - } - while (icomAudioUdp.isPttOn) { - if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期 - break; - } - } - } - Log.e(TAG, "run: 音频发送完毕!!" ); - Thread.currentThread().interrupt(); - } - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java deleted file mode 100644 index 75fd2df..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * 处理ICom的CIV流。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import com.bg7yoz.ft8cn.rigs.IcomRigConstant; - -import java.net.DatagramPacket; -import java.util.Timer; -import java.util.TimerTask; - -public class IcomCivUdp extends IcomUdpBase{ - private static final String TAG="IcomCivUdp"; - public byte civAddress=(byte)0xA4; - public boolean supportTX=true; - public short civSeq=0; - - - private Timer openCivDataTimer; - - - public IcomCivUdp() { - udpStyle=IcomUdpStyle.CivUdp; - } - - @Override - public void onDataReceived(DatagramPacket packet,byte[] data) { - super.onDataReceived(packet,data); - - if (data.length == IComPacketTypes.CONTROL_SIZE) { - if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) { - Log.d(TAG, "onDataReceived: civ I am ready!!"); - sendOpenClose(true);//打开连接 - startIdleTimer();//打开发送空包时钟 - startCivDataTimer();//启动civ看门狗 - - } - } else { - if (IComPacketTypes.ControlPacket.getType(data) != IComPacketTypes.CMD_PING) { - Log.d(TAG, "onDataReceived: CIV :" + IComPacketTypes.byteToStr(data)); - checkCivData(data); - } - if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_RETRANSMIT) { - Log.d(TAG, "onDataReceived: type=0x01"+IComPacketTypes.byteToStr(data) ); - //lastReceived=System.currentTimeMillis();//更新一下最后接收数据的时间,让watchDog处理 - } - - } - } - - public void checkCivData(byte[] data){ - if (IComPacketTypes.CivPacket.checkIsCiv(data)){ - lastReceivedTime=System.currentTimeMillis(); - if (getOnStreamEvents()!=null){ - getOnStreamEvents().OnReceivedCivData(IComPacketTypes.CivPacket.getCivData(data)); - } - } - } - - public void sendOpenClose(boolean open){ - if (open) { - sendTrackedPacket(IComPacketTypes.OpenClosePacket.toBytes((short) 0 - , localId, remoteId, civSeq,(byte) 0x04));//打开连接 - }else { - sendTrackedPacket(IComPacketTypes.OpenClosePacket.toBytes((short) 0 - , localId, remoteId, civSeq,(byte) 0x00));//关闭连接 - } - civSeq++; - } - public void startCivDataTimer(){ - stopTimer(openCivDataTimer); - Log.d(TAG, String.format("openCivDataTimer: local port:%d,remote port %d", localPort, rigPort)); - openCivDataTimer = new Timer(); - openCivDataTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (System.currentTimeMillis()-lastReceivedTime>2000) { - sendOpenClose(true); - } - } - }, 100, IComPacketTypes.OPEN_CLOSE_PERIOD_MS); - } - - - public void sendPttAction(boolean pttOn){ - if (pttOn) { - sendCivData(IcomRigConstant.setPTTState(0xe0, civAddress, IcomRigConstant.PTT_ON)); - }else { - sendCivData(IcomRigConstant.setPTTState(0xe0, civAddress, IcomRigConstant.PTT_OFF)); - } - } - - public void sendCivData(byte[] data){ - sendTrackedPacket(IComPacketTypes.CivPacket.setCivData((short) 0,localId,remoteId,civSeq,data)); - civSeq++; - } - - @Override - public void close() { - super.close(); - sendOpenClose(false); - stopTimer(openCivDataTimer); - stopTimer(idleTimer); - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java deleted file mode 100644 index 6579afe..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * ICom的控制流。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import java.net.DatagramPacket; -import java.util.Timer; -import java.util.TimerTask; - -public class IcomControlUdp extends IcomUdpBase { - private static final String TAG = "IcomControlUdp"; - public final String APP_NAME = "FT8CN"; - - //与采样率有关,每20ms发送的样本数12000/50=240=F0,实际字节数是(16bit),还要乘以2,也就是480字节 - - - public Timer tokenTimer;//续订令牌的时钟 - - public String userName; - public String password; - public String rigName = ""; - public String audioName = ""; - public byte[] rigMacAddress=new byte[6];//0xA8、0x90包中提供 - public String connectionMode = ""; - - public boolean gotAuthOK = false;//token认证通过了 - public boolean isAuthenticated = false;//登录成功 - public boolean rigIsBusy = false; - - public IcomCivUdp civUdp; - public IcomAudioUdp audioUdp; - - - public IcomControlUdp(String userName, String password, String remoteIp, int remotePort) { - udpStyle=IcomUdpStyle.ControlUdp; - this.userName = userName; - this.password = password; - - this.rigIp = remoteIp; - this.rigPort = remotePort; - - civUdp = new IcomCivUdp(); - audioUdp = new IcomAudioUdp(); - civUdp.rigIp = remoteIp; - audioUdp.rigIp = remoteIp; - civUdp.openStream(); - audioUdp.openStream(); - } - - @Override - public void onDataReceived(DatagramPacket packet,byte[] data) { - super.onDataReceived(packet,data); - switch (data.length) { - case IComPacketTypes.CONTROL_SIZE://在父类中已经实现0x04,0x01指令 - if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_HERE) { - rigIp=packet.getAddress().getHostAddress(); - //civUdp.rigIp=packet.getAddress().getHostAddress(); - //audioUdp.rigIp=packet.getAddress().getHostAddress(); - } //如果电台回复I'm ready,就发起login - if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) { - sendLoginPacket();//电台准备好了,申请登录 - startIdleTimer();//打开发送空包时钟 - } - break; - case IComPacketTypes.TOKEN_SIZE://处理令牌的续订之类的事情 - onReceiveTokenPacket(data); - break; - case IComPacketTypes.STATUS_SIZE://0x50电台回复我它的参数:CivPort,AudioPort等 - onReceiveStatusPacket(data); - break; - case IComPacketTypes.LOGIN_RESPONSE_SIZE://0x60电台回复登录的请求 - onReceiveLoginResponse(data); - break; - case IComPacketTypes.CONNINFO_SIZE://电台会回复2次0x90包,区别在于busy字段 - onReceiveConnInfoPacket(data); - break; - case IComPacketTypes.CAP_CAPABILITIES_SIZE://0xA8数据包,返回civ地址 - byte[] audioCap = IComPacketTypes.CapCapabilitiesPacket.getRadioCapPacket(data, 0); - if (audioCap!=null) { - civUdp.supportTX = IComPacketTypes.RadioCapPacket.getSupportTX(audioCap); - civUdp.civAddress = IComPacketTypes.RadioCapPacket.getCivAddress(audioCap); - audioName = IComPacketTypes.RadioCapPacket.getAudioName(audioCap); - } - break; - } - } - - /** - * 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=0,第二次busy=1。 - * 在0x90数据包中取macAddress,电台名称 - * - * @param data 0x90数据包 - */ - public void onReceiveConnInfoPacket(byte[] data) { - rigMacAddress = IComPacketTypes.ConnInfoPacket.getMacAddress(data); - rigIsBusy = IComPacketTypes.ConnInfoPacket.getBusy(data); - rigName = IComPacketTypes.ConnInfoPacket.getRigName(data); - //if (!rigIsBusy) {//说明是第一次收到0x90数据包,要回复一个x090数据包 - Log.e(TAG, "onReceiveConnInfoPacket: send 0x90"); - sendTrackedPacket( - IComPacketTypes.ConnInfoPacket.connInfoPacketData(data, (short) 0 - , localId, remoteId - , (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken - , rigName, userName - , IComPacketTypes.AUDIO_SAMPLE_RATE//48000采样率 - , civUdp.localPort, audioUdp.localPort - , IComPacketTypes.TX_BUFFER_SIZE)); - innerSeq++; - //} - } - - /** - * 处理电台回复登录数据包 - * - * @param data 0x60数据包 - */ - public void onReceiveLoginResponse(byte[] data) { - if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; - connectionMode = IComPacketTypes.LoginResponsePacket.getConnection(data); - Log.d(TAG, "connection mode:" + connectionMode); - if (IComPacketTypes.LoginResponsePacket.authIsOK(data)) {//errorCode=0x00,认证成功 - Log.d(TAG, "onReceiveLoginResponse: Login succeed!"); - if (!isAuthenticated) { - rigToken = IComPacketTypes.LoginResponsePacket.getToken(data); - Log.d(TAG, "onReceiveLoginResponse: send token confirm 0x02"); - sendTokenPacket(IComPacketTypes.TOKEN_TYPE_CONFIRM);//发送令牌确认包 - startTokenTimer();//启动令牌续订时钟 - isAuthenticated = true; - } - } - if (onStreamEvents!=null){//触发认证事件 - onStreamEvents.OnLoginResponse(IComPacketTypes.LoginResponsePacket.authIsOK(data)); - } - } - - /** - * 处理电台回复我的参数。0x50数据包 - * - * @param data 0x50数据包 - */ - public void onReceiveStatusPacket(byte[] data) { - if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; - if (IComPacketTypes.StatusPacket.getAuthOK(data) - && IComPacketTypes.StatusPacket.getIsConnected(data)) {//令牌认证成功,且处于连接状态 - audioUdp.rigPort = IComPacketTypes.StatusPacket.getRigAudioPort(data); - audioUdp.rigIp = rigIp; - civUdp.rigPort = IComPacketTypes.StatusPacket.getRigCivPort(data); - civUdp.rigIp = rigIp; - Log.e(TAG, String.format("onReceiveStatusPacket: Status packet 0x50: civRigPort:%d,audioRigPort:%d" - ,civUdp.rigPort,audioUdp.rigPort )); - civUdp.startAreYouThereTimer();//civ端口启动连接电台 - audioUdp.startAreYouThereTimer();//audio端口启动连接电台 - }//else处理关闭连接??? - } - - /** - * 处理令牌数据包 - * - * @param data 0x40数据包 - */ - public void onReceiveTokenPacket(byte[] data) { - //看是不是续订令牌包 - if (IComPacketTypes.TokenPacket.getRequestType(data) == IComPacketTypes.TOKEN_TYPE_RENEWAL - && IComPacketTypes.TokenPacket.getRequestReply(data) == 0x02 - && IComPacketTypes.ControlPacket.getType(data) != IComPacketTypes.CMD_RETRANSMIT) { - int response = IComPacketTypes.TokenPacket.getResponse(data); - if (response == 0x0000) {//说明续订成功了 - gotAuthOK = true; - } else if (response == 0xffffffff) { - remoteId = IComPacketTypes.ControlPacket.getSentId(data); - localToken = IComPacketTypes.TokenPacket.getTokRequest(data); - rigToken = IComPacketTypes.TokenPacket.getToken(data); - sendConnectionRequest();//申请连接 - } else { - Log.e(TAG, "Token renewal failed,unknow response"); - } - } - } - - /** - * 发送音频数据到电台 - * @param data 数据 - */ - public void sendWaveData(float[] data){ - audioUdp.sendTxAudioData(data); - } - - /** - * 发送0x90数据包,向电台请求连接 - */ - public void sendConnectionRequest() { - sendTrackedPacket(IComPacketTypes.ConnInfoPacket.connectRequestPacket((short) 0 - , localId, remoteId, (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken - , rigMacAddress, rigName, userName, IComPacketTypes.AUDIO_SAMPLE_RATE - , civUdp.getLocalPort(), audioUdp.getLocalPort() - , IComPacketTypes.TX_BUFFER_SIZE)); - innerSeq++; - } - - /** - * 发送登录数据包 - */ - public void sendLoginPacket() { - sendTrackedPacket(IComPacketTypes.LoginPacket.loginPacketData((short) 0 - , localId, remoteId, innerSeq, localToken, rigToken, userName, password, APP_NAME)); - innerSeq++; - } - - @Override - public void setOnStreamEvents(OnStreamEvents onStreamEvents) { - super.setOnStreamEvents(onStreamEvents); - audioUdp.onStreamEvents=onStreamEvents; - civUdp.onStreamEvents=onStreamEvents; - } - - /** - * 启动令牌续订时钟 - */ - public void startTokenTimer() { - stopTimer(tokenTimer); - Log.d(TAG, String.format("start Toke Timer: local port:%d,remote port %d", localPort, rigPort)); - tokenTimer = new Timer(); - tokenTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - sendTokenPacket(IComPacketTypes.TOKEN_TYPE_RENEWAL); - } - }, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS); - } - public void closeAll(){ - sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short)0 - ,localId,remoteId,IComPacketTypes.TOKEN_TYPE_DELETE,innerSeq,localToken,rigToken)); - innerSeq++; - this.close(); - civUdp.close(); - audioUdp.close(); - - civUdp.sendOpenClose(false); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java deleted file mode 100644 index 54ba904..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * ICom指令数据的缓存。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import java.util.ArrayList; - -public class IcomSeqBuffer { - private long last=System.currentTimeMillis(); - - public static class SeqBufEntry { - public short seq;//序号 - public byte[] data;//数据 - public long addedAt;//添加的时间,最多保存10秒钟 - - public SeqBufEntry(short seq, byte[] data) { - this.seq = seq; - this.data = data; - addedAt = System.currentTimeMillis(); - } - } - - public ArrayList entries = new ArrayList<>(); - - /** - * 添加指令号缓存 - * - * @param seq 序号 - * @param data 指令 - */ - public synchronized void add(short seq, byte[] data) { - entries.add(new SeqBufEntry(seq, data)); - last=System.currentTimeMillis(); - purgeOldEntries();//要删除多余的历史记录 - } - - /** - * 弹出旧的指令,保证指令在缓存的限定范围内 - */ - public void purgeOldEntries() { - if (entries.size() == 0) return; - long now=System.currentTimeMillis(); - for (int i = entries.size()-1; i >=0 ; i--) {//删除超过10秒的历史记录 - if (now-entries.get(i).addedAt>IComPacketTypes.PURGE_MILLISECONDS){ - entries.remove(i); - } - } - - //while (entries.size() > MaxBufferCount) { - // entries.remove(0); - //} - } - - /** - * 按序号查找缓存中是否有历史指令 - * - * @param seqNum 序号 - * @return 指令数据,如果没有为NULL。 - */ - public synchronized byte[] get(int seqNum) { - int founded = -1; - for (int i = entries.size() - 1; i >= 0; i--) { - if (entries.get(i).seq == seqNum) { - founded = i; - } - } - if (founded != -1) { - return entries.get(founded).data; - } else { - return null; - } - } - public long getTimeOut(){ - return System.currentTimeMillis()-last; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java deleted file mode 100644 index caf4560..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java +++ /dev/null @@ -1,430 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * 简单封装的udp流处理。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Timer; -import java.util.TimerTask; - -public class IcomUdpBase { - public enum IcomUdpStyle{//数据流的类型 - UdpBase, - ControlUdp, - CivUdp, - AudioUdp - } - public static String getUdpStyle(IcomUdpStyle style){ - switch (style){ - case ControlUdp: - return GeneralVariables.getStringFromResource(R.string.control_stream); - case CivUdp: - return GeneralVariables.getStringFromResource(R.string.civ_stream); - case AudioUdp: - return GeneralVariables.getStringFromResource(R.string.audio_stream); - default: - return GeneralVariables.getStringFromResource(R.string.data_stream); - } - } - /** - * 事件接口 - */ - public interface OnStreamEvents { - void OnReceivedIAmHere(byte[] data); - void OnReceivedCivData(byte[] data); - void OnReceivedAudioData(byte[] audioData); - void OnUdpSendIOException(IcomUdpStyle style,IOException e); - void OnLoginResponse(boolean authIsOK); - //void OnWatchDogAlert(IcomUdpStyle style,boolean isAlerted); - } - public IcomUdpStyle udpStyle=IcomUdpStyle.UdpBase; - - private static final String TAG = "IcomUdpBase"; - public int rigPort; - public String rigIp; - public int localPort; - public int localId = (int) System.currentTimeMillis();//随机码,以时间为随机变量 - public int remoteId; - public boolean authDone = false;//登录 - public boolean rigReadyDone = false;//电台已经ready,control可以执行登录,ci-v可以执行open. - public short trackedSeq = 1;//因为are you there=0,are you ready=1。从are you ready之后才发track包 - public short pingSeq = 0;//ping的起始值是0 - public short innerSeq = 0x30; - public int rigToken;//电台提供的令牌 - public short localToken = (short) System.currentTimeMillis();//本地生成的令牌,可以是随机数 - public boolean isPttOn=false; - - - - public IcomSeqBuffer txSeqBuffer = new IcomSeqBuffer();//发送命令的历史列表 - //public IcomSeqBuffer rxSeqBuffer = new IcomSeqBuffer();//接收命令的历史列表 - public long lastReceivedTime=System.currentTimeMillis();//最后收到数据的时间 - public long lastSentTime=System.currentTimeMillis();//最后收到数据的时间 - - - public IcomUdpClient udpClient;//用于与电台通讯的udp - - - public OnStreamEvents onStreamEvents;//一些事件处理 - //Timer,执行代码部分:TimerTask,具体执行:timer.schedule(task,delay,period) - public Timer areYouThereTimer; - public Timer pingTimer; - public Timer idleTimer;//发送空数据包的时钟 - - - public void close(){ - onStreamEvents=null;//不用弹出网络异常的消息了 - sendUntrackedPacket(IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_DISCONNECT - ,(short)0,localId,remoteId)); - stopTimer(areYouThereTimer); - stopTimer(pingTimer); - stopTimer(idleTimer); - } - - /** - * 关闭udpClient - */ - public void closeStream(){ - if (udpClient!=null){ - try { - udpClient.setActivated(false); - } catch (SocketException e) { - e.printStackTrace(); - Log.e(TAG, "closeStream: "+e.getMessage() ); - } - } - } - /** - * 打开Udp流端口,如果Udp端口已经打开了,会再打开一次,本地端口应该会变化 - */ - public void openStream() {//打开 - if (udpClient == null) { - udpClient = new IcomUdpClient(-1); - } - udpClient.setOnUdpEvents(new IcomUdpClient.OnUdpEvents() { - @Override - public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { - //屏蔽掉非法的数据包 - if (data.length data.length - 1) break;//做一个保护,如果字节数不是偶数,防止数组下标溢出 - //重新传输指令 - retransmitPacket(IComPacketTypes.readShortBigEndianData(data, i)); - } - } - - - - /** - * 启动Are you there 时钟 - */ - public void startAreYouThereTimer() { - stopTimer(areYouThereTimer); - areYouThereTimer = new Timer(); - areYouThereTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - Log.d(TAG, String.format("AreYouThereTimer: local port:%d,remote port %d", localPort, rigPort)); - sendUntrackedPacket( - IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_ARE_YOU_THERE - , (short) 0, localId, 0)); - } - }, 0, IComPacketTypes.ARE_YOU_THERE_PERIOD_MS); - } - - /** - * 启动ping时钟 - */ - public void startPingTimer() { - stopTimer(pingTimer);//如果之前有打开的时钟,就关闭 - Log.d(TAG, String.format("start PingTimer: local port:%d,remote port %d", localPort, rigPort)); - pingTimer = new Timer(); - pingTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - sendPingPacket();//发送Ping包 - } - }, 0, IComPacketTypes.PING_PERIOD_MS);//500ms周期 - } - /** - * 发送空包时钟 - */ - public void startIdleTimer() { - stopTimer(idleTimer); - Log.d(TAG, String.format("start Idle Timer: local port:%d,remote port %d", localPort, rigPort)); - idleTimer = new Timer(); - idleTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (txSeqBuffer.getTimeOut()>200) {//当超过200毫秒没有发送指令,就发送一个空指令 - sendTrackedPacket( - IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_NULL - , (short) 0, localId, remoteId)); - } - } - }, IComPacketTypes.IDLE_PERIOD_MS, IComPacketTypes.IDLE_PERIOD_MS); - } - - /** - * 停止时钟 - * - * @param timer 时钟 - */ - public void stopTimer(Timer timer) { - if (timer != null) { - timer.cancel(); - timer.purge(); - timer = null; - } - } - - - public void onReceivedPingPacket(byte[] data) { - //两种情况,一种是电台ping我,另一个是电台回复我的Ping包 - if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_PING) { - if (IComPacketTypes.PingPacket.getReply(data) == 0x00) {//电台ping我 - sendReplyPingPacket(data);//回复电台Ping - } else {//回复我的ping,序号++ - if (IComPacketTypes.ControlPacket.getSeq(data) == pingSeq) { - pingSeq++; - } - } - } - } - - /** - * 发送令牌包0x40 - * @param requestType 令牌类型,0x02确认,0x05续订 - */ - public void sendTokenPacket(byte requestType){ - sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short)0 - ,localId,remoteId,requestType,innerSeq,localToken,rigToken)); - innerSeq++; - } - - /** - * 发Ping电台数据包 - */ - public void sendPingPacket() { - byte[] data = IComPacketTypes.PingPacket.sendPingData(localId, remoteId, pingSeq); - sendUntrackedPacket(data);//因为Ping包走自己的序列,所以发送unTracked包 - //pingSeq++;要在电台回复我之后,再自增 - } - - /** - * 回复电台的ping - * - * @param data 对方的ping数据 - */ - public void sendReplyPingPacket(byte[] data) { - byte[] packet = IComPacketTypes.PingPacket.sendReplayPingData(data, localId, remoteId); - sendUntrackedPacket(packet); - } - - /** - * 发送指令数据包 - * - * @param data 数据包 - */ - public synchronized void sendUntrackedPacket(byte[] data) { - try { - udpClient.sendData(data, rigIp, rigPort); - } catch (UnknownHostException e) { - e.printStackTrace(); - } - } - - /** - * 发送tracked数据包 - * - * @param data 数据包 - */ - public synchronized void sendTrackedPacket(byte[] data) { - try { - lastSentTime=System.currentTimeMillis(); - System.arraycopy(IComPacketTypes.shortToBigEndian(trackedSeq), 0 - , data, 6, 2);//把序号写到数据列表里 - udpClient.sendData(data, rigIp, rigPort); - txSeqBuffer.add(trackedSeq, data); - trackedSeq++; - } catch (UnknownHostException e) { - e.printStackTrace(); - } - } - - public int getLocalPort() { - return localPort; - } - - /** - * 发送空数据包,是Tracked发送。此函数,一般在idleTimer中调用。放在此处,是为了方便调用。 - */ - public void sendIdlePacket() { - //seq设置为0,是因为:在sendTrackedPacket中,会把trackedSeq写到数据包中 - sendTrackedPacket(IComPacketTypes.ControlPacket.idlePacketData((short) 0, localPort, remoteId)); - } - - - public OnStreamEvents getOnStreamEvents() { - return onStreamEvents; - } - - public void setOnStreamEvents(OnStreamEvents onStreamEvents) { - this.onStreamEvents = onStreamEvents; - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java deleted file mode 100644 index 36cf8c5..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.bg7yoz.ft8cn.icom; -/** - * 简单封装的udp协议处理 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.util.Log; - -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class IcomUdpClient { - private static final String TAG = "RadioUdpSocket"; - - - private final int MAX_BUFFER_SIZE = 1024 *2; - private DatagramSocket sendSocket; - //private int remotePort; - private int localPort=-1; - private boolean activated = false; - private OnUdpEvents onUdpEvents = null; - private final ExecutorService doReceiveThreadPool = Executors.newCachedThreadPool(); - private DoReceiveRunnable doReceiveRunnable=new DoReceiveRunnable(this); - private final ExecutorService sendDataThreadPool = Executors.newCachedThreadPool(); - private SendDataRunnable sendDataRunnable=new SendDataRunnable(this); - - public IcomUdpClient() {//本地端口随机 - localPort=-1; - } - public IcomUdpClient(int localPort) {//如果localPort==-1,本地端口随机 - this.localPort=localPort; - } - - public void sendData(byte[] data, String ip,int port) throws UnknownHostException { - if (!activated) return; - - InetAddress address = InetAddress.getByName(ip); - sendDataRunnable.address=address; - sendDataRunnable.data=data; - sendDataRunnable.port=port; - sendDataThreadPool.execute(sendDataRunnable); -// new Thread(new Runnable() { -// @Override -// public void run() { -// DatagramPacket packet = new DatagramPacket(data, data.length, address, port); -// synchronized (this) { -// try { -// sendSocket.send(packet); -// } catch (IOException e) { -// e.printStackTrace(); -// Log.e(TAG, "IComUdpClient: " + e.getMessage()); -// if (onUdpEvents!=null){ -// onUdpEvents.OnUdpSendIOException(e); -// } -// } -// } -// } -// }).start(); - } - private static class SendDataRunnable implements Runnable{ - byte[] data; - int port; - InetAddress address; - IcomUdpClient client; - - public SendDataRunnable(IcomUdpClient client) { - this.client = client; - } - - @Override - public void run() { - DatagramPacket packet = new DatagramPacket(data, data.length, address, port); - synchronized (this) { - try { - client.sendSocket.send(packet); - } catch (IOException e) { - e.printStackTrace(); - Log.e(TAG, "IComUdpClient: " + e.getMessage()); - if (client.onUdpEvents!=null){ - client.onUdpEvents.OnUdpSendIOException(e); - } - } - } - } - } - - public boolean isActivated() { - return activated; - } - - public synchronized void setActivated(boolean activated) throws SocketException { - this.activated = activated; - if (activated) {//通过activated判断是否结束接收线程,并清空sendSocket指针 - sendSocket = new DatagramSocket(); - //new DatagramSocket(null);//绑定的端口号随机 - sendSocket.setReuseAddress(true); - if (localPort!=-1) {//绑定指定的本机端口 - sendSocket.bind(new InetSocketAddress(localPort)); - } - - //更新一下本地端口值 - localPort=sendSocket.getLocalPort(); - Log.e(TAG, "openUdpPort: " + sendSocket.getLocalPort()); - //Log.e(TAG, "openUdpIp: " + sendSocket.getLocalAddress()); - - - receiveData(); - } else { - if (sendSocket != null) { - sendSocket.close(); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } - - private void receiveData() { - doReceiveThreadPool.execute(doReceiveRunnable); -// new Thread(new Runnable() { -// @Override -// public void run() { -// while (activated) { -// byte[] data = new byte[MAX_BUFFER_SIZE]; -// DatagramPacket packet = new DatagramPacket(data, data.length); -// try { -// sendSocket.receive(packet); -// if (onUdpEvents != null) { -// byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength()); -// onUdpEvents.OnReceiveData(sendSocket, packet, temp); -// } -// //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName()); -// } catch (IOException e) { -// e.printStackTrace(); -// Log.e(TAG, "receiveData: error:" + e.getMessage()); -// } -// -// } -// Log.e(TAG, "udpClient: is exit!"); -// sendSocket.close(); -// sendSocket = null; -// } -// }).start(); - - } - - public void setOnUdpEvents(OnUdpEvents onUdpEvents) { - this.onUdpEvents = onUdpEvents; - } - - public interface OnUdpEvents { - void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data); - void OnUdpSendIOException(IOException e); - } - - public int getLocalPort() { - if (sendSocket != null) { - return sendSocket.getLocalPort(); - } else { - return 0; - } - } - - public String getLocalIp() { - if (sendSocket != null) { - return sendSocket.getLocalAddress().toString(); - } else { - return "127.0.0.1"; - } - } - - public DatagramSocket getSendSocket() { - return sendSocket; - } - - - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%02x ", data[i] & 0xff)); - } - return s.toString(); - } - private static class DoReceiveRunnable implements Runnable{ - IcomUdpClient icomUdpClient; - - public DoReceiveRunnable(IcomUdpClient icomUdpClient) { - this.icomUdpClient = icomUdpClient; - } - - @Override - public void run() { - while (icomUdpClient.activated) { - byte[] data = new byte[icomUdpClient.MAX_BUFFER_SIZE]; - DatagramPacket packet = new DatagramPacket(data, data.length); - try { - icomUdpClient.sendSocket.receive(packet); - if (icomUdpClient.onUdpEvents != null) { - byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength()); - icomUdpClient.onUdpEvents.OnReceiveData(icomUdpClient.sendSocket, packet, temp); - } - //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName()); - } catch (IOException e) { - e.printStackTrace(); - Log.e(TAG, "receiveData: error:" + e.getMessage()); - } - - } - Log.e(TAG, "udpClient: is exit!"); - icomUdpClient.sendSocket.close(); - icomUdpClient.sendSocket = null; - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java deleted file mode 100644 index 1e8368e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.bg7yoz.ft8cn.log; -/** - * 用于记录SWL QSO所用的哈希表类型 ,因为要记录上方的呼号,所以要有2个String KEY,HashMap并不合适, - * 这里采用谷歌的guava:31.1-jre库 - * - * BG7YOZ - * 2023-03-20 - * - */ - -import androidx.annotation.Nullable; - -import com.google.common.collect.Table; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -public class HashTable implements Table { - @Override - public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { - return false; - } - - @Override - public boolean containsRow(@Nullable Object rowKey) { - return false; - } - - @Override - public boolean containsColumn(@Nullable Object columnKey) { - return false; - } - - @Override - public boolean containsValue(@Nullable Object value) { - return false; - } - - @Nullable - @Override - public @org.checkerframework.checker.nullness.qual.Nullable Object get(@Nullable Object rowKey, @Nullable Object columnKey) { - return null; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public int size() { - return 0; - } - - @Override - public void clear() { - - } - - @Nullable - @Override - public @org.checkerframework.checker.nullness.qual.Nullable Object put(Object rowKey, Object columnKey, Object value) { - return null; - } - - @Override - public void putAll(Table table) { - - } - - @Nullable - @Override - public @org.checkerframework.checker.nullness.qual.Nullable Object remove(@Nullable Object rowKey, @Nullable Object columnKey) { - return null; - } - - @Override - public Map row(Object rowKey) { - return null; - } - - @Override - public Map column(Object columnKey) { - return null; - } - - @Override - public Set cellSet() { - return null; - } - - @Override - public Set rowKeySet() { - return null; - } - - @Override - public Set columnKeySet() { - return null; - } - - @Override - public Collection values() { - return null; - } - - @Override - public Map rowMap() { - return null; - } - - @Override - public Map columnMap() { - return null; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java deleted file mode 100644 index 980842a..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.bg7yoz.ft8cn.log; -/** - * 日志中通联呼号的列表 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.callsign.CallsignInfo; -import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; - -import java.util.ArrayList; - -public class LogCallsignAdapter extends RecyclerView.Adapter { - //private ArrayList callsignRecords=new ArrayList<>(); - private final MainViewModel mainViewModel; - private final Context context; - - public LogCallsignAdapter(Context context,MainViewModel mainViewModel) { - this.mainViewModel = mainViewModel; - this.context=context; - } - - @NonNull - @Override - public LogCallsignItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.log_callsign_holder_item, parent, false); - return new LogCallsignItemHolder(view); - } - - - - - /** - * 获取记录 - * @param position 位置 - * @return 记录 - */ - public QSLCallsignRecord getRecord(int position){ - return mainViewModel.callsignRecords.get(position); - } - /** - * 返回查询结题 - * @param records 记录 - */ - @SuppressLint("NotifyDataSetChanged") - public void setQSLCallsignList(ArrayList records){ - mainViewModel.callsignRecords.addAll(records); - //mainViewModel.callsignRecords=records; - notifyDataSetChanged(); - } - - /** - * 清空记录 - */ - public void clearRecords(){ - mainViewModel.callsignRecords.clear(); - } - - @SuppressLint("SetTextI18n") - @Override - public void onBindViewHolder(@NonNull LogCallsignItemHolder holder, int position) { - if ((position%2)==0){ - holder.logCallSignQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); - }else { - holder.logCallSignQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); - } - holder.record=mainViewModel.callsignRecords.get(position); - - - if (holder.record.isQSL||holder.record.isLotW_QSL){ - holder.callsignQSOIsQSLextView.setText(GeneralVariables.getStringFromResource(R.string.confirmed)); - holder.callsignQSOIsQSLextView.setTextColor(context.getResources().getColor( - R.color.is_qsl_text_color)); - }else { - holder.callsignQSOIsQSLextView.setText(GeneralVariables.getStringFromResource(R.string.unconfirmed)); - holder.callsignQSOIsQSLextView.setTextColor(context.getResources().getColor( - R.color.is_not_qsl_text_color)); - } - if (holder.record.isLotW_QSL){ - holder.isQSLModeDistTextView.setText(GeneralVariables.getStringFromResource(R.string.lotw_confirmation)); - }else if (holder.record.isQSL){ - holder.isQSLModeDistTextView.setText(GeneralVariables.getStringFromResource(R.string.manual_confirmation)); - }else { - holder.isQSLModeDistTextView.setText(""); - } - - holder.callsignLogTextView.setText(holder.record.getCallsign()); - - holder.callsignQSOLastTimeTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.log_last_time) - ,holder.record.getLastTime())); - if (holder.record.getGrid().length()>0) { - holder.callsignQSOGridTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.log_grid) - , holder.record.getGrid())); - }else { - holder.callsignQSOGridTextView.setText(""); - } - - holder.callsignQSLBandTextView.setText(holder.record.getBand()); - holder.callsignQSOModeTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.log_mode) - ,holder.record.getMode())); - //计算距离 - holder.callsignQSLDistTextView.setText(MaidenheadGrid.getDistStr( - GeneralVariables.getMyMaidenheadGrid() - , holder.record.getGrid())); - - if (holder.record.where==null){ - setQueryHolderCallsign(holder); - }else {holder.callsignQSOWhereTextView.setText(holder.record.where);} - holder.callsignDxccZoneTextView.setText(holder.record.dxccStr); - } - - - //查呼号的归属地 - private void setQueryHolderCallsign(@NonNull LogCallsignAdapter.LogCallsignItemHolder holder) { - GeneralVariables.callsignDatabase.getCallsignInformation(holder.record.getCallsign() - , new OnAfterQueryCallsignLocation() { - @Override - public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { - holder.callsignQSOWhereTextView.post(new Runnable() { - @SuppressLint("DefaultLocale") - @Override - public void run() { - if (GeneralVariables.isChina) { - holder.callsignQSOWhereTextView.setText(callsignInfo.CountryNameCN); - holder.record.where = callsignInfo.CountryNameCN; - }else { - holder.callsignQSOWhereTextView.setText(callsignInfo.CountryNameEn); - holder.record.where = callsignInfo.CountryNameEn; - } - holder.record.dxccStr=String.format("DXCC : %s, ITU : %d, CQZONE : %d" - ,callsignInfo.DXCC,callsignInfo.ITUZone,callsignInfo.CQZone); - holder.callsignDxccZoneTextView.setText(holder.record.dxccStr); - } - }); - } - }); - } - - - - @Override - public int getItemCount() { - return mainViewModel.callsignRecords.size(); - } - - static class LogCallsignItemHolder extends RecyclerView.ViewHolder{ - QSLCallsignRecord record; - ConstraintLayout logCallSignQSLHolderConstraintLayout; - TextView callsignLogTextView,callsignQSOLastTimeTextView,callsignQSLBandTextView - ,callsignQSOGridTextView,callsignQSOModeTextView,callsignQSLDistTextView - ,callsignQSOWhereTextView,callsignQSOIsQSLextView - ,isQSLModeDistTextView,callsignDxccZoneTextView; - public LogCallsignItemHolder(@NonNull View itemView) { - super(itemView); - logCallSignQSLHolderConstraintLayout=itemView.findViewById(R.id.logCallSignQSLHolderConstraintLayout); - callsignLogTextView=itemView.findViewById(R.id.callsignLogTextView); - callsignQSOLastTimeTextView=itemView.findViewById(R.id.callsignQSOLastTimeTextView); - callsignQSLBandTextView=itemView.findViewById(R.id.callsignQSLBandTextView); - callsignQSOGridTextView=itemView.findViewById(R.id.callsignQSOGridTextView); - callsignQSOModeTextView=itemView.findViewById(R.id.callsignQSOModeTextView); - callsignQSLDistTextView=itemView.findViewById(R.id.callsignQSLDistTextView); - callsignQSOWhereTextView=itemView.findViewById(R.id.callsignQSOWhereTextView); - callsignQSOIsQSLextView=itemView.findViewById(R.id.callsignQSOIsQSLextView); - isQSLModeDistTextView=itemView.findViewById(R.id.isQSLModeDistTextView); - callsignDxccZoneTextView=itemView.findViewById(R.id.callsignDxccZoneTextView); - - itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { - @Override - public void onCreateContextMenu(ContextMenu contextMenu, View view - , ContextMenu.ContextMenuInfo contextMenuInfo) { - view.setTag(getAdapterPosition()); - contextMenu.add(0,2,0 - ,String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) - ,record.getCallsign())).setActionView(view); - } - }); - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java deleted file mode 100644 index d040203..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.bg7yoz.ft8cn.log; - -import android.util.Log; - -import com.bg7yoz.ft8cn.html.ImportTaskList; - -import java.io.FileInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * 日志文件导入。 - * 构建方法需要日志文件名,此处的文件是由NanoHTTPd的session中的post过来的。 - * getFileContext是获取全部文件内容。 - * getLogBody是获取日志文件中全部的原始记录内容,也就是全部以后面的数据 - * getLogRecords是获取拆解后的全部记录列表,记录是以HashMap方式保存的,其中HashMap的Key是字段名(大写),value是实际的值 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -public class LogFileImport { - private static final String TAG = "LogFileImport"; - private final String fileContext; - private final HashMap errorLines=new HashMap<>(); - private ImportTaskList.ImportTask importTask; - - /** - * 构建函数,需要文件名,如果在读取文件时出错,会回抛异常 - * - * @param logFileName 日志文件名 - * @throws IOException 回抛异常 - */ - public LogFileImport(ImportTaskList.ImportTask task, String logFileName) throws IOException { - importTask=task; - FileInputStream logFileStream = new FileInputStream(logFileName); - byte[] bytes = new byte[logFileStream.available()]; - logFileStream.read(bytes); - fileContext = new String(bytes); - } - - /** - * 获取日志文件的全部内容 - * - * @return 全部文本 - */ - public String getFileContext() { - return fileContext; - } - - public String getLogBody() { - String[] temp = fileContext.split("[<][Ee][Oo][Hh][>]"); - if (temp.length > 1) { - return temp[temp.length - 1]; - } else { - return ""; - } - } - - /** - * 获取日志文件中全部的记录,每条记录是以HashMap保存的。HashMap的Key是字段名(大写),Value是值。 - * - * @return 记录列表。ArrayList - */ - public ArrayList> getLogRecords() { - String[] temp = getLogBody().split("[<][Ee][Oo][Rr][>]");//拆解出每个记录的原始内容 - ArrayList> records = new ArrayList<>(); - int count=0;//解析计数器 - for (String s : temp) {//对每一个原始记录内容做拆解 - count++; - if (!s.contains("<")) { - continue; - }//说明没有标签,不做拆解 - try { - HashMap record = new HashMap<>();//创建一个记录 - String[] fields = s.split("<");//拆解记录的每一个字段 - - for (String field : fields) {//对每一个原始记录做拆解 - - if (field.length() > 1) {//如果是可拆解的 - String[] values = field.split(">");//拆解记录的字段名和值 - - if (values.length > 1) {//如果是可拆解的 - if (values[0].contains(":")) {//拆解字段名和字段的长度,冒号前的字段名,后面是长度 - String[] ttt = values[0].split(":"); - if (ttt.length > 1) { - String name = ttt[0];//字段名 - int valueLen = Integer.parseInt(ttt[1]);//字段长度 - if (valueLen > 0) { - if (values[1].length() < valueLen) { - valueLen = values[1].length() - 1; - } - String value = values[1].substring(0, valueLen);//字段值 - record.put(name.toUpperCase(), value);//保存字段,key要大写 - } - } - - } - } - } - } - records.add(record);//保存记录 - }catch (Exception e){ - errorLines.put(count,s.replace("<","<"));//把错误的内容保存下来。 - importTask.readErrorCount=errorLines.size(); - } - } - return records; - } - public int getErrorCount(){ - return errorLines.size(); - } - public HashMap getErrorLines(){ - return errorLines; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java deleted file mode 100644 index 8e170f8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.bg7yoz.ft8cn.log; -/** - * 通联日志的列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.callsign.CallsignInfo; -import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; - -import java.util.ArrayList; - -public class LogQSLAdapter extends RecyclerView.Adapter { - private ArrayList qslRecords=new ArrayList<>(); - private final MainViewModel mainViewModel; - private final Context context; - - public LogQSLAdapter(Context context, MainViewModel mainViewModel) { - this.mainViewModel = mainViewModel; - this.context = context; - } - - @NonNull - @Override - public LogQSLItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.log_qsl_holder_item, parent, false); - return new LogQSLItemHolder(view); - } - - - @SuppressLint("NotifyDataSetChanged") - public void setQSLList(ArrayList list) { - qslRecords.addAll(list); - notifyDataSetChanged(); - } - - /** - * 清空记录列表 - */ - public void clearRecords(){ - qslRecords.clear(); - } - - /** - * 删除日志 - * - * @param position 在列表中的位置 - */ - public void deleteRecord(int position) { - mainViewModel.databaseOpr.deleteQSLByID(qslRecords.get(position).id); - qslRecords.remove(position); - } - - public QSLRecordStr getRecord(int position) { - return qslRecords.get(position); - } - - /** - * 修改手工确认项 - * - * @param position 列表位置 - * @param b 状态 - */ - public void setRecordIsQSL(int position, boolean b) { - qslRecords.get(position).isQSL = b; - mainViewModel.databaseOpr.setQSLTableIsQSL(b, qslRecords.get(position).id); - - } - - @SuppressLint({"DefaultLocale", "SetTextI18n"}) - @Override - public void onBindViewHolder(@NonNull LogQSLItemHolder holder, int position) { - holder.record = qslRecords.get(position); - - if ((position % 2) == 0) { - holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); - } else { - holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); - } - holder.logQSLCallsignTextView.setText(holder.record.getCall()); - if (!holder.record.getGridsquare().equals("")) { - holder.logQSOGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid) - , holder.record.getGridsquare())); - } else { - holder.logQSOGridTextView.setText(""); - } - holder.logQSLMyCallsignTextView.setText(holder.record.getStation_callsign()); - if (!holder.record.getMy_gridsquare().equals("")) { - holder.logQSLMyGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid) - , holder.record.getMy_gridsquare())); - } else { - holder.logQSLMyGridTextView.setText(""); - } - holder.logQSOStartTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) - , holder.record.getTime_on())); - holder.logQSOEndTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) - , holder.record.getTime_off())); - - holder.logQSOReceiveTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) - , holder.record.getRst_rcvd().equals("-120") - ||holder.record.getRst_rcvd().equals("-100") - ?"":holder.record.getRst_rcvd())); - holder.logQSOSendTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) - ,holder.record.getRst_sent().equals("-120") - ||holder.record.getRst_sent().equals("-100") - ?"": holder.record.getRst_sent())); - holder.logQSLBandTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) - , holder.record.getBand())); - holder.logQSLFreqTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) - , holder.record.getFreq())); - holder.logQSOModeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) - , holder.record.getMode())); - holder.logQSOcCommentTextView.setText(holder.record.getComment()); - - if (holder.record.isLotW_QSL) { - holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_lotw_confirmation)); - holder.logIsQSLTextView.setTextColor(context.getResources().getColor( - R.color.is_qsl_text_color)); - } else if (holder.record.isQSL) { - holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation)); - holder.logIsQSLTextView.setTextColor(context.getResources().getColor( - R.color.is_qsl_text_color)); - - } else { - holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_unconfirmed)); - holder.logIsQSLTextView.setTextColor(context.getResources().getColor( - R.color.is_not_qsl_text_color)); - } - - //查呼号的位置 - if (holder.record.where == null) { - setQueryHolderCallsign(holder); - } else if (holder.record.where.equals("")) { - setQueryHolderCallsign(holder); - } else { - holder.logQSLWhereTextView.setText(holder.record.where); - } - } - - //查呼号的归属地 - private void setQueryHolderCallsign(@NonNull LogQSLAdapter.LogQSLItemHolder holder) { - GeneralVariables.callsignDatabase.getCallsignInformation(holder.record.getCall() - , new OnAfterQueryCallsignLocation() { - @Override - public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { - holder.logQSLWhereTextView.post(new Runnable() { - @Override - public void run() { - if (GeneralVariables.isChina) { - holder.logQSLWhereTextView.setText(callsignInfo.CountryNameCN); - holder.record.where = callsignInfo.CountryNameCN; - } else { - holder.logQSLWhereTextView.setText(callsignInfo.CountryNameEn); - holder.record.where = callsignInfo.CountryNameEn; - } - } - }); - - } - }); - } - - - @Override - public int getItemCount() { - return qslRecords.size(); - } - - public ArrayList getRecords() { - return qslRecords; - } - - static class LogQSLItemHolder extends RecyclerView.ViewHolder { - QSLRecordStr record; - ConstraintLayout logQSLHolderConstraintLayout; - TextView logQSLCallsignTextView, logQSOGridTextView, logQSOStartTimeTextView, logQSOEndTimeTextView, logQSOReceiveTextView, logQSOSendTextView, logQSLBandTextView, logQSLFreqTextView, logQSOModeTextView, logQSOcCommentTextView, logQSLMyCallsignTextView, logQSLMyGridTextView, logQSLWhereTextView, logIsQSLTextView; - - public LogQSLItemHolder(@NonNull View itemView) { - super(itemView); - logQSLHolderConstraintLayout = itemView.findViewById(R.id.logQSLHolderConstraintLayout); - logQSLCallsignTextView = itemView.findViewById(R.id.logQSLCallsignTextView); - logQSOGridTextView = itemView.findViewById(R.id.logQSOGridTextView); - logQSOStartTimeTextView = itemView.findViewById(R.id.logQSOStartTimeTextView); - logQSOEndTimeTextView = itemView.findViewById(R.id.logQSOEndTimeTextView); - logQSOReceiveTextView = itemView.findViewById(R.id.logQSOReceiveTextView); - logQSOSendTextView = itemView.findViewById(R.id.logQSOSendTextView); - logQSLBandTextView = itemView.findViewById(R.id.logQSLBandTextView); - logQSLFreqTextView = itemView.findViewById(R.id.logQSLFreqTextView); - logQSOModeTextView = itemView.findViewById(R.id.logQSOModeTextView); - logQSOcCommentTextView = itemView.findViewById(R.id.logQSOcCommentTextView); - logQSLMyCallsignTextView = itemView.findViewById(R.id.logQSLMyCallsignTextView); - logQSLMyGridTextView = itemView.findViewById(R.id.logQSLMyGridTextView); - logQSLWhereTextView = itemView.findViewById(R.id.logQSLWhereTextView); - logIsQSLTextView = itemView.findViewById(R.id.logIsQSLTextView); - - itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { - @Override - public void onCreateContextMenu(ContextMenu contextMenu, View view - , ContextMenu.ContextMenuInfo contextMenuInfo) { - view.setTag(getAdapterPosition()); - //添加菜单的参数i1:组,i2:id值,i3:显示顺序 - if (record.isQSL) { - contextMenu.add(0, 0, 0 - , String.format(GeneralVariables.getStringFromResource(R.string.qsl_cancel_confirmation) - , record.getCall())).setActionView(view); - } else { - contextMenu.add(0, 1, 0 - , String.format(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation_s) - , record.getCall())).setActionView(view); - } - contextMenu.add(0, 2, 0 - , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) - , record.getCall())).setActionView(view); - - if (record.getGridsquare() != null && !record.getGridsquare().equals("") - && record.getMy_gridsquare() != null && !record.getMy_gridsquare().equals("")) { - contextMenu.add(0, 3, 0 - , GeneralVariables.getStringFromResource(R.string.log_menu_location)) - .setActionView(view); - } - } - }); - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java deleted file mode 100644 index ea54026..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.bg7yoz.ft8cn.log; -/** - * 查询呼号日志的回调。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import java.util.ArrayList; - -public interface OnQueryQSLCallsign { - void afterQuery(ArrayList records); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java deleted file mode 100644 index e24b8d9..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.bg7yoz.ft8cn.log; -/** - * 查询通联日志的回调。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import java.util.ArrayList; - -public interface OnQueryQSLRecordCallsign { - void afterQuery(ArrayList records); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java deleted file mode 100644 index 7c0b466..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.bg7yoz.ft8cn.log; - -/** - * 通联过的呼号的日志。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class QSLCallsignRecord { - private String callsign; - private String mode; - private String grid; - private String band; - private String lastTime; - public String where=null; - public String dxccStr=""; - public boolean isQSL=false;//是否手工确认 - public boolean isLotW_QSL = false;//是否是lotw确认的 - - public String getCallsign() { - return callsign; - } - - public void setCallsign(String callsign) { - if (callsign!=null) { - this.callsign = callsign; - }else { - this.callsign=""; - } - } - - public String getLastTime() { - return lastTime; - } - - public void setLastTime(String lastTime) { - if (lastTime!=null) { - this.lastTime = lastTime; - }else { - this.lastTime=""; - } - } - - - - public String getMode() { - return mode; - } - - public void setMode(String mode) { - if (mode!=null) { - this.mode = mode; - }else { - this.mode=""; - } - } - - public String getGrid() { - return grid; - } - - public void setGrid(String grid) { - if (grid!=null) { - this.grid = grid; - }else { - this.grid=""; - } - } - - public String getBand() { - return band; - } - - public void setBand(String band) { - if (band!=null) { - this.band = band; - }else { - this.band=""; - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java deleted file mode 100644 index 974690c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java +++ /dev/null @@ -1,386 +0,0 @@ -package com.bg7yoz.ft8cn.log; - -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.HashMap; -import java.util.Objects; - -/** - * 用于记录通联成功信息的类。通联成功是指FT8完成6条消息的通联。并不是互认。 - * isLotW_import是指是否是外部的数据导入,因为用户可能使用了JTDX等软件通联,这样可以把通联的结果导入到FT8CN - * isLotW_QSL是指是否被平台确认。 - * isQSL是指是否被手工确认 - * - * @author BGY70Z - * @date 2023-03-20 - */ -public class QSLRecord { - private static final String TAG = "QSLRecord"; - public long id = -1; - //private long startTime;//起始时间 - private String qso_date; - private String time_on; - private String qso_date_off; - private String time_off; - //private long endTime;//结束时间 - - private final String myCallsign;//我的呼号 - private String myMaidenGrid;//我的网格 - private String toCallsign;//对方的呼号 - private String toMaidenGrid;//对方的网格 - private int sendReport;//对方收到我的报告(也就是我发送的信号强度) - private int receivedReport;//我收到对方的报告(也就是SNR) - private String mode = "FT8"; - private String bandLength = ""; - private long bandFreq;//发射的波段 - private int wavFrequency;//发射的频率 - private String comment; - public boolean isQSL = false;//手工确认 - public boolean isLotW_import = false;//是否是从外部数据导入的,此项需要在数据库中比对才能设定 - public boolean isLotW_QSL = false;//是否是lotw确认的 - - public boolean saved = false;//是否被保存到数据库中 - - public boolean isInvalid=false;//是否解析出错 - public String errorMSG="";//如果解析出错,错误的消息 - - /** - * 用于SWL QSO记录,记录SWL QSO的条件是收听到双方的信号报告 - * - * @param msg FT8消息 - */ - public QSLRecord(Ft8Message msg) { - this.qso_date_off = UtcTimer.getYYYYMMDD(msg.utcTime); - this.time_off = UtcTimer.getTimeHHMMSS(msg.utcTime); - this.myCallsign = msg.callsignFrom; - this.toCallsign = msg.callsignTo; - wavFrequency = Math.round(msg.freq_hz); - sendReport = -100; - receivedReport = -100; - bandLength = BaseRigOperation.getMeterFromFreq(GeneralVariables.band);//获取波长 - bandFreq = GeneralVariables.band; - comment = "SWL By FT8CN"; - } - - /** - * 构建通联成功的对象 - * - * @param startTime 起始时间 - * @param endTime 结束时间 - * @param myCallsign 我的呼号 - * @param myMaidenGrid 我的网格 - * @param toCallsign 对方呼号 - * @param toMaidenGrid 对方网格 - * @param sendReport 发送的报告 - * @param receivedReport 接收的报告 - * @param mode 模式 默认FT8 - * @param bandFreq 载波频率 - * @param wavFrequency 声音频率 - */ - public QSLRecord(long startTime, long endTime, String myCallsign, String myMaidenGrid - , String toCallsign, String toMaidenGrid, int sendReport, int receivedReport - , String mode, long bandFreq, int wavFrequency) { - //this.startTime = startTime; - this.qso_date = UtcTimer.getYYYYMMDD(startTime); - this.time_on = UtcTimer.getTimeHHMMSS(startTime); - this.qso_date_off = UtcTimer.getYYYYMMDD(endTime); - this.time_off = UtcTimer.getTimeHHMMSS(endTime); - this.myCallsign = myCallsign; - this.myMaidenGrid = myMaidenGrid; - this.toCallsign = toCallsign; - this.toMaidenGrid = toMaidenGrid; - this.sendReport = sendReport; - this.receivedReport = receivedReport; - this.mode = mode; - this.bandLength = BaseRigOperation.getMeterFromFreq(bandFreq);//获取波长 - this.bandFreq = bandFreq; - this.wavFrequency = wavFrequency; - String distance = ""; - if (!myMaidenGrid.equals("") && !toMaidenGrid.equals("")) { - distance = MaidenheadGrid.getDistStrEN(myMaidenGrid, toMaidenGrid); - } - this.comment = - distance.equals("") ? "QSO by FT8CN" - : String.format("Distance: %s, QSO by FT8CN", distance); - } - - public void update(QSLRecord record) { - this.qso_date_off = record.qso_date_off; - this.time_off = record.time_off; - this.toMaidenGrid = record.toMaidenGrid; - this.sendReport = record.sendReport; - this.receivedReport = record.receivedReport; - } - - public QSLRecord(HashMap map) { - isLotW_import = true;//说明是外部导入的数据 - if (map.containsKey("CALL")) {//对方呼号 - toCallsign = map.get("CALL"); - } - if (map.containsKey("STATION_CALLSIGN")) {//我的呼号 - myCallsign = map.get("STATION_CALLSIGN"); - } else { - myCallsign = ""; - } - if (map.containsKey("BAND")) {//载波波长 - bandLength = map.get("BAND"); - } else { - bandLength = ""; - } - - if (map.containsKey("FREQ")) {//载波频率 - try {//要把float转成Long - float freq = Float.parseFloat(Objects.requireNonNull(map.get("FREQ"))); - bandFreq = Math.round(freq * 1000000); - } catch (NumberFormatException e) { - isInvalid=true; - errorMSG="freq:"+e.getMessage(); - e.printStackTrace(); - Log.e(TAG, "QSLRecord: freq" + e.getMessage()); - } - } - if (map.containsKey("MODE")) {//模式 - mode = map.get("MODE"); - } else { - mode = ""; - } - if (map.containsKey("QSO_DATE")) {//通联日期 - qso_date = map.get("QSO_DATE"); - } else { - qso_date = ""; - } - if (map.containsKey("TIME_ON")) {//通联起始时间 - time_on = map.get("TIME_ON"); - } else { - time_on = ""; - } - if (map.containsKey("QSO_DATE_OFF")) {//通联结束日期,此字段只在JTDX中有。 - qso_date_off = map.get("QSO_DATE_OFF"); - } else { - qso_date_off = qso_date; - } - if (map.containsKey("TIME_OFF")) {//通联结束时间,n1mm、Log32、JTDX有,Lotw没有 - time_off = map.get("TIME_OFF"); - } else { - time_off = ""; - } - if (map.containsKey("QSL_RCVD")) {//通联互认,lotw中有。 - isLotW_QSL = Objects.requireNonNull(map.get("QSL_RCVD")).equalsIgnoreCase("Y"); - } - if (map.containsKey("LOTW_QSL_RCVD")) {//通联互认,log32中有。 - isLotW_QSL = Objects.requireNonNull(map.get("LOTW_QSL_RCVD")).equalsIgnoreCase("Y"); - } - if (map.containsKey("QSL_MANUAL")) {//通联互认,lotw中有。 - isQSL = Objects.requireNonNull(map.get("QSL_MANUAL")).equalsIgnoreCase("Y"); - } - - if (map.containsKey("MY_GRIDSQUARE")) {//我的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格 - myMaidenGrid = map.get("MY_GRIDSQUARE"); - } else { - myMaidenGrid = ""; - } - - if (map.containsKey("GRIDSQUARE")) {//对方的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格 - toMaidenGrid = map.get("GRIDSQUARE"); - } else { - toMaidenGrid = ""; - } - - - if (map.containsKey("RST_RCVD")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有 - try {//要把float转成Long - receivedReport = Integer.parseInt(Objects.requireNonNull(map.get("RST_RCVD").trim())); - } catch (NumberFormatException e) { - isInvalid=true; - errorMSG="RST_RCVD:"+e.getMessage(); - e.printStackTrace(); - Log.e(TAG, "QSLRecord: RST_RCVD:" + e.getMessage()); - } - } else { - receivedReport = -120; - } - - if (map.containsKey("RST_SENT")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有 - try {//要把float转成Long - sendReport = Integer.parseInt(Objects.requireNonNull(map.get("RST_SENT").trim())); - } catch (NumberFormatException e) { - isInvalid=true; - errorMSG="RST_SENT:"+e.getMessage(); - e.printStackTrace(); - Log.e(TAG, "QSLRecord: RST_SENT:" + e.getMessage()); - } - } else { - sendReport = -120; - } - if (map.containsKey("COMMENT")) {//注释,JTDX中有 - comment = map.get("COMMENT"); - } else { - comment = String.format(GeneralVariables.getStringFromResource(R.string.qsl_record_import_time) - , UtcTimer.getDatetimeStr(UtcTimer.getSystemTime())); - } - - - } - - /** - * SWL QSO的提示 - * - * @return 提示 - */ - public String swlQSOInfo() { - return String.format("QSO of SWL:%s<--%s", toCallsign, myCallsign); - } - - @Override - public String toString() { - return "QSLRecord{" + - "id=" + id + - ", qso_date='" + qso_date + '\'' + - ", time_on='" + time_on + '\'' + - ", qso_date_off='" + qso_date_off + '\'' + - ", time_off='" + time_off + '\'' + - ", myCallsign='" + myCallsign + '\'' + - ", myMaidenGrid='" + myMaidenGrid + '\'' + - ", toCallsign='" + toCallsign + '\'' + - ", toMaidenGrid='" + toMaidenGrid + '\'' + - ", sendReport=" + sendReport + - ", receivedReport=" + receivedReport + - ", mode='" + mode + '\'' + - ", bandLength='" + bandLength + '\'' + - ", bandFreq=" + bandFreq + - ", wavFrequency=" + wavFrequency + - ", isQSL=" + isQSL + - ", isLotW_import=" + isLotW_import + - ", isLotW_QSL=" + isLotW_QSL + - ", saved=" + saved + - ", comment='" + comment + '\'' + - '}'; - } - - public String toHtmlString() { - String ss = saved ? ", saved=true" : ", saved=false"; - return "QSLRecord{" + - "id=" + id + - ", qso_date='" + qso_date + '\'' + - ", time_on='" + time_on + '\'' + - ", qso_date_off='" + qso_date_off + '\'' + - ", time_off='" + time_off + '\'' + - ", myCallsign='" + myCallsign + '\'' + - ", myMaidenGrid='" + myMaidenGrid + '\'' + - ", toCallsign='" + toCallsign + '\'' + - ", toMaidenGrid='" + toMaidenGrid + '\'' + - ", sendReport=" + sendReport + - ", receivedReport=" + receivedReport + - ", mode='" + mode + '\'' + - ", bandLength='" + bandLength + '\'' + - ", bandFreq=" + bandFreq + - ", wavFrequency=" + wavFrequency + - ", isQSL=" + isQSL + - ", isLotW_import=" + isLotW_import + - ", isLotW_QSL=" + isLotW_QSL + - ss + - ", comment='" + comment + '\'' + - '}'; - } - - public String getBandLength() { - return bandLength; - } - - public String getToCallsign() { - return toCallsign; - } - - public String getToMaidenGrid() { - return toMaidenGrid; - } - - public String getMode() { - return mode; - } - - public long getBandFreq() { - return bandFreq; - } - - public int getWavFrequency() { - return wavFrequency; - } - - - public String getMyCallsign() { - return myCallsign; - } - - public String getMyMaidenGrid() { - return myMaidenGrid; - } - - public void setMyMaidenGrid(String myMaidenGrid) { - this.myMaidenGrid = myMaidenGrid; - } - - public int getSendReport() { - return sendReport; - } - - public int getReceivedReport() { - return receivedReport; - } - - public String getQso_date() { - return qso_date; - } - - public String getTime_on() { - return time_on; - } - - public String getQso_date_off() { - return qso_date_off; - } - - public String getTime_off() { - return time_off; - } - - public String getStartTime() { - return qso_date + "-" + time_on; - } - - public String getEndTime() { - return qso_date_off + "-" + time_off; - } - - public String getComment() { - return comment; - } - - - public void setToMaidenGrid(String toMaidenGrid) { - this.toMaidenGrid = toMaidenGrid; - } - - public void setSendReport(int sendReport) { - this.sendReport = sendReport; - } - - public void setReceivedReport(int receivedReport) { - this.receivedReport = receivedReport; - } - - public void setQso_date(String qso_date) { - this.qso_date = qso_date; - } - - public void setTime_on(String time_on) { - this.time_on = time_on; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java deleted file mode 100644 index 42e74fd..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.bg7yoz.ft8cn.log; - -import android.os.Parcel; -import android.os.Parcelable; - -import com.bg7yoz.ft8cn.Ft8Message; - -import java.io.Serializable; - -/** - * 用于在ADAPTER中显示内容,此数据在数据库的查询中生成 - * @author BGY70Z - * @date 2023-03-20 - */ -public class QSLRecordStr implements Serializable { - public int id; - private String call=""; - private String gridsquare=""; - private String mode=""; - private String rst_sent=""; - private String rst_rcvd=""; - private String time_on="";//此时间包括日期(QSO_DATE+TIME_ON组成) - private String time_off="";//此时间包括日期(QSO_DATE_OFF+TIME_OFF组成) - private String band="";//波长 - private String freq=""; - private String station_callsign=""; - private String my_gridsquare=""; - private String comment; - public String where = null; - public boolean isQSL = false;//手工确认 - public boolean isLotW_import = false;//是否是lotw导入的 - public boolean isLotW_QSL = false;//是否是lotw确认的 - - - public String getCall() { - return call; - } - - public void setCall(String call) { - if (call!=null) { - this.call = call; - }else { - this.call=""; - } - } - - public String getGridsquare() { - return gridsquare; - } - - public void setGridsquare(String gridsquare) { - if (gridsquare!=null) { - this.gridsquare = gridsquare; - }else { - this.gridsquare=""; - } - } - - public String getMode() { - return mode; - } - - public void setMode(String mode) { - if (mode!=null) { - this.mode = mode; - }else { - this.mode=""; - } - } - - public String getRst_sent() { - return rst_sent; - } - - public void setRst_sent(String rst_sent) { - if (rst_sent!=null) { - this.rst_sent = rst_sent; - }else { - this.rst_sent=""; - } - } - - public String getRst_rcvd() { - return rst_rcvd; - } - - public void setRst_rcvd(String rst_rcvd) { - if (rst_rcvd!=null) { - this.rst_rcvd = rst_rcvd; - }else { - this.rst_rcvd=""; - } - } - - public String getTime_on() { - return time_on; - } - - public void setTime_on(String time_on) { - if (time_on!=null) { - this.time_on = time_on; - }else { - this.time_on=""; - } - } - - public String getTime_off() { - return time_off; - } - - public void setTime_off(String time_off) { - if (time_off!=null) { - this.time_off = time_off; - }else { - this.time_off=""; - } - } - - public String getBand() { - return band; - } - - public void setBand(String band) { - if (band!=null) { - this.band = band; - }else { - this.band=""; - } - } - - public String getFreq() { - return freq; - } - - public void setFreq(String freq) { - if (freq!=null) { - this.freq = freq; - }else { - this.freq=""; - } - } - - public String getStation_callsign() { - return station_callsign; - } - - public void setStation_callsign(String station_callsign) { - if (station_callsign!=null) { - this.station_callsign = station_callsign; - }else { - this.station_callsign=""; - } - } - - public String getMy_gridsquare() { - return my_gridsquare; - } - - public void setMy_gridsquare(String my_gridsquare) { - if (my_gridsquare!=null) { - this.my_gridsquare = my_gridsquare; - }else { - this.my_gridsquare=""; - } - } - - public String getComment() { - return comment; - } - - public void setComment(String comment) { - if (comment!=null) { - this.comment = comment; - }else { - this.comment=""; - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java deleted file mode 100644 index 6604a1a..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.bg7yoz.ft8cn.log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; - -/** - * 用于计算和处理SWL消息中的QSO记录。 - * QSO的计算方法:把FT8通联的6个阶段分成3部分: - * 1.CQ C1 grid - * 2.C1 C2 grid - * ---------第一部分--- - * 3.C2 C1 report - * 4.C1 C2 r-report - * --------第二部分---- - * 5.C2 C1 RR73(RRR) - * 6.C1 C2 73 - * --------第三部分---- - *

- * 一个基本的QSO,必须有自己的结束点(第三部分),双方的信号报告(在第二部分判断),网格报告可有可无(第一部分) - * 以RR73、RRR、73为检查点,符合以上第一、二部分 - * swlQsoList是个双key的HashMap,用于防止重复记录QSO。 - * C1与C2顺序不同,代表不同的呼叫方。体现在station_callsign和call字段上 - * - * @author BG7YOZ - * @date 2023-03-07 - */ -public class SWLQsoList { - private static final String TAG = "SWLQsoList"; - //通联成功的列表,防止重复,两个KEY顺序分别是:station_callsign和call,Boolean=true,已经QSO - private final HashTable qsoList =new HashTable(); - - public SWLQsoList() { - } - - /** - * 检查有没有QSO消息 - * - * @param newMessages 新的FT8消息 - * @param allMessages 全部的FT8消息 - * @param onFoundSwlQso 当有发现的回调 - */ - public void findSwlQso(ArrayList newMessages, ArrayList allMessages - , OnFoundSwlQso onFoundSwlQso) { - for (int i = 0; i < newMessages.size(); i++) { - Ft8Message msg = newMessages.get(i); - if (msg.inMyCall()) continue;//对包含我自己的消息不处理 - - if (GeneralVariables.checkFun4_5(msg.extraInfo)//结束标识RRR、RR73、73 - && !qsoList.contains(msg.callsignFrom, msg.callsignTo)) {//没有QSO记录 - - QSLRecord qslRecord = new QSLRecord(msg); - - if (checkPart2(allMessages, qslRecord)) {//找双方的信号报告,一个基本的QSO,必须有双方的信号报告 - - checkPart1(allMessages, qslRecord);//找双方的网格报告,顺便更新time_on的时间 - - if (onFoundSwlQso != null) {//触发回调,用于记录到数据库 - qsoList.put(msg.callsignFrom, msg.callsignTo, true);//把QSO记录保存下来 - onFoundSwlQso.doFound(qslRecord);//触发找到QSO的动作 - } - } - } - } - } - - /** - * 查第2部分是否存在,顺便把信号报告保存到QSLRecord中 - * - * @param allMessages 消息列表 - * @param record QSLRecord - * @return 返回值 没有发现:0,存在:2 - */ - private boolean checkPart2(ArrayList allMessages, QSLRecord record) { - boolean foundFromReport = false; - boolean foundToReport = false; - long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间 - for (int i = allMessages.size() - 1; i >= 0; i--) { - Ft8Message msg = allMessages.get(i); - if (msg.callsignFrom.equals(record.getMyCallsign()) - && msg.callsignTo.equals(record.getToCallsign()) - && !foundFromReport) {//callsignFrom发出的信号报告 - int report = GeneralVariables.checkFun2_3(msg.extraInfo); - - if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 - if (report != -100) { - record.setSendReport(report); - foundFromReport = true; - } - } - - if (msg.callsignFrom.equals(record.getToCallsign()) - && msg.callsignTo.equals(record.getMyCallsign()) - && !foundToReport) {//callsignTo发出的信号报告 - int report = GeneralVariables.checkFun2_3(msg.extraInfo); - if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 - if (report != -100) { - record.setReceivedReport(report); - foundToReport = true; - } - } - if (foundToReport && foundFromReport) {//如果双方的信号报告都找到了,就退出循环 - record.setQso_date(UtcTimer.getYYYYMMDD(time_on)); - record.setTime_on(UtcTimer.getTimeHHMMSS(time_on)); - break; - } - } - return foundToReport && foundFromReport;//双方的信号报告都有,才算一个QSO - } - - /** - * 查第2部分是否存在,顺便把网格报告保存到QSLRecord中 - * - * @param allMessages 消息列表 - * @param record QSLRecord - */ - private void checkPart1(ArrayList allMessages, QSLRecord record) { - boolean foundFromGrid = false; - boolean foundToGrid = false; - long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间 - for (int i = allMessages.size() - 1; i >= 0; i--) { - Ft8Message msg = allMessages.get(i); - if (!foundFromGrid - && msg.callsignFrom.equals(record.getMyCallsign()) - && (msg.callsignTo.equals(record.getToCallsign()) || msg.checkIsCQ())) {//callsignFrom的网格报告 - - if (GeneralVariables.checkFun1_6(msg.extraInfo)) { - record.setMyMaidenGrid(msg.extraInfo.trim()); - foundFromGrid = true; - } - if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 - } - - if (!foundToGrid - && msg.callsignFrom.equals(record.getToCallsign()) - && (msg.callsignTo.equals(record.getMyCallsign())|| msg.checkIsCQ())) {//callsignTo发出的信号报告 - if (GeneralVariables.checkFun1_6(msg.extraInfo)) { - record.setToMaidenGrid(msg.extraInfo.trim()); - foundToGrid = true; - } - if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 - } - if (foundToGrid && foundFromGrid) {//如果双方的信号报告都找到了,就退出循环 - break; - } - } - - if (foundFromGrid || foundToGrid) {//发现网格报告,至少一个方向的 - record.setQso_date(UtcTimer.getYYYYMMDD(time_on)); - record.setTime_on(UtcTimer.getTimeHHMMSS(time_on)); - } - } - - public interface OnFoundSwlQso { - void doFound(QSLRecord record); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java deleted file mode 100644 index 27b5e9d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java +++ /dev/null @@ -1,406 +0,0 @@ -package com.bg7yoz.ft8cn.maidenhead; -/** - * 梅登海德网格的处理。包括经纬度换算、距离计算。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.location.Location; -import android.location.LocationManager; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.google.android.gms.maps.model.LatLng; - -import java.util.List; - -public class MaidenheadGrid { - private static final String TAG = "MaidenheadGrid"; - private static final double EARTH_RADIUS = 6371393; // 平均半径,单位:m;不是赤道半径。赤道为6378左右 - - /** - * 计算梅登海德网格的经纬度,4字符或6字符。如果网格数据不正确,返回null。如果是四字符的,尾部加ll,取中间的位置。 - * - * @param grid 梅登海德网格数据 - * @return LatLng 返回经纬度,如果数据不正确,返回null - */ - public static LatLng gridToLatLng(String grid) { - if (grid==null) return null; - if (grid.length()==0) return null; - //判断是不是符合梅登海德网格的规则 - if (grid.length() != 2&&grid.length() != 4 && grid.length() != 6) { - return null; - } - if (grid.equalsIgnoreCase("RR73")) return null; - if (grid.equalsIgnoreCase("RR")) return null; - double x=0; - double y=0; - double z=0; - //纬度 - double lat=0; - if (grid.length()==2){ - x=grid.toUpperCase().getBytes()[1]-'A'+0.5f; - }else { - x=grid.toUpperCase().getBytes()[1]-'A'; - } - x*=10; - - if (grid.length()==4){ - y=grid.getBytes()[3]-'0'+0.5f; - }else if (grid.length()==6){ - y=grid.getBytes()[3]-'0'; - } - - if (grid.length()==6){ - z=grid.toUpperCase().getBytes()[5]-'A'+0.5f; - z=z*(1/18f); - } - lat=x+y+z-90; - - //经度 - x=0; - y=0; - z=0; - double lng=0; - if (grid.length()==2){ - x=grid.toUpperCase().getBytes()[0]-'A'+0.5; - }else { - x=grid.toUpperCase().getBytes()[0]-'A'; - } - x*=20; - if (grid.length()==4){ - y=grid.getBytes()[2]-'0'+0.5; - }else if (grid.length()==6){ - y=grid.getBytes()[2]-'0'; - } - y*=2; - if (grid.length()==6){ - z=grid.toUpperCase().getBytes()[4]-'A'+0.5; - z=z*(2/18f); - } - lng=x+y+z-180; - if (lat>85) lat=85;//防止在地图上越界 - if (lat<-85) lat=-85;//防止在地图上越界 - - - return new LatLng(lat,lng); - - - } - - - - - - - public static LatLng[] gridToPolygon(String grid) { - if (grid.length() != 2 && grid.length() != 4 && grid.length() != 6) { - return null; - } - LatLng[] latLngs = new LatLng[4]; - - //纬度1 - double x; - double y = 0; - double z = 0; - double lat1; - x = grid.toUpperCase().getBytes()[1] - 'A'; - x *= 10; - if (grid.length() > 2) { - y = grid.getBytes()[3] - '0'; - } - if (grid.length() > 4) { - z = grid.toUpperCase().getBytes()[5] - 'A'; - z = z * (1f / 18f); - } - lat1 = x + y + z - 90; - if (lat1<-85.0){ - lat1=-85.0; - } - if (lat1>85.0){ - lat1=85.0; - } - - //纬度2 - x = 0; - y = 0; - z = 0; - double lat2; - if (grid.length() == 2) { - x = grid.toUpperCase().getBytes()[1] - 'A' + 1; - } else { - x = grid.toUpperCase().getBytes()[1] - 'A'; - } - x *= 10; - if (grid.length() == 4) { - y = grid.getBytes()[3] - '0' + 1; - } else if (grid.length() == 6) { - y = grid.getBytes()[3] - '0'; - } - if (grid.length() == 6) { - z = grid.toUpperCase().getBytes()[5] - 'A' + 1; - z = z * (1f / 18f); - } - lat2 = x + y + z - 90; - if (lat2<-85.0){ - lat2=-85.0; - } - if (lat2>85.0){ - lat2=85.0; - } - - - //经度1 - x=0;y=0;z=0; - double lng1; - x=grid.toUpperCase().getBytes()[0]-'A'; - x*=20; - - if (grid.length()>2){ - y=grid.getBytes()[2]-'0'; - y*=2; - } - if (grid.length()>4){ - z=grid.toUpperCase().getBytes()[4]-'A'; - z=z*2/18f; - } - lng1=x+y+z-180; - - //经度2 - x=0;y=0;z=0; - double lng2; - if (grid.length()==2){ - x=grid.toUpperCase().getBytes()[0]-'A'+1; - }else { - x=grid.toUpperCase().getBytes()[0]-'A'; - } - x*=20; - if (grid.length()==4){ - y=grid.getBytes()[2]-'0'+1; - }else if (grid.length()==6){ - y=grid.getBytes()[2]-'0'; - } - y*=2; - if (grid.length()==6){ - z=grid.toUpperCase().getBytes()[4]-'A'+1; - z=z*2/18f; - } - lng2=x+y+z-180; - - latLngs[0] = new LatLng(lat1,lng1); - latLngs[1] = new LatLng(lat1,lng2); - latLngs[2] = new LatLng(lat2,lng2); - latLngs[3] = new LatLng(lat2,lng1); - - return latLngs; - - - } - - /** - * 此函数根据纬度计算 6 字符 Maidenhead网格。 - * 经纬度采用 NMEA 格式。换句话说,西经和南纬度为负数。它们被指定为double类型 - * - * @param location 经纬度 - * @return String 梅登海德字符 - */ - public static String getGridSquare(LatLng location) { - double tempNumber;//用于中间计算 - int index;//确定要显示的字符 - double _long = location.longitude; - double _lat = location.latitude; - StringBuilder buff = new StringBuilder(); - - /* - * 计算第一对两个字符 - */ - _long += 180; // 从太平洋中部开始 - tempNumber = _long / 20; // 每个主要正方形都是 20 度宽 - index = (int) tempNumber; // 大写字母的索引 - buff.append(String.valueOf((char) (index + 'A'))); // 设置第一个字符 - _long = _long - (index * 20); // 第 2 步的剩余部分 - - _lat += 90; //从南极开始 180 度 - tempNumber = _lat / 10; // 每个大正方形高 10 度 - index = (int) tempNumber; // 大写字母的索引 - buff.append(String.valueOf((char) (index + 'A')));//设置第二个字符 - _lat = _lat - (index * 10); // 第 2 步的剩余部分 - - /* - * 现在是第二对两数字: - */ - tempNumber = _long / 2; // 步骤 1 的余数除以 2 - index = (int) tempNumber; // 数字索引 - buff.append(String.valueOf((char) (index + '0')));//设置第三个字符 - _long = _long - (index * 2); //第 3 步的剩余部分 - - tempNumber = _lat; // 步骤 1 的余数除以 1 - index = (int) tempNumber; // 数字索引 - buff.append(String.valueOf((char) (index + '0')));//设置第四个字符 - _lat = _lat - index; //第 3 步的剩余部分 - - /* - *现在是第三对两个小写字符: - */ - tempNumber = _long / 0.083333; //步骤 2 的余数除以 0.083333 - index = (int) tempNumber; // 小写字母的索引 - buff.append(String.valueOf((char) (index + 'a')));//设置第五个字符 - - tempNumber = _lat / 0.0416665; // 步骤 2 的余数除以 0.0416665 - index = (int) tempNumber; // 小写字母的索引 - buff.append(String.valueOf((char) (index + 'a')));//设置第五个字符 - - return buff.toString().substring(0, 4); - } - - /** - * 计算经纬度之间的距离 - * - * @param latLng1 经纬度 - * @param latLng2 经纬度 - * @return 距离,公里。 - */ - public static double getDist(LatLng latLng1, LatLng latLng2) { - double radiansAX = Math.toRadians(latLng1.longitude); // A经弧度 - double radiansAY = Math.toRadians(latLng1.latitude); // A纬弧度 - double radiansBX = Math.toRadians(latLng2.longitude); // B经弧度 - double radiansBY = Math.toRadians(latLng2.latitude); // B纬弧度 - - // 公式中“cosβ1cosβ2cos(α1-α2)+sinβ1sinβ2”的部分,得到∠AOB的cos值 - double cos = Math.cos(radiansAY) * Math.cos(radiansBY) * Math.cos(radiansAX - radiansBX) - + Math.sin(radiansAY) * Math.sin(radiansBY); - double acos = Math.acos(cos); // 反余弦值 - return EARTH_RADIUS * acos / 1000; // 最终结果km - } - - /** - * 计算梅登海德网格之间的距离 - * - * @param mGrid1 梅登海德网格 - * @param mGrid2 梅登海德网格2 - * @return double 两个网格之间的距离 - */ - public static double getDist(String mGrid1, String mGrid2) { - LatLng latLng1 = gridToLatLng(mGrid1); - LatLng latLng2 = gridToLatLng(mGrid2); - if (latLng1 != null && latLng2 != null) { - return getDist(latLng1, latLng2); - } else { - return 0; - } - } - - /** - * 计算两个网格之间的距离 - * - * @param mGrid1 网格 - * @param mGrid2 网格 - * @return 距离 - */ - @SuppressLint("DefaultLocale") - public static String getDistStr(String mGrid1, String mGrid2) { - double dist = getDist(mGrid1, mGrid2); - if (dist == 0) { - return ""; - } else { - return String.format(GeneralVariables.getStringFromResource(R.string.distance), dist); - } - } - public static String getDistLatLngStr(LatLng latLng1,LatLng latLng2){ - return String.format(GeneralVariables.getStringFromResource(R.string.distance), getDist(latLng1,latLng2)); - - } - - /** - * 计算两个网格之间的距离,以英文显示公里数 - * - * @param mGrid1 网格 - * @param mGrid2 网格 - * @return 距离 - */ - @SuppressLint("DefaultLocale") - public static String getDistStrEN(String mGrid1, String mGrid2) { - double dist = getDist(mGrid1, mGrid2); - if (dist == 0) { - return ""; - } else { - return String.format("%.0f km", dist); - } - } - - /** - * 获取本设备的经纬度 - * - * @param context context - * @return 经纬度 - */ - public static LatLng getLocalLocation(Context context) { - // 获取位置服务 - String serviceName = Context.LOCATION_SERVICE; - // 调用getSystemService()方法来获取LocationManager对象 - LocationManager locationManager = (LocationManager) context.getSystemService(serviceName); - // 指定LocationManager的定位方法 - //String provider = LocationManager.GPS_PROVIDER; - // 调用getLastKnownLocation()方法获取当前的位置信息 - - List providers = locationManager.getProviders(true); - Location location = null; - for (String s : providers) { - @SuppressLint("MissingPermission") Location l = locationManager.getLastKnownLocation(s); - if (l == null) { - continue; - } - if (location == null || l.getAccuracy() < location.getAccuracy()) { - // Found best last known location: %s", l); - location = l; - } - } - - if (location != null) { - return new LatLng(location.getLatitude(), location.getLongitude()); - } else { - return null; - } - } - - - /** - * 获取本机的梅登海德网格数据。需要定位的权限。 - * - * @param context context - * @return String 返回6字符的梅登海德网格。 - */ - public static String getMyMaidenheadGrid(Context context) { - LatLng latLng = getLocalLocation(context); - - if (latLng != null) { - return getGridSquare(latLng); - } else { - //ToastMessage.show("无法定位,请确认是否有定位的权限。"); - return ""; - } - } - - /** - * 检查是不是梅登海德网格。如果不是返回false。 - * - * @param s 梅登海德网格 - * @return boolean 是否是梅登海德网格。 - */ - public static boolean checkMaidenhead(String s) { - if (s.length() != 4 && s.length() != 6) { - return false; - } else { - if (s.equals("RR73")) { - return false; - } - return Character.isAlphabetic(s.charAt(0)) - && Character.isAlphabetic(s.charAt(1)) - && Character.isDigit(s.charAt(2)) - && Character.isDigit(s.charAt(3)); - } - } - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java deleted file mode 100644 index def2611..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import androidx.lifecycle.MutableLiveData; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.connector.BaseRigConnector; - -/** - * 电台的抽象类。 - * @author BGY70Z - * @date 2023-03-20 - */ -public abstract class BaseRig { - private long freq;//当前频率值 - public MutableLiveData mutableFrequency = new MutableLiveData<>(); - private int controlMode;//控制模式 - private OnRigStateChanged onRigStateChanged;//当电台的一些状态发生变化的回调 - private int civAddress;//CIV地址 - private int baudRate;//波特率 - private boolean isPttOn=false;//ptt是否打开 - private BaseRigConnector connector = null;//连接电台的对象 - - public abstract boolean isConnected();//确认电台是否连接 - - public abstract void setUsbModeToRig();//设置电台上边带方式 - - public abstract void setFreqToRig();//设置电台频率 - - public abstract void onReceiveData(byte[] data);//当电台发送回数据的动作 - - public abstract void readFreqFromRig();//从电台读取频率 - - public abstract String getName();//获取电台的名字 - - private final OnConnectReceiveData onConnectReceiveData = new OnConnectReceiveData() { - @Override - public void onData(byte[] data) { - onReceiveData(data); - } - }; - - public void setPTT(boolean on) {//设置PTT打开或关闭 - isPttOn=on; - if (onRigStateChanged != null) { - onRigStateChanged.onPttChanged(on); - } - } - -// public void sendWaveData(float[] data) { -// //留给ICOM电台使用 -// } - public void sendWaveData(Ft8Message message) { - //留给ICOM电台使用 - } - - public long getFreq() { - return freq; - } - - public void setFreq(long freq) { - if (freq == this.freq) return; - if (freq == 0) return; - if (freq == -1) return; - mutableFrequency.postValue(freq); - this.freq = freq; - if (onRigStateChanged != null) { - onRigStateChanged.onFreqChanged(freq); - } - } - - public void setConnector(BaseRigConnector connector) { - this.connector = connector; - - this.connector.setOnRigStateChanged(onRigStateChanged); - this.connector.setOnConnectReceiveData(new OnConnectReceiveData() { - @Override - public void onData(byte[] data) { - onReceiveData(data); - } - }); - } - - public void setControlMode(int mode) { - controlMode = mode; - if (connector != null) { - connector.setControlMode(mode); - } - } - - public int getControlMode() { - return controlMode; - } - - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("%02x ", data[i] & 0xff)); - } - return s.toString(); - } - - public BaseRigConnector getConnector() { - return connector; - } - - public OnRigStateChanged getOnRigStateChanged() { - return onRigStateChanged; - } - - public void setOnRigStateChanged(OnRigStateChanged onRigStateChanged) { - this.onRigStateChanged = onRigStateChanged; - } - - public int getCivAddress() { - return civAddress; - } - - public void setCivAddress(int civAddress) { - this.civAddress = civAddress; - } - - public int getBaudRate() { - return baudRate; - } - - public void setBaudRate(int baudRate) { - this.baudRate = baudRate; - } - - public boolean isPttOn() { - return isPttOn; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java deleted file mode 100644 index 796fb00..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; -/** - * 电台相关计算。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; - -public class BaseRigOperation { - @SuppressLint("DefaultLocale") - public static String getFrequencyStr(long freq) { - return String.format("%d.%03dMHz", freq / 1000000, (freq % 1000000) / 1000); - } - /** - * 检查是不是在WSPR2的频段内 - * - * @param freq 频率 - * @return 是否 - */ - public static boolean checkIsWSPR2(long freq) { - //freq=电台频率+声音频率 - return (freq >= 137400 && freq <= 137600) //2190m - || (freq >= 475400 && freq <= 475600) //630m - || (freq >= 1838000 && freq <= 1838200) //160m - || (freq >= 3594000 && freq <= 3594200) //80m - || (freq >= 5288600 && freq <= 5288800) //60m - || (freq >= 7040000 && freq <= 7040200) //40m - || (freq >= 10140100 && freq <= 10140300) //30m - || (freq >= 14097000 && freq <= 14097200) //20m - || (freq >= 18106000 && freq <= 18106200) //17m - || (freq >= 21096000 && freq <= 21096200) //15m - || (freq >= 24926000 && freq <= 24926200) //12m - || (freq >= 28126000 && freq <= 28126200) //10m - || (freq >= 50294400 && freq <= 50294600) //6m - || (freq >= 70092400 && freq <= 70092600) //4m - || (freq >= 144489900 && freq <= 144490100) //2m - || (freq >= 432301600 && freq <= 432301800) //70cm - || (freq >= 1296501400 && freq <= 1296501600);//23cm - } - - /** - * 通过频率获取波长 - * - * @param freq 频率 - * @return 波长 - */ - @SuppressLint("DefaultLocale") - public static String getMeterFromFreq(long freq) { - if (freq >= 135700 && freq <= 137800) { - return "2200m"; - } // 2200m - else if (freq >= 472000 && freq <= 479000) { - return "630m"; - } // 160m - else if (freq >= 1800000 && freq <= 2000000) { - return "160m"; - } // 160m - else if (freq >= 3500000 && freq <= 4000000) { - return "80m"; - } // 80m - else if (freq >= 5351500 && freq <= 5366500) { - return "60m"; - } // 80m - else if (freq >= 7000000 && freq <= 7300000) { - return "40m"; - } // 40m - else if (freq >= 10100000 && freq <= 10150000) { - return "30m"; - } // 30m - else if (freq >= 14000000 && freq <= 14350000) { - return "20m"; - } // 20m - else if (freq >= 18068000 && freq <= 18168000) { - return "17m"; - } // 17m - else if (freq >= 21000000 && freq <= 21450000) { - return "15m"; - } // 15m - else if (freq >= 24890000 && freq <= 24990000) { - return "12m"; - } // 12m - else if (freq >= 28000000 && freq <= 29700000) { - return "10m"; - } // 10m - else if (freq >= 50000000 && freq <= 54000000) { - return "6m"; - } // 6m - else if (freq >= 144000000 && freq <= 148000000) { - return "2m"; - } // 2m - else if (freq >= 220000000 && freq <= 225000000) { - return "1.25m"; - } //1.25m - else if (freq >= 420000000 && freq <= 450000000) { - return "70cm"; - } //0.7m - else if (freq >= 902000000 && freq <= 928000000) { - return "33cm"; - } //0.33m - else if (freq >= 1240000000 && freq <= 1300000000) { - return "23cm"; - } //0.23m - else { - return calculationMeterFromFreq(freq); - }//不在范围内,就计算一下 - } - - @SuppressLint("DefaultLocale") - private static String calculationMeterFromFreq(Long freq) { - if (freq == 0) return ""; - float meter = 300000000f / (float) freq; - if (meter < 1) {//以厘米为单位 - return String.format("%dcm", Math.round(meter * 10) * 10); - } else if (meter < 20) {//小于20米,以米为单位 - return String.format("%dm", Math.round(meter)); - } else {//大于20M,以10米为单位 - return String.format("%dm", Math.round(meter / 10) * 10); - } - } - - public static String getFrequencyAllInfo(long freq) { - return String.format("%s (%s)", getFrequencyStr(freq), getMeterFromFreq(freq)); - } - - @SuppressLint("DefaultLocale") - public static String getFrequencyFloat(long freq) { - return String.format("%d.%06d", freq / 1000000, (freq % 1000000)); - } - - public static String byteToStr(byte[] data) { - StringBuilder s = new StringBuilder(); - for (int i = 0; i < data.length; i++) { - s.append(String.format("0x%02x ", data[i] & 0xff)); - } - return s.toString(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java deleted file mode 100644 index 33cfeab..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -/** - * crc16-ccitt-false加密工具 - * - * @author BGY70Z - * @date 2023-03-20 - * - */ -public class CRC16 { - - /** - * crc16-ccitt-false加/解密(四字节) - * - * @param bytes - * @return - */ - public static int crc16(byte[] bytes) { - return crc16(bytes, bytes.length); - } - - /** - * crc16-ccitt-false加/解密(四字节) - * - * @param bytes -字节数组 - * @return - */ - public static int crc16(byte[] bytes, int len) { - int crc = 0xFFFF; - for (int j = 0; j < len; j++) { - crc = ((crc >>> 8) | (crc << 8)) & 0xffff; - crc ^= (bytes[j] & 0xff);// byte to int, trunc sign - crc ^= ((crc & 0xff) >> 4); - crc ^= (crc << 12) & 0xffff; - crc ^= ((crc & 0xFF) << 5) & 0xffff; - } - crc &= 0xffff; - return crc; - } - - /** - * crc16-ccitt-false加/解密(四字节) - * - * @param bytes - * @return - */ - public static int crc16(byte[] bytes, int start, int len) { - int crc = 0xFFFF; - for (; start < len; start++) { - crc = ((crc >>> 8) | (crc << 8)) & 0xffff; - crc ^= (bytes[start] & 0xff);// byte to int, trunc sign - crc ^= ((crc & 0xff) >> 4); - crc ^= (crc << 12) & 0xffff; - crc ^= ((crc & 0xFF) << 5) & 0xffff; - } - crc &= 0xffff; - return crc; - } - - /** - * crc16-ccitt-false加/解密 - * - * @param bytes - * -字节数组 - * @return - */ - public static short crc16_short(byte[] bytes) { - return crc16_short(bytes, 0, bytes.length); - } - - /** - * crc16-ccitt-false加/解密(计算从0位置开始的len长度) - * - * @param bytes - * -字节数组 - * @param len - * -长度 - * @return - */ - public static short crc16_short(byte[] bytes, int len) { - return (short) crc16(bytes, len); - } - - /** - * crc16-ccitt-false加/解密(两字节) - * - * @param bytes - * @return - */ - public static short crc16_short(byte[] bytes, int start, int len) { - return (short) crc16(bytes, start, len); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java deleted file mode 100644 index f1454a3..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - - -import android.util.Log; - -public class ElecraftCommand { - private static final String TAG = "ElecraftCommand"; - private final String commandID; - private final String data; - /** - * 获取命令(两字节字符串) - * - * @return 主命令值 - */ - public String getCommandID() {//获取主命令 - return commandID; - } - - /** - * 获取命令数据,字符串,没有分号 - * - * @return 命令数据 - */ - public String getData() {//获取命令数据 - return data; - } - - public ElecraftCommand(String commandID, String data) { - this.commandID = commandID; - this.data = data; - } - //解析接收的指令 - - /** - * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 - * - * @param buffer 从串口接收到的数据 - * @return 返回电台指令对象,如果不符合指令的格式,返回null。 - */ - public static ElecraftCommand getCommand(String buffer) { - if (buffer.length() < 2) {//指令的长度必须大于等于2 - return null; - } - if (buffer.substring(0,2).matches("[a-zA-Z][a-zA-Z]")) { - return new ElecraftCommand(buffer.substring(0, 2), buffer.substring(2)); - } - return null; - } - - - /** - * 计算频率 - * @param command 指令 - * @return 频率 - */ - public static long getFrequency(ElecraftCommand command) { - try { - if(command.getCommandID().equals("FA")||command.getCommandID().equals("FB")) { - return Long.parseLong(command.getData()); - }else { - return 0; - } - }catch (Exception e){ - Log.e(TAG, "获取频率失败: "+command.getData()+"\n"+e.getMessage() ); - } - return 0; - } - - public static boolean isSWRMeter(ElecraftCommand command) { - return command.data.length() >= 3; - } - - public static int getSWRMeter(ElecraftCommand command) { - return Integer.parseInt(command.data.substring(0,3)); - } - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java deleted file mode 100644 index bb5a98d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 - */ -public class ElecraftRig extends BaseRig { - private static final String TAG = "ElecraftRig"; - private final StringBuilder buffer = new StringBuilder(); - private int swr = 0; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(ElecraftRigConstant.setReadMetersSWR()); - } - } - - private void showAlert() { - if (swr >= ElecraftRigConstant.swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - - } - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(ElecraftRigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(ElecraftRigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(ElecraftRigConstant.setOperationFreq11Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains(";")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - // return;//说明数据还没接收完。 - }else { - if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf(";"))); - } - - //开始分析数据 - ElecraftCommand elecraftCommand = ElecraftCommand.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";")+1)); - - if (elecraftCommand == null) { - return; - } - if (elecraftCommand.getCommandID().equalsIgnoreCase("FA") - || elecraftCommand.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq=ElecraftCommand.getFrequency(elecraftCommand); - if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - setFreq(ElecraftCommand.getFrequency(elecraftCommand)); - } - }else if (elecraftCommand.getCommandID().equalsIgnoreCase("SW")) {//METER - if (ElecraftCommand.isSWRMeter(elecraftCommand)) { - swr = ElecraftCommand.getSWRMeter(elecraftCommand); - } - - showAlert(); - } - - - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(ElecraftRigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "Elecraft series"; - } - - public ElecraftRig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java deleted file mode 100644 index 0c3c15e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; - -public class ElecraftRigConstant { - private static final String TAG = "ElecraftRigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x01; - public static final int USB = 0x02; - public static final int CW = 0x03; - public static final int FM = 0x04; - public static final int AM = 0x05; - public static final int DATA = 0x06; - public static final int CW_R = 0x07; - public static final int DATA_R = 0x08; - public static final int swr_alert_max=30;//相当于3.0 - - //PTT状态 - - //指令集 - private static final String PTT_ON = "TX;"; - private static final String PTT_OFF = "RX;"; - private static final String USB_MODE = "MD2;"; - private static final String READ_FREQ = "FA;"; - private static final String READ_SWR = "SWR;"; - - - - - - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW: - return "CW"; - case FM: - return "FM"; - case AM: - return "AM"; - case CW_R: - return "CW_R"; - case DATA: - return "DATA"; - case DATA_R: - return "DATA_R"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - - public static byte[] setOperationUSBMode() { - return USB_MODE.getBytes(); - } - - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 - return String.format("FA%011d;",freq).getBytes(); - } - - - - public static byte[] setReadOperationFreq(){ - return READ_FREQ.getBytes(); - } - - public static byte[] setReadMetersSWR(){ - return READ_SWR.getBytes(); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java deleted file mode 100644 index 483889e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - - -import android.util.Log; - -public class Flex6000Command { - private static final String TAG = "Flex6000Command"; - private final String commandID; - private final String data; - /** - * 获取命令(两字节字符串) - * - * @return 主命令值 - */ - public String getCommandID() {//获取主命令 - return commandID; - } - - /** - * 获取命令数据,字符串,没有分号 - * - * @return 命令数据 - */ - public String getData() {//获取命令数据 - return data; - } - - public Flex6000Command(String commandID, String data) { - this.commandID = commandID; - this.data = data; - } - //解析接收的指令 - - /** - * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 - * - * @param buffer 从串口接收到的数据 - * @return 返回电台指令对象,如果不符合指令的格式,返回null。 - */ - public static Flex6000Command getCommand(String buffer) { - if (buffer.length() < 4) {//指令的长度必须大于等于4,ZZFA - return null; - } - if (buffer.substring(0,4).matches("[a-zA-Z][a-zA-Z][a-zA-Z][a-zA-Z]")) { - return new Flex6000Command(buffer.substring(0, 4), buffer.substring(4)); - } - return null; - } - - - /** - * 计算频率 - * @param command 指令 - * @return 频率 - */ - public static long getFrequency(Flex6000Command command) { - try { - if(command.getCommandID().equals("ZZFA")||command.getCommandID().equals("ZZFB")) { - return Long.parseLong(command.getData()); - }else { - return 0; - } - }catch (Exception e){ - Log.e(TAG, "获取频率失败: "+command.getData()+"\n"+e.getMessage() ); - } - return 0; - } - - - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java deleted file mode 100644 index 003d598..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.os.Handler; -import android.util.Log; - -import com.bg7yoz.ft8cn.database.ControlMode; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * KENWOOD TS590,与YAESU3代指令接近,命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 - */ -public class Flex6000Rig extends BaseRig { - private static final String TAG = "KenwoodTS590Rig"; - private final StringBuilder buffer = new StringBuilder(); - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - readFreqFromRig(); - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Flex6000RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(Flex6000RigConstant.setOperationUSB_DIGI_Mode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Flex6000RigConstant.setOperationFreq(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains(";")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - return;//说明数据还没接收完。 - }else { - if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf(";"))); - } - //开始分析数据 - Flex6000Command flex6000Command = Flex6000Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";")+1)); - - if (flex6000Command == null) { - return; - } - if (flex6000Command.getCommandID().equalsIgnoreCase("ZZFA")) { - long tempFreq=Flex6000Command.getFrequency(flex6000Command); - if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - setFreq(Flex6000Command.getFrequency(flex6000Command)); - } - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Flex6000RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "FLEX 6000 series"; - } - - public Flex6000Rig() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (getConnector()!=null){//切换VFO A - //getConnector().sendData(Flex6000RigConstant.setVFOMode()); - } - } - },START_QUERY_FREQ_DELAY-500); - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java deleted file mode 100644 index 26e4870..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; - -public class Flex6000RigConstant { - private static final String TAG = "Flex6000RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x00; - public static final int USB = 0x01; - public static final int CW_L = 0x03; - public static final int CW_U = 0x04; - public static final int FM = 0x05; - public static final int AM = 0x06; - public static final int DIGI_U= 0x07; - public static final int DIGI_L= 0x09; - - //PTT状态 - - //指令集 - private static final String PTT_ON = "ZZTX1;"; - private static final String PTT_OFF = "ZZTX0;"; - private static final String USB_DIGI = "ZZMD07;"; - private static final String READ_FREQ = "ZZFA;"; - private static final String SET_VFO = "ZZFR"; - - - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW_L: - return "CW_L"; - case CW_U: - return "CW_U"; - case FM: - return "FM"; - case AM: - return "AM"; - case DIGI_U: - return "DIGI_U"; - case DIGI_L: - return "DIGI_L"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - - public static byte[] setFlexTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - - - - //设置成VFO模式 - public static byte[] setVFOMode(){ - return SET_VFO.getBytes(); - } - - - public static byte[] setOperationUSB_DIGI_Mode() { - return USB_DIGI.getBytes(); - } - - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq(long freq) { - return String.format("ZZFA%011d\r",freq).getBytes(); - } - - public static byte[] setReadOperationFreq(){ - return READ_FREQ.getBytes(); - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java deleted file mode 100644 index 1c9a0a8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.connector.FlexConnector; -import com.bg7yoz.ft8cn.flex.FlexCommand; -import com.bg7yoz.ft8cn.flex.FlexRadio; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; - -public class FlexNetworkRig extends BaseRig { - private static final String TAG = "FlexNetworkRig"; - private int commandSeq = 1;//指令的序列 - private FlexCommand flexCommand; - private String commandStr; - - //private final int ctrAddress=0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 - //private byte[] dataBuffer=new byte[0];//数据缓冲区 - @SuppressLint("DefaultLocale") - public void sendCommand(FlexCommand command, String cmdContent) { - if (getConnector().isConnected()) { - commandSeq++; - flexCommand = command; - commandStr = String.format("C%d%03d|%s\n", commandSeq, command.ordinal() - , cmdContent); - getConnector().sendData(commandStr.getBytes()); - } - } - - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceTune(int sliceOder, String freq) { - sendCommand(FlexCommand.SLICE_TUNE, String.format("slice t %d %s", sliceOder, freq)); - } - - @SuppressLint("DefaultLocale") - public synchronized void commandSliceSetMode(int sliceOder, FlexRadio.FlexMode mode) { - sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d mode=%s", sliceOder, mode.toString())); - } - - @Override - public void setPTT(boolean on) { - getConnector().setPttOn(on); - - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式 - } - } - - @SuppressLint("DefaultLocale") - @Override - public void setFreqToRig() { - if (getConnector() != null) { - commandSliceTune(0, String.format("%.3f", getFreq() / 1000000f)); - } - } - - - @Override - public void onReceiveData(byte[] data) { - //ToastMessage.show("--"+byteToStr(data)); - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - //getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); - } - } - - @Override - public void sendWaveData(Ft8Message message) { - - if (getConnector() != null) { - float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() - , 24000);//flex音频的采样率是24000,todo 此处可改为动态设置24000,48000 - if (data == null) { - setPTT(false); - return; - } - getConnector().sendWaveData(data); - } - } - - @Override - public String getName() { - return "FlexRadio series"; - } - - - public String getFrequencyStr() { - return BaseRigOperation.getFrequencyStr(getFreq()); - } - - public FlexNetworkRig() { - Log.d(TAG, "FlexRadio: Create."); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java deleted file mode 100644 index bba0124..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * YAESU的部分电台,回送的数据不是连续的,所以,要做一个缓冲区,接受5字节长度。满了就复位。或发送指令时,就复位。 - */ -public class GuoHeQ900Rig extends BaseRig { - private static final String TAG = "GuoHeQ900Rig"; - private Timer readFreqTimer = new Timer(); - private byte[] buffer; - private int dataCount=-1; - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - readFreqFromRig(); - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - Log.d(TAG, "setPTT: " + on); - - if (getConnector() != null) { - getConnector().setPttOn(GuoHeRigConstant.setPTTState(on)); - } - } - - public synchronized void setPttOn(byte[] command) { - - getConnector().sendData(command);//以CAT指令发送PTT - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(GuoHeRigConstant.setOperationUSBMode());//USB模式 - //getConnector().sendData(GuoHeRigConstant.setOperationFT8Mode());//FT8模式 - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(GuoHeRigConstant.setOperationFreq(getFreq())); - } - } - - - private int checkHead(byte[] data) { - int count = 0; - for (int i = 0; i < data.length; i++) { - if (data[i] == (byte) 0xa5) { - count++; - if (count == 4) { - return i+1; - } - } - } - return -1; - } - - private void clearBuffer(){ - dataCount=-1; - buffer=null; - } - - @Override - public void onReceiveData(byte[] data) { - synchronized (this) { - try { - int startIndex = checkHead(data); - if (startIndex != -1) { - int len = data[startIndex]+1; - - buffer = new byte[len]; - dataCount = 0; - for (int i = startIndex ; i < data.length; i++) { - buffer[dataCount] = data[i]; - dataCount++; - if (dataCount == buffer.length) { - break; - } - } - } else { - if (buffer == null) { - return; - } - if (dataCount < buffer.length && dataCount > 0) { - for (int i = 0; i < data.length; i++) { - buffer[dataCount] = data[i]; - dataCount++; - if (dataCount == buffer.length) { - break; - } - } - } - } - - if (buffer.length == dataCount) {//说明已经收取全部指令 - byte[] crcData=new byte[buffer.length-2]; - for (int i = 0; i < crcData.length; i++) { - crcData[i]=buffer[i]; - } - //Log.e(TAG, "onReceiveData: crc data:"+byteToStr(crcData) ); - //Log.e(TAG, "onReceiveData: crc --->"+String.format("%x",CRC16.crc16(crcData)) ); - int crc=CRC16.crc16(crcData); - int ttt=((buffer[buffer.length-2]& 0xFF)<<8)|(buffer[buffer.length-1]&0xff); - //Log.e(TAG, "onReceiveData:数据内容:" + byteToStr(buffer)); - if (crc==ttt) {//crc校验成功 - - if (buffer[1] == (byte) 0x0b) {//是电台状态指令 - - long vfoa = ((buffer[5] & 0xFFL) << 24) | - ((buffer[6] & 0xFFL) << 16) | - ((buffer[7] & 0xFFL) << 8) | - ((buffer[8] & 0xFFL)); - long vfob = ((buffer[9] & 0xFFL) << 24) | - ((buffer[10] & 0xFFL) << 16) | - ((buffer[11] & 0xFFL) << 8) | - ((buffer[12] & 0xFFL)); - - if (buffer[13] == (byte) 0x00) { - setFreq(vfoa); - //Log.e(TAG, "onReceiveData: is VFO a"); - } else { - setFreq(vfob); - //Log.e(TAG, "onReceiveData: is VFO b"); - } - //Log.e(TAG, "onReceiveData: vfoa:" + vfoa); - //Log.e(TAG, "onReceiveData: vfob:" + vfob); - - } - } - clearBuffer(); - } - } catch (Exception e) { - Log.e(TAG, "onReceiveData: " + e.getMessage()); - } - } - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - getConnector().sendData(GuoHeRigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "GuoHe series"; - } - - public GuoHeQ900Rig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java deleted file mode 100644 index d07a6f1..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -public class GuoHeRigConstant { - private static final String TAG = "Yaesu2RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int USB = 0x00; - public static final int LSB = 0x01; - public static final int CW_R = 0x02; - public static final int CW_L = 0x03; - public static final int AM = 0x04; - public static final int WFM = 0x05; - public static final int NFM = 0x06; - public static final int DIGI = 0x07; - public static final int PKT = 0x08; - - //PTT状态 - - //指令集 -// private static final byte[] PTT_ON = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}; -// private static final byte[] PTT_OFF = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88}; - private static final byte[] PTT_ON = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x04, (byte) 0x07, (byte) 0x00, (byte) 0x89, (byte) 0xCB}; - private static final byte[] PTT_OFF = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x04, (byte) 0x07, (byte) 0x01, (byte) 0x99, (byte) 0xEA}; - private static final byte[] USB_MODE = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x05, (byte) 0x0A, (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0x44}; - private static final byte[] FT8_MODE = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x05, (byte) 0x0A, (byte) 0x08, (byte) 0x08, (byte) 0xF7, (byte) 0xE5}; - private static final byte[] READ_FREQ = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x03, (byte) 0x0B, (byte) 0xF9, (byte) 0x37}; -// private static final byte[] READ_FREQ = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03}; - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW_R: - return "CW_R"; - case CW_L: - return "CW_L"; - case AM: - return "AM"; - case WFM: - return "WFM"; - case NFM: - return "NFM"; - case DIGI: - return "DIGI"; - case PKT: - return "PKT"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON; - } else { - return PTT_OFF; - } - - } - - public static byte[] setOperationUSBMode() { - return USB_MODE; - } - public static byte[] setOperationFT8Mode() { - return FT8_MODE; - } - public static byte[] setOperationFreq(long freq) { - - byte[] data = new byte[]{(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x0b, (byte) 0x09 - , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00//频率VFOA - , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00//频率VFOB - , (byte) 0x3B, (byte) 0x6B}; - - data[6] = (byte) ((0x0000000000ff000000 & freq) >> 24); - data[7] = (byte) ((0x000000000000ff0000 & freq) >> 16); - data[8] = (byte) ((0x00000000000000ff00 & freq) >> 8); - data[9] = (byte) (0x0000000000000000ff & freq); - data[10] = (byte) ((0x0000000000ff000000 & freq) >> 24); - data[11] = (byte) ((0x000000000000ff0000 & freq) >> 16); - data[12] = (byte) ((0x00000000000000ff00 & freq) >> 8); - data[13] = (byte) (0x0000000000000000ff & freq); - - byte[] crcData=new byte[]{(byte)0x0b,(byte) 0x09 - ,(byte) ((0x0000000000ff000000 & freq) >> 24) - ,(byte) ((0x000000000000ff0000 & freq) >> 16) - ,(byte) ((0x00000000000000ff00 & freq) >> 8) - ,(byte) (0x0000000000000000ff & freq) - ,(byte) ((0x0000000000ff000000 & freq) >> 24) - ,(byte) ((0x000000000000ff0000 & freq) >> 16) - ,(byte) ((0x00000000000000ff00 & freq) >> 8) - ,(byte) (0x0000000000000000ff & freq)}; - - int crc=CRC16.crc16(crcData); - data[14]=(byte) ((crc&0x00ff00)>>8); - data[15]=(byte) (crc&0x0000ff); - - - - return data; - - } - - public static byte[] setReadOperationFreq() { - return READ_FREQ; - } - - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java deleted file mode 100644 index dae403b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - - -import android.util.Log; - -public class IcomCommand { - private static final String TAG = "RigCommand"; - private byte[] rawData; - - /** - * 获取主命令 - * - * @return 主命令值 - */ - public int getCommandID() {//获取主命令 - if (rawData.length < 5) { - return -1; - } - return rawData[4]; - } - - /** - * 获取子命令,有的指令没有子命令,要注意。 - * - * @return 子命令 - */ - public int getSubCommand() {//获取子命令 - if (rawData.length < 7) { - return -1; - } - return rawData[5]; - } - - /** - * 获取带2字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。 - * @return 子指令 - */ - public int getSubCommand2() {//获取子命令 - if (rawData.length < 8) { - return -1; - } - return readShortData(rawData,6); - } - /** - * 获取带3字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。 - * @return 子指令 - */ - public int getSubCommand3() {//获取子命令 - if (rawData.length < 9) { - return -1; - } - return ((int) rawData[7] & 0xff) - | ((int) rawData[6] & 0xff) << 8 - | ((int) rawData[5] & 0xff) << 16; - - - } - - /** - * 获取数据区,有的指令有子命令,有的没有子命令,所以要区分出来。子命令占一个字节 - * - * @param hasSubCommand 是否有子命令 - * @return 返回数据区 - */ - public byte[] getData(boolean hasSubCommand) { - int pos; - - if (hasSubCommand) { - pos = 6; - } else { - pos = 5; - } - if (rawData.length < pos + 1) {//没有数据区了 - return null; - } - - byte[] data = new byte[rawData.length - pos]; - - for (int i = 0; i < rawData.length - pos; i++) { - data[i] = rawData[pos + i]; - } - return data; - } - - public byte[] getData2Sub() { - if (rawData.length < 9) {//没有数据区了 - return null; - } - - byte[] data = new byte[rawData.length - 8]; - - System.arraycopy(rawData, 8, data, 0, rawData.length - 8); - return data; - } - //解析接收的指令 - - /** - * 从串口中接到的数据解析出指令的数据:FE FE E0 A4 Cn Sc data FD - * - * @param ctrAddr 控制者地址,默认E0或00 - * @param rigAddr 电台地址,705默认是A4 - * @param buffer 从串口接收到的数据 - * @return 返回电台指令对象,如果不符合指令的格式,返回null。 - */ - public static IcomCommand getCommand(int ctrAddr, int rigAddr, byte[] buffer) { - Log.d(TAG, "getCommand: "+BaseRig.byteToStr(buffer) ); - if (buffer.length <= 5) {//指令的长度不可能小于等5 - return null; - } - int position = -1;//指令的位置 - for (int i = 0; i < buffer.length; i++) { - if (i + 6 > buffer.length) {//说明没找到指令 - return null; - } - if (buffer[i] == (byte) 0xfe - && buffer[i + 1] == (byte) 0xfe//命令头0xfe 0xfe - && (buffer[i + 2] == (byte) ctrAddr || buffer[i + 2] == (byte) 0x00)//控制者地址默认E0或00 - && buffer[i + 3] == (byte) rigAddr) {//电台地址,705的默认值是A4 - position = i; - break; - } - } - //说明没找到 - if (position == -1) { - return null; - } - - int dataEnd = -1; - //从命令头之后查起。所以i=position - for (int i = position; i < buffer.length; i++) { - if (buffer[i] == (byte) 0xfd) {//是否到结尾了 - dataEnd = i; - break; - } - } - if (dataEnd == -1) {//说明没找到结尾 - return null; - } - - IcomCommand icomCommand = new IcomCommand(); - icomCommand.rawData = new byte[dataEnd - position]; - int pos = 0; - for (int i = position; i < dataEnd; i++) {//把指令数据搬到rawData中 - //icomCommand.rawData[i] = buffer[i]; - icomCommand.rawData[pos] = buffer[i];//定位错误 - pos++; - } - return icomCommand; - } - - - /** - * 从数据区中计算频率BCD码 - * - * @param hasSubCommand 是否含有子命令 - * @return 返回频率值 - */ - public long getFrequency(boolean hasSubCommand) { - byte[] data = getData(hasSubCommand); - if (data.length < 5) { - return -1; - } - return (int) (data[0] & 0x0f)//取个位 1hz - + ((int) (data[0] >> 4) & 0xf) * 10//取十位 10hz - + (int) (data[1] & 0x0f) * 100//百位 100hz - + ((int) (data[1] >> 4) & 0xf) * 1000//千位 1khz - + (int) (data[2] & 0x0f) * 10000//万位 10khz - + ((int) (data[2] >> 4) & 0xf) * 100000//十万位 100khz - + (int) (data[3] & 0x0f) * 1000000//百万位 1Mhz - + ((int) (data[3] >> 4) & 0xf) * 10000000//千万位 10Mhz - + (int) (data[4] & 0x0f) * 100000000//亿位 100Mhz - + ((int) (data[4] >> 4) & 0xf) * 100000000;//十亿位 1Ghz - } - - - /** - * 把字节转换成short,不做小端转换!! - * - * @param data 字节数据 - * @return short - */ - public static short readShortData(byte[] data, int start) { - if (data.length - start < 2) return 0; - return (short) ((short) data[start + 1] & 0xff - | ((short) data[start] & 0xff) << 8); - } - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java deleted file mode 100644 index 129aea2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java +++ /dev/null @@ -1,275 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.util.Log; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; -import com.bg7yoz.ft8cn.icom.IComPacketTypes; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -public class IcomRig extends BaseRig { - private static final String TAG = "IcomRig"; - - private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 - private byte[] dataBuffer = new byte[0];//数据缓冲区 - private int alc = 0; - private int swr = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - private Timer meterTimer;//查询meter的Timer - //private boolean isPttOn = false; - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - //isPttOn = on; - alcMaxAlert = false; - swrAlert = false; - if (on) { - //修正连接方式0x03是wlan,01是usb,0x02是usb+mic,确保声音能发送到电台 - if (GeneralVariables.connectMode == ConnectMode.NETWORK) { - sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x03)); - } else if (GeneralVariables.connectMode == ConnectMode.USB_CABLE) { - sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x01)); - } else { - sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x02)); - } - } - - if (getConnector() != null) { - if (GeneralVariables.connectMode == ConnectMode.NETWORK) { - getConnector().setPttOn(on); - return; - } - - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() - , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); - break; - //case ControlMode.NETWORK: - case ControlMode.RTS: - case ControlMode.DTR: - - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - //因为担心老的ICOM电台不一定支持USB-D,所以,先做个一USB模式,再进入USB-D模式, - // 这样,如果USB-D模式不支持,USB-D的指令就是无效的,电台就停留在USB模式下了 - //getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress - // , getCivAddress(), IcomRigConstant.USB));//usb - getConnector().sendData(IcomRigConstant.setOperationDataMode(ctrAddress - , getCivAddress(), IcomRigConstant.USB));//usb-d - } - } - - private void sendCivData(byte[] data) { - if (getConnector() != null) { - getConnector().sendData(data); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress - , getCivAddress(), getFreq())); - } - } - - /** - * 查找指令的结尾的位置,如果没找到,值是-1。 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandEnd(byte[] data) { - for (int i = 0; i < data.length; i++) { - if (data[i] == (byte) 0xFD) { - return i; - } - } - return -1; - } - - /** - * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandHead(byte[] data) { - if (data.length < 2) return -1; - for (int i = 0; i < data.length - 1; i++) { - if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { - return i; - } - } - return -1; - } - - @Override - public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式 - if (getConnector() != null) { - float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() - ,12000);//此处icom电台发射音频的采样率是12000,todo 此处可改为动态设置24000,48000 - if (data==null){ - setPTT(false); - return; - } - getConnector().sendWaveData(data); - } - } - - private void analysisCommand(byte[] data) { - int headIndex = getCommandHead(data); - if (headIndex == -1) {//说明没有指令头 - return; - } - IcomCommand icomCommand; - if (headIndex == 0) { - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); - } else { - byte[] temp = new byte[data.length - headIndex]; - System.arraycopy(data, headIndex, temp, 0, temp.length); - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); - } - if (icomCommand == null) { - return; - } - - //目前只对频率和模式消息作反应 - switch (icomCommand.getCommandID()) { - case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 - case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: - //获取频率 - setFreq(icomCommand.getFrequency(false)); - break; - case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 - case IcomRigConstant.CMD_READ_OPERATING_MODE: - break; - case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 - if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_ALC) { - alc = IcomRigConstant.twoByteBcdToInt(icomCommand.getData(true)); - } - if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { - swr = IcomRigConstant.twoByteBcdToInt(icomCommand.getData(true)); - } - showAlert();//检查meter值是否在告警范围 - break; - case IcomRigConstant.CMD_CONNECTORS: - break; - - } - } - - private void showAlert() { - if (swr >= IcomRigConstant.swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > IcomRigConstant.alc_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - - @Override - public void onReceiveData(byte[] data) { - - //ToastMessage.show("--"+byteToStr(data)); - - - int commandEnd = getCommandEnd(data); - if (commandEnd <= -1) {//这是没有指令结尾 - byte[] temp = new byte[dataBuffer.length + data.length]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - System.arraycopy(data, 0, temp, dataBuffer.length, data.length); - dataBuffer = temp; - } else { - byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - dataBuffer = temp; - System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); - } - if (commandEnd != -1) { - analysisCommand(dataBuffer); - } - dataBuffer = new byte[0];//清空缓冲区 - if (commandEnd <= -1 || commandEnd < data.length) { - byte[] temp = new byte[data.length - commandEnd + 1]; - for (int i = 0; i < data.length - commandEnd - 1; i++) { - temp[i] = data[commandEnd + i + 1]; - } - dataBuffer = temp; - } - - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); - } - } - - @Override - public String getName() { - return "ICOM series"; - } - - public void startMeterTimer() { - meterTimer = new Timer(); - meterTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (isPttOn()) {//当Ptt被按下去的时候测量 - sendCivData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); - sendCivData(IcomRigConstant.getALCState(ctrAddress, getCivAddress())); - } - } - }, 0, IComPacketTypes.METER_TIMER_PERIOD_MS); - } - - - public String getFrequencyStr() { - return BaseRigOperation.getFrequencyStr(getFreq()); - } - - public IcomRig(int civAddress) { - Log.d(TAG, "IcomRig: Create."); - setCivAddress(civAddress); - startMeterTimer(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java deleted file mode 100644 index 5f781e0..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.util.Log; - -public class IcomRigConstant { - private static final String TAG = "IcomRigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0; - public static final int USB = 1; - public static final int AM = 2; - public static final int CW = 3; - public static final int RTTY = 4; - public static final int FM = 5; - public static final int WFM = 6; - public static final int CW_R = 7; - public static final int RTTY_R = 8; - public static final int DV = 0x17; - public static final int UNKNOWN = -1; - - - public static final int swr_alert_max=120;//相当于3.0 - public static final int alc_alert_max=120;//超过,在表上显示红色 - - - - //PTT状态 - public static final int PTT_ON = 1; - public static final int PTT_OFF = 0; - - //指令集 - public static final byte CMD_RESULT_OK = (byte) 0xfb;// - public static final byte CMD_RESULT_FAILED = (byte) 0xfa;// - - public static final byte[] SEND_FREQUENCY_DATA = {0x00};//发送频率数据 - public static final byte CMD_SEND_FREQUENCY_DATA = 0x00;//发送频率数据 - - public static final byte[] SEND_MODE_DATA = {0x01};//发送模式数据 - public static final byte CMD_SEND_MODE_DATA = 0x01;//发送模式数据 - - public static final byte[] READ_BAND_EDGE_DATA = {0x02};//读频率的波段边界 - public static final byte CMD_READ_BAND_EDGE_DATA = 0x02;//读频率的波段边界 - - public static final byte[] READ_OPERATING_FREQUENCY = {0x03};//发送模式数据 - public static final byte CMD_READ_OPERATING_FREQUENCY = 0x03;//发送模式数据 - - public static final byte[] READ_OPERATING_MODE = {0x04};//读取操作模式 - public static final byte CMD_READ_OPERATING_MODE = 0x04;//读取操作模式 - - public static final byte[] SET_OPERATING_FREQUENCY = {0x05};//设置操作的频率 - public static final byte CMD_SET_OPERATING_FREQUENCY = 0x05;//设置操作的频率 - - public static final byte[] SET_OPERATING_MODE = {0x06};//设置操作的模式 - public static final byte CMD_SET_OPERATING_MODE = 0x06;//设置操作的模式 - - public static final byte CMD_READ_METER = 0x15;//读meter - public static final byte CMD_READ_METER_SWR = 0x12;//读meter子命令,驻波表 - public static final byte CMD_READ_METER_ALC = 0x13;//读meter子命令,ALC表 - public static final byte CMD_CONNECTORS = 0x1A;//Connector设置,读取 - public static final byte CMD_CONNECTORS_DATA_MODE = 0x05;//Connector设置,读取 - public static final int CMD_CONNECTORS_DATA_WLAN_LEVEL = 0x050117;//Connector设置,读取 - - - - - - public static final byte CMD_COMMENT_1A = 0x1A;//1A指令 - public static final byte[] SET_READ_PTT_STATE = {0x1A, 0x00, 0x48};//读取或设置PTT状态,不建议使用 - - public static final byte[] READ_TRANSCEIVER_STATE = {0x1A, 0x00, 0x48};//读取电台发射状态 - public static final byte[] SET_TRANSCEIVER_STATE_ON = {0x1C, 0x00, 0x01};//设置电台处于发射状态TX - public static final byte[] SET_TRANSCEIVER_STATE_OFF = {0x1C, 0x00, 0x00};//设置电台关闭发射状态RX - public static final byte[] READ_TRANSMIT_FREQUENCY = {0x1C, 0x03};//读取电台发射时的频率 - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case AM: - return "AM"; - case CW: - return "CW"; - case RTTY: - return "RTTY"; - case FM: - return "FM"; - case CW_R: - return "CW_R"; - case RTTY_R: - return "RTTY_R"; - case DV: - return "DV"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(int ctrAddr, int rigAddr, int state) { - //1C指令,例如PTT ON:FE FE A1 E0 1C 00 01 FD - byte[] data = new byte[8]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) 0x1c;//主指令代码 - data[5] = (byte) 0x00;//子指令代码 - data[6] = (byte) state;//状态 01=tx 00=rx - data[7] = (byte) 0xfd; - return data; - } - - /** - * 读驻波表 - * - * @param ctrAddr 我的地址 - * @param rigAddr 电台地址 - * @return 指令数据包 - */ - public static byte[] getSWRState(int ctrAddr, int rigAddr) { - //1C指令,例如PTT ON:FE FE A1 E0 15 12 FD - byte[] data = new byte[7]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) CMD_READ_METER;//主指令代码 - data[5] = (byte) CMD_READ_METER_SWR;//子指令代码SWR - data[6] = (byte) 0xfd; - return data; - } - - /** - * 读ALC表 - * - * @param ctrAddr 我的地址 - * @param rigAddr 电台地址 - * @return 指令数据包 - */ - public static byte[] getALCState(int ctrAddr, int rigAddr) { - //1C指令,例如PTT ON:FE FE A1 E0 15 12 FD - byte[] data = new byte[7]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) CMD_READ_METER;//主指令代码 - data[5] = (byte) CMD_READ_METER_ALC;//子指令代码ALC - data[6] = (byte) 0xfd; - return data; - } - - public static byte[] getConnectorWLanLevel(int ctrAddr, int rigAddr){ - //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 17 FD - byte[] data = new byte[9]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) CMD_CONNECTORS;//主指令代码1A - data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//WLan level - data[6] = (byte) 0x01; - data[7] = (byte) 0x17; - data[8] = (byte) 0xfd; - return data; - } - - public static byte[] setConnectorWLanLevel(int ctrAddr, int rigAddr,int level){ - //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 17 FD - byte[] data = new byte[11]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) CMD_CONNECTORS;//主指令代码1A - data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//子指令代码ALC - data[6] = (byte) 0x01; - data[7] = (byte) 0x17; - data[8] = (byte) (level >> 8 & 0xff); - data[9] = (byte) (level &0xff); - data[10] = (byte) 0xfd; - return data; - } - - //设置数据通讯方式 - public static byte[] setConnectorDataMode(int ctrAddr, int rigAddr,byte mode){ - //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 19 FD - byte[] data = new byte[10]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) CMD_CONNECTORS;//主指令代码1A - data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//子指令代码ALC - data[6] = (byte) 0x01;// - data[7] = (byte) 0x19;// - data[8] = (byte) mode;//数据连接的方式 - data[9] = (byte) 0xfd; - return data; - } - public static byte[] setOperationMode(int ctrAddr, int rigAddr, int mode) { - //06指令,例如USB=01:FE FE A1 E0 06 01 FD - byte[] data = new byte[8]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) 0x06;//指令代码 - data[5] = (byte) mode;//USB=01 - data[6] = (byte) 0x01;//fil1 - data[7] = (byte) 0xfd; - return data; - } - - public static byte[] setOperationDataMode(int ctrAddr, int rigAddr, int mode) { - //26指令,例如USB-D=01:FE FE A1 E0 26 01 01 01 FD - byte[] data = new byte[10]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr;//70 - data[3] = (byte) ctrAddr;//E0 - data[4] = (byte) 0x26;//指令代码 - data[5] = (byte) 0x00;//指令代码 - data[6] = (byte) mode;//USB=01 - data[7] = (byte) 0x01;//data模式 - data[8] = (byte) 0x01;//fil1 - data[9] = (byte) 0xfd; - return data; - } - - public static byte[] setReadFreq(int ctrAddr, int rigAddr) { - //06指令,例如USB=01:FE FE A1 E0 06 01 FD - byte[] data = new byte[6]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) 0x03;//指令代码 - data[5] = (byte) 0xfd; - return data; - } - - - public static byte[] setOperationFrequency(int ctrAddr, int rigAddr, long freq) { - //05指令,例如14.074M:FE FE A4 E0 05 00 40 07 14 00 FD - byte[] data = new byte[11]; - data[0] = (byte) 0xfe; - data[1] = (byte) 0xfe; - data[2] = (byte) rigAddr; - data[3] = (byte) ctrAddr; - data[4] = (byte) 0x05;//指令代码 - data[5] = (byte) (((byte) (freq % 100 / 10) << 4) + (byte) (freq % 10)); - data[6] = (byte) (((byte) (freq % 10000 / 1000) << 4) + (byte) (freq % 1000 / 100)); - data[7] = (byte) (((byte) (freq % 1000000 / 100000) << 4) + (byte) (freq % 100000 / 10000)); - data[8] = (byte) (((byte) (freq % 100000000 / 10000000) << 4) + (byte) (freq % 10000000 / 1000000)); - data[9] = (byte) (((byte) (freq / 1000000000) << 4) + (byte) (freq % 1000000000 / 100000000)); - data[10] = (byte) 0xfd; - - Log.d(TAG, "setOperationFrequency: " + BaseRig.byteToStr(data)); - return data; - } - - public static int twoByteBcdToInt(byte[] data) { - if (data.length < 2) return 0; - return (int) (data[1] & 0x0f)//取个位 - + ((int) (data[1] >> 4) & 0xf) * 10//取十位 - + (int) (data[0] & 0x0f) * 100//百位 - + ((int) (data[0] >> 4) & 0xf) * 1000;//千位 - - } - public static int twoByteBcdToIntBigEnd(byte[] data) { - if (data.length < 2) return 0; - return (int) (data[0] & 0x0f)//取个位 - + ((int) (data[0] >> 4) & 0xf) * 10//取十位 - + (int) (data[1] & 0x0f) * 100//百位 - + ((int) (data[1] >> 4) & 0xf) * 1000;//千位 - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java deleted file mode 100644 index 46ad6b2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -public class InstructionSet { - public static final int ICOM=0; - public static final int YAESU_2=1;//5字节数据 817 - public static final int YAESU_3_9=2;//9字节频率 - public static final int YAESU_3_8=3;//8字节频率 - public static final int YAESU_3_450=4;//PTT发射不通 - public static final int KENWOOD_TK90=5;//11字节频率,VFO模式下才可用 - public static final int YAESU_DX10=6;//9字节,DATA-U模式 - public static final int KENWOOD_TS590=7;//11字节频率,VFO模式 - public static final int GUOHE_Q900=8;//国赫Q900,5字节一次性接收,与YAESU2代不同 - public static final int XIEGUG90S=9;//协谷G90S,指令与ICOM兼容,但不会主动发送频率的变化,需要定期获取 - public static final int ELECRAFT=10;//Elecraft k3 - public static final int FLEX_CABLE=11;//FLEX6000; - public static final int FLEX_NETWORK=12;//FLEX网络模式连接; - public static final int XIEGU_6100=13;//协谷X6100; - public static final int KENWOOD_TS2000=14;//建武TS2000,发射指令是TX0; - public static final int WOLF_SDR_DIGU=15;//UA3REO Wolf SDR,DIG-U模式,兼容YAESU 450D; - public static final int WOLF_SDR_USB=16;//UA3REO Wolf SDR,USB模式,兼容YAESU 450D; - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java deleted file mode 100644 index d044da8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.os.Handler; -import android.util.Log; - -import com.bg7yoz.ft8cn.database.ControlMode; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 - */ -public class KenwoodKT90Rig extends BaseRig { - private static final String TAG = "KenwoodKT90Rig"; - private final StringBuilder buffer = new StringBuilder(); - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - readFreqFromRig(); - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(KenwoodTK90RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setOperationFreq(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains("\r")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - //说明数据还没接收完。 - }else { - if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf("\r"))); - } - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf("\r")+1)); - - if (yaesu3Command == null) { - return; - } - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA")) { - long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(KenwoodTK90RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "KENWOOD TK90"; - } - - public KenwoodKT90Rig() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (getConnector()!=null){ - getConnector().sendData(KenwoodTK90RigConstant.setVFOMode()); - } - } - },START_QUERY_FREQ_DELAY-500); - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java deleted file mode 100644 index 624488b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; - -public class KenwoodTK90RigConstant { - private static final String TAG = "KenwoodTK90RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x01; - public static final int USB = 0x02; - public static final int CW = 0x03; - public static final int FSK = 0x04; - public static final int AM = 0x05; - public static final int DATA = 0x06; - - public static final int ts_590_swr_alert_max=15;//相当于3.0 - public static final int ts_590_alc_alert_max=15;//超过,在表上显示红色 - - //PTT状态 - - //指令集 - private static final String PTT_ON = "TX\r"; - private static final String PTT_OFF = "RX\r"; - private static final String USB_MODE = "MD2\r"; - private static final String READ_FREQ = "FA\r"; - private static final String READ_METERS = "RM\r"; - private static final String SET_VFO = "FR0\r"; - - - - private static final String TS590_VFO_A="FR0;";//KENWOOD TS590,设置VFO -A - private static final String TS2000_PTT_ON="TX0;";//KENWOOD TS2000,PTT - private static final String TS590_PTT_ON="TX1;";//KENWOOD TS590,PTT - private static final String FLEX_6000_PTT_ON="TX01;";//FLEX_6000,PTT - private static final String TS590_PTT_OFF="RX;";//KENWOOD TS590,PTT - private static final String FLEX_SET_USB_DATA="MD9;";//FLEX6000 DIGU - private static final String TS590_SET_USB="MD2;";//KENWOOD USB MODE - private static final String TS590_READ_FREQ = "FA;";//KENWOOD 读频率 - private static final String TS590_READ_METERS = "RM;";//KENWOOD 读METER - - - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW: - return "CW"; - case FSK: - return "FSK"; - case AM: - return "AM"; - case DATA: - return "DATA"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - - public static byte[] setTS590PTTState(boolean on) { - if (on) { - return TS590_PTT_ON.getBytes(); - } else { - return TS590_PTT_OFF.getBytes(); - } - - } public static byte[] setTS2000PTTState(boolean on) { - if (on) { - return TS2000_PTT_ON.getBytes(); - } else { - return TS590_PTT_OFF.getBytes(); - } - - } - public static byte[] setFLEX6000PTTState(boolean on) { - if (on) { - return FLEX_6000_PTT_ON.getBytes(); - } else { - return TS590_PTT_OFF.getBytes(); - } - - } - - - //设置成VFO模式 - public static byte[] setVFOMode(){ - return SET_VFO.getBytes(); - } - public static byte[] setTS590VFOMode(){ - return TS590_VFO_A.getBytes(); - } - - public static byte[] setOperationUSBMode() { - return USB_MODE.getBytes(); - } - public static byte[] setTS590OperationUSBMode() { - return TS590_SET_USB.getBytes(); - } - public static byte[] setFLEX6000OperationUSBMode() { - return FLEX_SET_USB_DATA.getBytes(); - } - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq(long freq) { - return String.format("FA%011d\r",freq).getBytes(); - } - @SuppressLint("DefaultLocale") - public static byte[] setTS590OperationFreq(long freq) { - return String.format("FA%011d;",freq).getBytes(); - } - - public static byte[] setReadOperationFreq(){ - return READ_FREQ.getBytes(); - } - public static byte[] setRead590Meters(){ - return TS590_READ_METERS.getBytes(); - } - - public static byte[] setTS590ReadOperationFreq(){ - return TS590_READ_FREQ.getBytes(); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java deleted file mode 100644 index 848884c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.os.Handler; -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * KENWOOD TS2000,与YAESU3代指令接近,与TS590基本相同,唯一不同的是,PTT打开指令是TX0;而TS590是用的TX1; - * 命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 - */ -public class KenwoodTS2000Rig extends BaseRig { - private static final String TAG = "KenwoodTS590Rig"; - private final StringBuilder buffer = new StringBuilder(); - - private Timer readFreqTimer = new Timer(); - private int swr=0; - private int alc=0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()){ - readMeters();//读METER - }else { - readFreqFromRig();//读频率 - } - - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 读取Meter RM; - */ - private void readMeters(){ - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(KenwoodTK90RigConstant.setRead590Meters()); - } - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(KenwoodTK90RigConstant.setTS2000PTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationFreq(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains("\r")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - //return;//说明数据还没接收完。 - }else { - if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf("\r"))); - } - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf("\r")+1)); - - if (yaesu3Command == null) { - return; - } - String cmd=yaesu3Command.getCommandID(); - if (cmd.equalsIgnoreCase("FA")) {//频率 - long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - }else if (cmd.equalsIgnoreCase("RM")){//meter - if (Yaesu3Command.is590MeterSWR(yaesu3Command)) { - swr = Yaesu3Command.get590ALCOrSWR(yaesu3Command); - } - if (Yaesu3Command.is590MeterALC(yaesu3Command)) { - alc = Yaesu3Command.get590ALCOrSWR(yaesu3Command); - } - showAlert(); - } - - } - - } - private void showAlert() { - if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq()); - } - } - - @Override - public String getName() { - return "KENWOOD TS-2000"; - } - - public KenwoodTS2000Rig() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (getConnector()!=null){ - getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode()); - } - } - },START_QUERY_FREQ_DELAY-500); - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java deleted file mode 100644 index fb59880..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.os.Handler; -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * KENWOOD TS590,与YAESU3代指令接近,命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 - */ -public class KenwoodTS590Rig extends BaseRig { - private static final String TAG = "KenwoodTS590Rig"; - private final StringBuilder buffer = new StringBuilder(); - - private Timer readFreqTimer = new Timer(); - private int swr=0; - private int alc=0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()){ - readMeters();//读METER - }else { - readFreqFromRig();//读频率 - } - - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 读取Meter RM; - */ - private void readMeters(){ - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(KenwoodTK90RigConstant.setRead590Meters()); - } - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(KenwoodTK90RigConstant.setTS590PTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationFreq(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains("\r")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - //return;//说明数据还没接收完。 - }else { - if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf("\r"))); - } - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf("\r")+1)); - - if (yaesu3Command == null) { - return; - } - String cmd=yaesu3Command.getCommandID(); - if (cmd.equalsIgnoreCase("FA")) {//频率 - long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - }else if (cmd.equalsIgnoreCase("RM")){//meter - if (Yaesu3Command.is590MeterSWR(yaesu3Command)) { - swr = Yaesu3Command.get590ALCOrSWR(yaesu3Command); - } - if (Yaesu3Command.is590MeterALC(yaesu3Command)) { - alc = Yaesu3Command.get590ALCOrSWR(yaesu3Command); - } - showAlert(); - } - - } - - } - private void showAlert() { - if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq()); - } - } - - @Override - public String getName() { - return "KENWOOD TS-480/590"; - } - - public KenwoodTS590Rig() { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (getConnector()!=null){ - getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode()); - } - } - },START_QUERY_FREQ_DELAY-500); - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java deleted file mode 100644 index 3fc4542..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -/** - * 从电台接收到数据的回调 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnConnectReceiveData { - void onData(byte[] data); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java deleted file mode 100644 index 4e638cf..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -/** - * 电台状态的回调。 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnRigStateChanged { - void onDisconnected(); - void onConnected(); - void onPttChanged(boolean isOn); - void onFreqChanged(long freq); - void onRunError(String message); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java deleted file mode 100644 index 232e495..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -public class XieGu6100Rig extends BaseRig { - private static final String TAG = "IcomRig"; - - private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 - private byte[] dataBuffer = new byte[0];//数据缓冲区 - private int swr = 0; - private boolean swrAlert = false; - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()){ - readSWRMeter(); - }else { - readFreqFromRig(); - } - - } catch (Exception e) { - Log.e(TAG, "readFreq or meter error:" + e.getMessage()); - } - } - }; - } - - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() - , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { -// getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress -// , getCivAddress(), 1));//usb=1 - getConnector().sendData(IcomRigConstant.setOperationDataMode(ctrAddress - , getCivAddress(), IcomRigConstant.USB));//usb-d - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress - , getCivAddress(), getFreq())); - } - } - - /** - * 查找指令的结尾的位置,如果没找到,值是-1。 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandEnd(byte[] data) { - for (int i = 0; i < data.length; i++) { - if (data[i] == (byte) 0xFD) { - return i; - } - } - return -1; - } - - /** - * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandHead(byte[] data) { - if (data.length < 2) return -1; - for (int i = 0; i < data.length - 1; i++) { - if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { - return i; - } - } - return -1; - } - - private void analysisCommand(byte[] data) { - int headIndex = getCommandHead(data); - if (headIndex == -1) {//说明没有指令头 - return; - } - IcomCommand icomCommand; - if (headIndex == 0) { - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); - } else { - byte[] temp = new byte[data.length - headIndex]; - System.arraycopy(data, headIndex, temp, 0, temp.length); - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); - } - if (icomCommand == null) { - return; - } - - //目前只对频率和模式消息作反应 - switch (icomCommand.getCommandID()) { - case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 - case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: - //获取频率 - long freqTemp = icomCommand.getFrequency(false); - if (freqTemp >= 500000 && freqTemp <= 250000000) {//协谷的频率范围 - setFreq(freqTemp); - } - break; - case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 - case IcomRigConstant.CMD_READ_OPERATING_MODE: - break; - case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 - if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { - //协谷的小端模式 - int temp=IcomRigConstant.twoByteBcdToIntBigEnd(icomCommand.getData(true)); - if (temp!=255) { - swr = temp;// - } - } - showAlert();//检查meter值是否在告警范围 - - break; - } - } - - - private void showAlert() { - if (swr >= IcomRigConstant.swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - } - - - - @Override - public void onReceiveData(byte[] data) { - int commandEnd = getCommandEnd(data); - if (commandEnd <= -1) {//这是没有指令结尾 - byte[] temp = new byte[dataBuffer.length + data.length]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - System.arraycopy(data, 0, temp, dataBuffer.length, data.length); - dataBuffer = temp; - } else { - byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - dataBuffer = temp; - System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); - } - if (commandEnd != -1) { - analysisCommand(dataBuffer); - } - dataBuffer = new byte[0];//清空缓冲区 - if (commandEnd <= -1 || commandEnd < data.length) { - byte[] temp = new byte[data.length - commandEnd + 1]; - for (int i = 0; i < data.length - commandEnd - 1; i++) { - temp[i] = data[commandEnd + i + 1]; - } - dataBuffer = temp; - } - - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); - } - } - - private void readSWRMeter() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); - } - } - - @Override - public String getName() { - return "XIEGU X6100 series"; - } - - - public String getFrequencyStr() { - return BaseRigOperation.getFrequencyStr(getFreq()); - } - - public XieGu6100Rig(int civAddress) { - Log.d(TAG, "XieGuRig: Create."); - setCivAddress(civAddress); - - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); - //readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,1000); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java deleted file mode 100644 index d1200d4..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java +++ /dev/null @@ -1,240 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -public class XieGuRig extends BaseRig { - private static final String TAG = "IcomRig"; - - private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 - private byte[] dataBuffer = new byte[0];//数据缓冲区 - private int swr = 0; - private boolean swrAlert = false; - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()){ - readSWRMeter(); - }else { - readFreqFromRig(); - } - - } catch (Exception e) { - Log.e(TAG, "readFreq or meter error:" + e.getMessage()); - } - } - }; - } - - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() - , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress - , getCivAddress(), 1));//usb=1 - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress - , getCivAddress(), getFreq())); - } - } - - /** - * 查找指令的结尾的位置,如果没找到,值是-1。 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandEnd(byte[] data) { - for (int i = 0; i < data.length; i++) { - if (data[i] == (byte) 0xFD) { - return i; - } - } - return -1; - } - - /** - * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 - * - * @param data 数据 - * @return 位置 - */ - private int getCommandHead(byte[] data) { - if (data.length < 2) return -1; - for (int i = 0; i < data.length - 1; i++) { - if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { - return i; - } - } - return -1; - } - - private void analysisCommand(byte[] data) { - int headIndex = getCommandHead(data); - if (headIndex == -1) {//说明没有指令头 - return; - } - IcomCommand icomCommand; - if (headIndex == 0) { - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); - } else { - byte[] temp = new byte[data.length - headIndex]; - System.arraycopy(data, headIndex, temp, 0, temp.length); - icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); - } - if (icomCommand == null) { - return; - } - - //目前只对频率和模式消息作反应 - switch (icomCommand.getCommandID()) { - case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 - case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: - //获取频率 - long freqTemp = icomCommand.getFrequency(false); - if (freqTemp >= 500000 && freqTemp <= 250000000) {//协谷的频率范围 - setFreq(freqTemp); - } - break; - case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 - case IcomRigConstant.CMD_READ_OPERATING_MODE: - break; - case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 - if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { - //协谷的小端模式 - int temp=IcomRigConstant.twoByteBcdToIntBigEnd(icomCommand.getData(true)); - if (temp!=255) { - swr = temp;// - } - } - showAlert();//检查meter值是否在告警范围 - - break; - } - } - - - private void showAlert() { - if (swr >= IcomRigConstant.swr_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - } - - - - @Override - public void onReceiveData(byte[] data) { - int commandEnd = getCommandEnd(data); - if (commandEnd <= -1) {//这是没有指令结尾 - byte[] temp = new byte[dataBuffer.length + data.length]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - System.arraycopy(data, 0, temp, dataBuffer.length, data.length); - dataBuffer = temp; - } else { - byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; - System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); - dataBuffer = temp; - System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); - } - if (commandEnd != -1) { - analysisCommand(dataBuffer); - } - dataBuffer = new byte[0];//清空缓冲区 - if (commandEnd <= -1 || commandEnd < data.length) { - byte[] temp = new byte[data.length - commandEnd + 1]; - for (int i = 0; i < data.length - commandEnd - 1; i++) { - temp[i] = data[commandEnd + i + 1]; - } - dataBuffer = temp; - } - - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); - } - } - - private void readSWRMeter() { - if (getConnector() != null) { - getConnector().sendData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); - } - } - - @Override - public String getName() { - return "XIEGU series"; - } - - - public String getFrequencyStr() { - return BaseRigOperation.getFrequencyStr(getFreq()); - } - - public XieGuRig(int civAddress) { - Log.d(TAG, "XieGuRig: Create."); - setCivAddress(civAddress); - - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); - //readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,1000); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java deleted file mode 100644 index d8e18a4..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -public class Yaesu2Command { - private static final String TAG="Yaesu 2 Command"; - - public static long getFrequency(byte[] rawData){ - if (rawData.length==5){ - return ((int) (rawData[0] >> 4) & 0xf) * 100000000 - +(int) (rawData[0] & 0x0f) * 10000000 - +((int) (rawData[1] >> 4) & 0xf) * 1000000 - +(int) (rawData[1] & 0x0f) * 100000 - +((int) (rawData[2] >> 4) & 0xf) * 10000 - +(int) (rawData[2] & 0x0f) * 1000 - +((int) (rawData[3] >> 4) & 0xf) * 10000 - +(int) (rawData[3] & 0x0f) * 1000; - }else { - return -1; - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java deleted file mode 100644 index e54c6c6..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * YAESU的部分电台,回送的数据不是连续的,所以,要做一个缓冲区,接受5字节长度。满了就复位。或发送指令时,就复位。 - */ -public class Yaesu2Rig extends BaseRig{ - private static final String TAG="Yaesu2Rig"; - private Timer readFreqTimer = new Timer(); - - private int swr = 0; - private int alc = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private TimerTask readTask(){ - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()){ - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer=null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - }catch (Exception e) - { - Log.e(TAG, "readFreq error:"+e.getMessage() ); - } - } - }; - } - - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - - if (getConnector()!=null){ - switch (getControlMode()){ - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu2RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - - - - @Override - public boolean isConnected() { - if (getConnector()==null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector()!=null){ - getConnector().sendData(Yaesu2RigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector()!=null){ - getConnector().sendData(Yaesu2RigConstant.setOperationFreq(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - //YAESU 817的指令,返回频率是5字节的,METER是2字节的。 - //Meter是2字节的,第一字节高位功率,0-A,低位ALC 0-9,第二字节高位驻波比,0-C,0为高驻波,低位音频输入0-8 - if (data.length == 5) {//频率 - long freq = Yaesu2Command.getFrequency(data); - if (freq > -1) { - setFreq(freq); - } - } else if (data.length == 2) {//METERS - alc = (data[0] & 0x0f); - swr = (data[1] & 0x0f0) >> 4; - showAlert(); - } - - } - - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - getConnector().sendData(Yaesu2RigConstant.readMeter()); - } - } - private void showAlert() { - if (swr > Yaesu2RigConstant.swr_817_alert_min) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc >= Yaesu2RigConstant.alc_817_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - @Override - public void readFreqFromRig(){ - if (getConnector()!=null){ - //clearBuffer();//清除一下缓冲区 - getConnector().sendData(Yaesu2RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "YAESU 817 series"; - } - - public Yaesu2Rig() { - readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java deleted file mode 100644 index 15de904..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -public class Yaesu2RigConstant { - private static final String TAG = "Yaesu2RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x00; - public static final int USB = 0x01; - public static final int CW = 0x02; - public static final int CW_R = 0x03; - public static final int AM = 0x04; - public static final int FM = 0x05; - public static final int DIG = 0x0A; - public static final int PKT = 0x0C; - public static final int swr_817_alert_min=6;//相当于3.0, - public static final int alc_817_alert_max=7;//取值时0-9,合适值是7 - //PTT状态 - - //指令集 - private static final byte[] PTT_ON = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}; - private static final byte[] PTT_OFF = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88}; - private static final byte[] GET_METER = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xBD}; - //USB模式 - private static final byte[] USB_MODE = {(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x07}; - //DIG模式 - private static final byte[] DIG_MODE = {(byte) 0x0A, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x07}; - private static final byte[] READ_FREQ = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03}; - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case AM: - return "AM"; - case CW: - return "CW"; - case FM: - return "FM"; - case CW_R: - return "CW_R"; - case DIG: - return "DIG"; - case PKT: - return "PKT"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON; - } else { - return PTT_OFF; - } - - } - public static byte[] setOperationUSBMode() { - return DIG_MODE; - } - - public static byte[] readMeter() { - return GET_METER; - } - - public static byte[] setOperationFreq(long freq) { - byte[] data = new byte[]{ - (byte) (((byte) (freq % 1000000000 / 100000000) << 4) + (byte) (freq % 100000000 / 10000000)) - , (byte) (((byte) (freq % 10000000 / 1000000) << 4) + (byte) (freq % 1000000 / 100000)) - , (byte) (((byte) (freq % 100000 / 10000) << 4) + (byte) (freq % 10000 / 1000)) - , (byte) (((byte) (freq % 1000 / 100) << 4) + (byte) (freq % 100)) - ,(byte) 0x01 - }; - return data; - } - public static byte[] setReadOperationFreq(){ - return READ_FREQ; - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java deleted file mode 100644 index 20a4bbf..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -public class Yaesu38Rig extends BaseRig { - private static final String TAG = "Yaesu3Rig"; - private final StringBuilder buffer = new StringBuilder(); - - private int swr = 0; - private int alc = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); - } - } - - private void showAlert() { - if (swr >= Yaesu3RigConstant.swr_39_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - if (!s.contains(";")) { - buffer.append(s); - if (buffer.length() > 1000) clearBufferData(); - return;//说明数据还没接收完。 - } else { - if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0, s.indexOf(";"))); - } - - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";") + 1)); - - if (yaesu3Command == null) { - return; - } - //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - //} - - - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER - if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { - swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - if (Yaesu3Command.isALCMeter38(yaesu3Command)) { - alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - showAlert(); - } - - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "YAESU FT-2000 series"; - } - - public Yaesu38Rig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java deleted file mode 100644 index baa469c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -public class Yaesu38_450Rig extends BaseRig { - private static final String TAG = "Yaesu38_450Rig"; - private final StringBuilder buffer = new StringBuilder(); - private int swr = 0; - private int alc = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); - } - } - - private void showAlert() { - if (swr >= Yaesu3RigConstant.swr_39_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu3RigConstant.setPTT_TX_On(on));//针对YAESU 450指令 - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); - //getConnector().sendData(Yaesu3RigConstant.setOperationUSB_Data_Mode()); - //getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - //ToastMessage.showDebug("39 YAESU 读数据:"+new String(Yaesu3RigConstant.setReadOperationFreq())); - - if (!s.contains(";")) { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - //return;//说明数据还没接收完。 - } else { - if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0, s.indexOf(";"))); - } - - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";") + 1)); - - if (yaesu3Command == null) { - return; - } - //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - //} - - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER - if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { - swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - if (Yaesu3Command.isALCMeter38(yaesu3Command)) { - alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - showAlert(); - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "YAESU FT-450"; - } - - public Yaesu38_450Rig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java deleted file mode 100644 index 0851512..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 - */ -public class Yaesu39Rig extends BaseRig { - private static final String TAG = "Yaesu3Rig"; - private final StringBuilder buffer = new StringBuilder(); - private int swr=0; - private int alc=0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); - } - } - private void showAlert() { - if (swr >= Yaesu3RigConstant.swr_39_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationFreq9Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - - if (!s.contains(";")) { - buffer.append(s); - if (buffer.length() > 1000) clearBufferData(); - // return;//说明数据还没接收完。 - } else { - if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0, s.indexOf(";"))); - } - - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";") + 1)); - - if (yaesu3Command == null) { - return; - } - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - }else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")){//METER - if (Yaesu3Command.isSWRMeter39(yaesu3Command)){ - swr=Yaesu3Command.getSWROrALC39(yaesu3Command); - } - if (Yaesu3Command.isALCMeter39(yaesu3Command)){ - alc=Yaesu3Command.getSWROrALC39(yaesu3Command); - } - showAlert(); - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "YAESU FT-891"; - } - - public Yaesu39Rig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java deleted file mode 100644 index 976b787..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - - -import android.util.Log; - -public class Yaesu3Command { - private static final String TAG = "Yaesu3Command"; - private final String commandID; - private final String data; - - /** - * 获取命令(两字节字符串) - * - * @return 主命令值 - */ - public String getCommandID() {//获取主命令 - return commandID; - } - - /** - * 获取命令数据,字符串,没有分号 - * - * @return 命令数据 - */ - public String getData() {//获取命令数据 - return data; - } - - public Yaesu3Command(String commandID, String data) { - this.commandID = commandID; - this.data = data; - } - //解析接收的指令 - - /** - * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 - * - * @param buffer 从串口接收到的数据 - * @return 返回电台指令对象,如果不符合指令的格式,返回null。 - */ - public static Yaesu3Command getCommand(String buffer) { - if (buffer.length() < 2) {//指令的长度必须大于等于2 - return null; - } - if (buffer.substring(0, 2).matches("[a-zA-Z][a-zA-Z]")) { - return new Yaesu3Command(buffer.substring(0, 2), buffer.substring(2)); - } - return null; - } - - - /** - * 计算频率 - * - * @param command 指令 - * @return 频率 - */ - public static long getFrequency(Yaesu3Command command) { - try { - if (command.getCommandID().equals("FA") || command.getCommandID().equals("FB")) { - return Long.parseLong(command.getData()); - } else { - return 0; - } - } catch (Exception e) { - Log.e(TAG, "获取频率失败: " + command.getData() + "\n" + e.getMessage()); - } - return 0; - } - - - /** - * 获取SWR_YAESU 950 - * - * @param command 指令 - * @return 值 - */ - public static int getALCOrSWR38(Yaesu3Command command) { - if (command.data.length() < 7) return 0; - return Integer.parseInt(command.data.substring(1, 4)); - } - - public static boolean isSWRMeter38(Yaesu3Command command) { - if (command.data.length() < 7) return false; - return (command.data.charAt(0) == '6'); - } - - public static boolean isALCMeter38(Yaesu3Command command) { - if (command.data.length() < 7) return false; - return (command.data.charAt(0) == '4'); - } - - - /** - * 获取SWR_YAESU 891 - * - * @param command 指令 - * @return 值 - */ - public static int getSWROrALC39(Yaesu3Command command) { - if (command.data.length() < 4) return 0; - return Integer.parseInt(command.data.substring(1, 4)); - } - - public static boolean isSWRMeter39(Yaesu3Command command) { - if (command.data.length() < 4) return false; - return (command.data.charAt(0) == '6'); - } - - public static boolean isALCMeter39(Yaesu3Command command) { - if (command.data.length() < 4) return false; - return (command.data.charAt(0) == '4'); - } - - /** - * 获取ts-590的ALC,SWR - * - * @param command 指令 - * @return 值 - */ - public static int get590ALCOrSWR(Yaesu3Command command) { - return Integer.parseInt(command.data.substring(1, 5)); - } - - public static boolean is590MeterALC(Yaesu3Command command){ - if (command.data.length() < 5) return false; - return command.data.charAt(2) == '3'; - } - public static boolean is590MeterSWR(Yaesu3Command command){ - if (command.data.length() < 5) return false; - return command.data.charAt(2) == '1'; - } - - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java deleted file mode 100644 index aef3e00..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; - -public class Yaesu3RigConstant { - private static final String TAG = "Yaesu3RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x01; - public static final int USB = 0x02; - public static final int CW = 0x03; - public static final int FM = 0x04; - public static final int AM = 0x05; - public static final int RTTY = 0x06; - public static final int CW_R = 0x07; - public static final int DATA = 0x08; - public static final int RTTY_R = 0x09; - public static final int NONE = 0x0A; - public static final int FM_N = 0x0B; - public static final int DATA_R = 0x0C; - public static final int AM_N = 0x0D; - - - public static final int swr_39_alert_max=125;//相当于3.0 - public static final int alc_39_alert_max=125;//超过,在表上显示红色 - //PTT状态 - - //指令集 - private static final String PTT_ON = "MX1;"; - private static final String PTT_OFF = "MX0;"; - private static final String USB_MODE = "MD02;"; - private static final String USB_MODE_DATA = "MD09;"; - private static final String DATA_U_MODE = "MD0C;"; - private static final String READ_FREQ = "FA;"; - private static final String READ_39METER_ALC = "RM4;";//38,39的指令都是一样的 - private static final String READ_39METER_SWR = "RM6;";//38,39的指令都是一样的 - - private static final String TX_ON = "TX1;";//用于FT450 ptt - private static final String TX_OFF = "TX0;";//用于FT450 ptt - - - - - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW: - return "CW"; - case FM: - return "FM"; - case AM: - return "AM"; - case RTTY: - return "RTTY"; - case CW_R: - return "CW_R"; - case DATA: - return "DATA"; - case RTTY_R: - return "RTTY_R"; - case NONE: - return "NONE"; - case FM_N: - return "FM_N"; - case DATA_R: - return "DATA_R"; - case AM_N: - return "AM_N"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - //针对YAESU 450的发射指令 - public static byte[] setPTT_TX_On(boolean on) {//用于FT450 - if (on) { - return TX_ON.getBytes(); - } else { - return TX_OFF.getBytes(); - } - - } - public static byte[] setOperationUSBMode() { - return USB_MODE.getBytes(); - } - public static byte[] setOperationUSB_Data_Mode() { - return USB_MODE_DATA.getBytes(); - } - - public static byte[] setOperationDATA_U_Mode() { - return DATA_U_MODE.getBytes(); - } - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 - return String.format("FA%011d;",freq).getBytes(); - } - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq9Byte(long freq) { - return String.format("FA%09d;",freq).getBytes(); - } - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq8Byte(long freq) { - return String.format("FA%08d;",freq).getBytes(); - } - public static byte[] setReadOperationFreq(){ - return READ_FREQ.getBytes(); - } - - public static byte[] setRead39Meters_ALC(){ - return READ_39METER_ALC.getBytes(); - } - public static byte[] setRead39Meters_SWR(){ - return READ_39METER_SWR.getBytes(); - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java deleted file mode 100644 index 1f7244e..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.bg7yoz.ft8cn.rigs; - -import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; -import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; - -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import java.util.Timer; -import java.util.TimerTask; - -/** - * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 - */ -public class YaesuDX10Rig extends BaseRig { - private static final String TAG = "YaesuDX10Rig"; - private final StringBuilder buffer = new StringBuilder(); - private int swr = 0; - private int alc = 0; - private boolean alcMaxAlert = false; - private boolean swrAlert = false; - - private Timer readFreqTimer = new Timer(); - - private TimerTask readTask() { - return new TimerTask() { - @Override - public void run() { - try { - if (!isConnected()) { - readFreqTimer.cancel(); - readFreqTimer.purge(); - readFreqTimer = null; - return; - } - if (isPttOn()) { - readMeters(); - } else { - readFreqFromRig(); - } - } catch (Exception e) { - Log.e(TAG, "readFreq error:" + e.getMessage()); - } - } - }; - } - /** - * 读取Meter RM; - */ - private void readMeters() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); - getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); - } - } - - private void showAlert() { - if (swr >= Yaesu3RigConstant.swr_39_alert_max) { - if (!swrAlert) { - swrAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); - } - } else { - swrAlert = false; - } - if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC - if (!alcMaxAlert) { - alcMaxAlert = true; - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); - } - } else { - alcMaxAlert = false; - } - - } - /** - * 清空缓存数据 - */ - private void clearBufferData() { - buffer.setLength(0); - } - - @Override - public void setPTT(boolean on) { - super.setPTT(on); - if (getConnector() != null) { - switch (getControlMode()) { - case ControlMode.CAT://以CIV指令 - getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); - break; - case ControlMode.RTS: - case ControlMode.DTR: - getConnector().setPttOn(on); - break; - } - } - } - - @Override - public boolean isConnected() { - if (getConnector() == null) { - return false; - } - return getConnector().isConnected(); - } - - @Override - public void setUsbModeToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); - } - } - - @Override - public void setFreqToRig() { - if (getConnector() != null) { - getConnector().sendData(Yaesu3RigConstant.setOperationFreq9Byte(getFreq())); - } - } - - @Override - public void onReceiveData(byte[] data) { - String s = new String(data); - if (!s.contains(";")) - { - buffer.append(s); - if (buffer.length()>1000) clearBufferData(); - return;//说明数据还没接收完。 - }else { - if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; - buffer.append(s.substring(0,s.indexOf(";"))); - } - //开始分析数据 - Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); - clearBufferData();//清一下缓存 - //要把剩下的数据放到缓存里 - buffer.append(s.substring(s.indexOf(";")+1)); - - if (yaesu3Command == null) { - return; - } - //if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - // || yaesu3Command.getCommandID().toLowerCase().equals("FB")) { - // long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); - // if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 - // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - // } - //} - if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") - || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { - long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); - if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 - setFreq(Yaesu3Command.getFrequency(yaesu3Command)); - } - } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER - if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { - swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - if (Yaesu3Command.isALCMeter38(yaesu3Command)) { - alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); - } - showAlert(); - } - - } - - } - - @Override - public void readFreqFromRig() { - if (getConnector() != null) { - clearBufferData();//清空一下缓存 - - getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); - } - } - - @Override - public String getName() { - return "YAESU DX10 series"; - } - - public YaesuDX10Rig() { - readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java deleted file mode 100644 index be22673..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java +++ /dev/null @@ -1,353 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.util.Log; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * USB CDC/ACM serial driver implementation. - * - * @author mike wakerly (opensource@hoho.com) - * @see Universal - * Serial Bus Class Definitions for Communication Devices, v1.1 - */ -public class CdcAcmSerialDriver implements UsbSerialDriver { - - private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); - - private final UsbDevice mDevice; - private final List mPorts; - - public CdcAcmSerialDriver(UsbDevice device) { - mDevice = device; - mPorts = new ArrayList<>(); - - int controlInterfaceCount = 0; - int dataInterfaceCount = 0; - for( int i = 0; i < device.getInterfaceCount(); i++) { - if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) - controlInterfaceCount++; - if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) - dataInterfaceCount++; - } - for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { - mPorts.add(new CdcAcmSerialPort(mDevice, port)); - } - if(mPorts.size() == 0) { - mPorts.add(new CdcAcmSerialPort(mDevice, -1)); - } - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return mPorts; - } - - public class CdcAcmSerialPort extends CommonUsbSerialPort { - - private UsbInterface mControlInterface; - private UsbInterface mDataInterface; - - private UsbEndpoint mControlEndpoint; - - private int mControlIndex; - - private boolean mRts = false; - private boolean mDtr = false; - - private static final int USB_RECIP_INTERFACE = 0x01; - private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; - - private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 - private static final int GET_LINE_CODING = 0x21; - private static final int SET_CONTROL_LINE_STATE = 0x22; - private static final int SEND_BREAK = 0x23; - - public CdcAcmSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return CdcAcmSerialDriver.this; - } - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - if (mPortNumber == -1) { - Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); - openSingleInterface(); - } else { - Log.d(TAG,"trying default interface logic"); - openInterface(); - } - } - - private void openSingleInterface() throws IOException { - // the following code is inspired by the cdc-acm driver in the linux kernel - - mControlIndex = 0; - mControlInterface = mDevice.getInterface(0); - mDataInterface = mDevice.getInterface(0); - if (!mConnection.claimInterface(mControlInterface, true)) { - throw new IOException("Could not claim shared control/data interface"); - } - - for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) { - UsbEndpoint ep = mControlInterface.getEndpoint(i); - if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { - mControlEndpoint = ep; - } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { - mReadEndpoint = ep; - } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { - mWriteEndpoint = ep; - } - } - if (mControlEndpoint == null) { - throw new IOException("No control endpoint"); - } - } - - private void openInterface() throws IOException { - Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); - - int controlInterfaceCount = 0; - int dataInterfaceCount = 0; - mControlInterface = null; - mDataInterface = null; - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - UsbInterface usbInterface = mDevice.getInterface(i); - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { - if(controlInterfaceCount == mPortNumber) { - mControlIndex = i; - mControlInterface = usbInterface; - } - controlInterfaceCount++; - } - if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { - if(dataInterfaceCount == mPortNumber) { - mDataInterface = usbInterface; - } - dataInterfaceCount++; - } - } - - if(mControlInterface == null) { - throw new IOException("No control interface"); - } - Log.d(TAG, "Control iface=" + mControlInterface); - - if (!mConnection.claimInterface(mControlInterface, true)) { - throw new IOException("Could not claim control interface"); - } - - mControlEndpoint = mControlInterface.getEndpoint(0); - if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { - throw new IOException("Invalid control endpoint"); - } - - if(mDataInterface == null) { - throw new IOException("No data interface"); - } - Log.d(TAG, "data iface=" + mDataInterface); - - if (!mConnection.claimInterface(mDataInterface, true)) { - throw new IOException("Could not claim data interface"); - } - - for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { - UsbEndpoint ep = mDataInterface.getEndpoint(i); - if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) - mReadEndpoint = ep; - if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) - mWriteEndpoint = ep; - } - } - - private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { - int len = mConnection.controlTransfer( - USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); - if(len < 0) { - throw new IOException("controlTransfer failed"); - } - return len; - } - - @Override - protected void closeInt() { - try { - mConnection.releaseInterface(mControlInterface); - mConnection.releaseInterface(mDataInterface); - } catch(Exception ignored) {} - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - byte stopBitsByte; - switch (stopBits) { - case STOPBITS_1: stopBitsByte = 0; break; - case STOPBITS_1_5: stopBitsByte = 1; break; - case STOPBITS_2: stopBitsByte = 2; break; - default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - byte parityBitesByte; - switch (parity) { - case PARITY_NONE: parityBitesByte = 0; break; - case PARITY_ODD: parityBitesByte = 1; break; - case PARITY_EVEN: parityBitesByte = 2; break; - case PARITY_MARK: parityBitesByte = 3; break; - case PARITY_SPACE: parityBitesByte = 4; break; - default: throw new IllegalArgumentException("Invalid parity: " + parity); - } - byte[] msg = { - (byte) ( baudRate & 0xff), - (byte) ((baudRate >> 8 ) & 0xff), - (byte) ((baudRate >> 16) & 0xff), - (byte) ((baudRate >> 24) & 0xff), - stopBitsByte, - parityBitesByte, - (byte) dataBits}; - sendAcmControlMessage(SET_LINE_CODING, 0, msg); - } - - @Override - public boolean getDTR() throws IOException { - return mDtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - mDtr = value; - setDtrRts(); - } - - @Override - public boolean getRTS() throws IOException { - return mRts; - } - - @Override - public void setRTS(boolean value) throws IOException { - mRts = value; - setDtrRts(); - } - - private void setDtrRts() throws IOException { - int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); - sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); - } - - @Override - public EnumSet getControlLines() throws IOException { - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(mRts) set.add(ControlLine.RTS); - if(mDtr) set.add(ControlLine.DTR); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.of(ControlLine.RTS, ControlLine.DTR); - } - - @Override - public void setBreak(boolean value) throws IOException { - sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null); - } - - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap<>(); - supportedDevices.put(UsbId.VENDOR_ARDUINO, - new int[] { - UsbId.ARDUINO_UNO, - UsbId.ARDUINO_UNO_R3, - UsbId.ARDUINO_MEGA_2560, - UsbId.ARDUINO_MEGA_2560_R3, - UsbId.ARDUINO_SERIAL_ADAPTER, - UsbId.ARDUINO_SERIAL_ADAPTER_R3, - UsbId.ARDUINO_MEGA_ADK, - UsbId.ARDUINO_MEGA_ADK_R3, - UsbId.ARDUINO_LEONARDO, - UsbId.ARDUINO_MICRO, - }); - supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, - new int[] { - UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, - }); - - - supportedDevices.put(UsbId.VENDOR_ATMEL, - new int[] { - UsbId.ATMEL_LUFA_CDC_DEMO_APP, - }); - supportedDevices.put(UsbId.VENDOR_LEAFLABS, - new int[] { - UsbId.LEAFLABS_MAPLE, - }); - supportedDevices.put(UsbId.VENDOR_ARM, - new int[] { - UsbId.ARM_MBED, - }); - supportedDevices.put(UsbId.VENDOR_ST, - new int[] { - UsbId.ST_CDC, - UsbId.ST_CDC2, - UsbId.ST_CDC3, - UsbId.CDC_WOLF_PID - }); - supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI, - new int[] { - UsbId.RASPBERRY_PI_PICO_MICROPYTHON, - }); - - //国赫1带的USB口 - supportedDevices.put(UsbId.VENDOR_GUOHE1, - new int[] { - UsbId.PID_GUOHE1, - }); - - - supportedDevices.put(UsbId.VENDOR_ICOM, - new int[]{ - UsbId.IC_R30, - UsbId.IC_705, - UsbId.ICOM_USB_SERIAL_CONTROL - }); - supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ - UsbId.XIEGU_X6100,//协谷X6100 - }); - return supportedDevices; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java deleted file mode 100644 index 2029982..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java +++ /dev/null @@ -1,388 +0,0 @@ -/* Copyright 2014 Andreas Butti - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.util.Log; - -import com.bg7yoz.ft8cn.BuildConfig; - -import java.io.IOException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Ch34xSerialDriver implements UsbSerialDriver { - - private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); - - private final UsbDevice mDevice; - private final UsbSerialPort mPort; - - private static final int LCR_ENABLE_RX = 0x80; - private static final int LCR_ENABLE_TX = 0x40; - private static final int LCR_MARK_SPACE = 0x20; - private static final int LCR_PAR_EVEN = 0x10; - private static final int LCR_ENABLE_PAR = 0x08; - private static final int LCR_STOP_BITS_2 = 0x04; - private static final int LCR_CS8 = 0x03; - private static final int LCR_CS7 = 0x02; - private static final int LCR_CS6 = 0x01; - private static final int LCR_CS5 = 0x00; - - private static final int GCL_CTS = 0x01; - private static final int GCL_DSR = 0x02; - private static final int GCL_RI = 0x04; - private static final int GCL_CD = 0x08; - private static final int SCL_DTR = 0x20; - private static final int SCL_RTS = 0x40; - - public Ch34xSerialDriver(UsbDevice device) { - mDevice = device; - mPort = new Ch340SerialPort(mDevice, 0); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return Collections.singletonList(mPort); - } - - public class Ch340SerialPort extends CommonUsbSerialPort { - - private static final int USB_TIMEOUT_MILLIS = 5000; - - private final int DEFAULT_BAUD_RATE = 9600; - - private boolean dtr = false; - private boolean rts = false; - - public Ch340SerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return Ch34xSerialDriver.this; - } - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) { - UsbInterface usbIface = mDevice.getInterface(i); - if (!mConnection.claimInterface(usbIface, true)) { - throw new IOException("Could not claim data interface"); - } - } - - UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); - for (int i = 0; i < dataIface.getEndpointCount(); i++) { - UsbEndpoint ep = dataIface.getEndpoint(i); - if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { - if (ep.getDirection() == UsbConstants.USB_DIR_IN) { - mReadEndpoint = ep; - } else { - mWriteEndpoint = ep; - } - } - } - - initialize(); - setBaudRate(DEFAULT_BAUD_RATE); - } - - @Override - protected void closeInt() { - try { - for (int i = 0; i < mDevice.getInterfaceCount(); i++) - mConnection.releaseInterface(mDevice.getInterface(i)); - } catch(Exception ignored) {} - } - - private int controlOut(int request, int value, int index) { - final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; - return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, - value, index, null, 0, USB_TIMEOUT_MILLIS); - } - - - private int controlIn(int request, int value, int index, byte[] buffer) { - final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; - return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, - value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); - } - - - private void checkState(String msg, int request, int value, int[] expected) throws IOException { - byte[] buffer = new byte[expected.length]; - int ret = controlIn(request, value, 0, buffer); - - if (ret < 0) { - throw new IOException("Failed send cmd [" + msg + "]"); - } - - if (ret != expected.length) { - throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); - } - - for (int i = 0; i < expected.length; i++) { - if (expected[i] == -1) { - continue; - } - - int current = buffer[i] & 0xff; - if (expected[i] != current) { - throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); - } - } - } - - private void setControlLines() throws IOException { - if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { - throw new IOException("Failed to set control lines"); - } - } - - private byte getStatus() throws IOException { - byte[] buffer = new byte[2]; - int ret = controlIn(0x95, 0x0706, 0, buffer); - if (ret < 0) - throw new IOException("Error getting control lines"); - return buffer[0]; - } - - private void initialize() throws IOException { - checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); - - if (controlOut(0xa1, 0, 0) < 0) { - throw new IOException("Init failed: #2"); - } - - setBaudRate(DEFAULT_BAUD_RATE); - - checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); - - if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { - throw new IOException("Init failed: #5"); - } - - checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); - - if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { - throw new IOException("Init failed: #7"); - } - - setBaudRate(DEFAULT_BAUD_RATE); - - setControlLines(); - - checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); - } - - - private void setBaudRate(int baudRate) throws IOException { - long factor; - long divisor; - - if (baudRate == 921600) { - divisor = 7; - factor = 0xf300; - } else { - final long BAUDBASE_FACTOR = 1532620800; - final int BAUDBASE_DIVMAX = 3; - - if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) - baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling - factor = BAUDBASE_FACTOR / baudRate; - divisor = BAUDBASE_DIVMAX; - while ((factor > 0xfff0) && divisor > 0) { - factor >>= 3; - divisor--; - } - if (factor > 0xfff0) { - throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); - } - factor = 0x10000 - factor; - } - - divisor |= 0x0080; // else ch341a waits until buffer full - int val1 = (int) ((factor & 0xff00) | divisor); - int val2 = (int) (factor & 0xff); - Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2)); - int ret = controlOut(0x9a, 0x1312, val1); - if (ret < 0) { - throw new IOException("Error setting baud rate: #1)"); - } - ret = controlOut(0x9a, 0x0f2c, val2); - if (ret < 0) { - throw new IOException("Error setting baud rate: #2"); - } - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudRate(baudRate); - - int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; - - switch (dataBits) { - case DATABITS_5: - lcr |= LCR_CS5; - break; - case DATABITS_6: - lcr |= LCR_CS6; - break; - case DATABITS_7: - lcr |= LCR_CS7; - break; - case DATABITS_8: - lcr |= LCR_CS8; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - lcr |= LCR_ENABLE_PAR; - break; - case PARITY_EVEN: - lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; - break; - case PARITY_MARK: - lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; - break; - case PARITY_SPACE: - lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - lcr |= LCR_STOP_BITS_2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - int ret = controlOut(0x9a, 0x2518, lcr); - if (ret < 0) { - throw new IOException("Error setting control byte"); - } - } - - @Override - public boolean getCD() throws IOException { - return (getStatus() & GCL_CD) == 0; - } - - @Override - public boolean getCTS() throws IOException { - return (getStatus() & GCL_CTS) == 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & GCL_DSR) == 0; - } - - @Override - public boolean getDTR() throws IOException { - return dtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - dtr = value; - setControlLines(); - } - - @Override - public boolean getRI() throws IOException { - return (getStatus() & GCL_RI) == 0; - } - - @Override - public boolean getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - rts = value; - setControlLines(); - } - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); - if((status & GCL_CD) == 0) set.add(ControlLine.CD); - if((status & GCL_RI) == 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void setBreak(boolean value) throws IOException { - byte[] req = new byte[2]; - if(controlIn(0x95, 0x1805, 0, req) < 0) { - throw new IOException("Error getting BREAK condition"); - } - if(value) { - req[0] &= ~1; - req[1] &= ~0x40; - } else { - req[0] |= 1; - req[1] |= 0x40; - } - int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); - if(controlOut(0x9a, 0x1805, val) < 0) { - throw new IOException("Error setting BREAK condition"); - } - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap<>(); - supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ - UsbId.QINHENG_CH340, - UsbId.QINHENG_CH341A, - UsbId.QINHENG_CH341K - }); - return supportedDevices; - } - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java deleted file mode 100644 index 73f36d6..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java +++ /dev/null @@ -1,318 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbRequest; -import android.util.Log; - -import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.EnumSet; - -/** - * A base class shared by several driver implementations. - * - * @author mike wakerly (opensource@hoho.com) - */ -public abstract class CommonUsbSerialPort implements UsbSerialPort { - - public static boolean DEBUG = false; - - private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); - private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit - - protected final UsbDevice mDevice; - protected final int mPortNumber; - - // non-null when open() - protected UsbDeviceConnection mConnection = null; - protected UsbEndpoint mReadEndpoint; - protected UsbEndpoint mWriteEndpoint; - protected UsbRequest mUsbRequest; - - /** - * Internal write buffer. - * Guarded by {@link #mWriteBufferLock}. - * Default length = mReadEndpoint.getMaxPacketSize() - **/ - protected byte[] mWriteBuffer; - protected final Object mWriteBufferLock = new Object(); - - - public CommonUsbSerialPort(UsbDevice device, int portNumber) { - mDevice = device; - mPortNumber = portNumber; - } - - @Override - public String toString() { - return String.format("<%s device_name=%s device_id=%s port_number=%s>", - getClass().getSimpleName(), mDevice.getDeviceName(), - mDevice.getDeviceId(), mPortNumber); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public int getPortNumber() { - return mPortNumber; - } - - @Override - public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; } - - @Override - public UsbEndpoint getReadEndpoint() { return mReadEndpoint; } - - /** - * Returns the device serial number - * @return serial number - */ - @Override - public String getSerial() { - return mConnection.getSerial(); - } - - /** - * Sets the size of the internal buffer used to exchange data with the USB - * stack for write operations. Most users should not need to change this. - * - * @param bufferSize the size in bytes, <= 0 resets original size - */ - public final void setWriteBufferSize(int bufferSize) { - synchronized (mWriteBufferLock) { - if (bufferSize <= 0) { - if (mWriteEndpoint != null) { - bufferSize = mWriteEndpoint.getMaxPacketSize(); - } else { - mWriteBuffer = null; - return; - } - } - if (mWriteBuffer != null && bufferSize == mWriteBuffer.length) { - return; - } - mWriteBuffer = new byte[bufferSize]; - } - } - - @Override - public void open(UsbDeviceConnection connection) throws IOException { - if (mConnection != null) { - throw new IOException("Already open"); - } - if(connection == null) { - throw new IllegalArgumentException("Connection is null"); - } - mConnection = connection; - try { - openInt(connection); - if (mReadEndpoint == null || mWriteEndpoint == null) { - throw new IOException("Could not get read & write endpoints"); - } - mUsbRequest = new UsbRequest(); - mUsbRequest.initialize(mConnection, mReadEndpoint); - } catch(Exception e) { - try { - close(); - } catch(Exception ignored) {} - throw e; - } - } - - protected abstract void openInt(UsbDeviceConnection connection) throws IOException; - - @Override - public void close() throws IOException { - if (mConnection == null) { - throw new IOException("Already closed"); - } - try { - mUsbRequest.cancel(); - } catch(Exception ignored) {} - mUsbRequest = null; - try { - closeInt(); - } catch(Exception ignored) {} - try { - mConnection.close(); - } catch(Exception ignored) {} - mConnection = null; - } - - protected abstract void closeInt(); - - /** - * use simple USB request supported by all devices to test if connection is still valid - */ - protected void testConnection() throws IOException { - byte[] buf = new byte[2]; - int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); - if(len < 0) - throw new IOException("USB get_status request failed"); - } - - @Override - public int read(final byte[] dest, final int timeout) throws IOException { - return read(dest, timeout, true); - } - - protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { - if(mConnection == null) { - throw new IOException("Connection closed"); - } - if(dest.length <= 0) { - throw new IllegalArgumentException("Read buffer to small"); - } - final int nread; - if (timeout != 0) { - // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer - // https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data - // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, - // as it crashes with short timeout, e.g. - // A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test) - // /system/lib64/libusbhost.so (usb_request_wait+192) - // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) - // data loss / crashes were observed with timeout up to 200 msec - long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; - int readMax = Math.min(dest.length, MAX_READ_SIZE); - nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); - // Android error propagation is improvable: - // nread == -1 can be: timeout, connection lost, buffer to small, ??? - if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) - testConnection(); - - } else { - final ByteBuffer buf = ByteBuffer.wrap(dest); - if (!mUsbRequest.queue(buf, dest.length)) { - throw new IOException("Queueing USB request failed"); - } - final UsbRequest response = mConnection.requestWait(); - if (response == null) { - throw new IOException("Waiting for USB request failed"); - } - nread = buf.position(); - // Android error propagation is improvable: - // response != null & nread == 0 can be: connection lost, buffer to small, ??? - if(nread == 0) { - testConnection(); - } - } - return Math.max(nread, 0); - } - - @Override - public void write(final byte[] src, final int timeout) throws IOException { - int offset = 0; - final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); - - if(mConnection == null) { - throw new IOException("Connection closed"); - } - while (offset < src.length) { - int requestTimeout; - final int requestLength; - final int actualLength; - - synchronized (mWriteBufferLock) { - final byte[] writeBuffer; - - if (mWriteBuffer == null) { - mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; - } - requestLength = Math.min(src.length - offset, mWriteBuffer.length); - if (offset == 0) { - writeBuffer = src; - } else { - // bulkTransfer does not support offsets, make a copy. - System.arraycopy(src, offset, mWriteBuffer, 0, requestLength); - writeBuffer = mWriteBuffer; - } - if (timeout == 0 || offset == 0) { - requestTimeout = timeout; - } else { - requestTimeout = (int)(endTime - MonotonicClock.millis()); - if(requestTimeout == 0) - requestTimeout = -1; - } - if (requestTimeout < 0) { - actualLength = -2; - } else { - actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); - } - } - if (DEBUG) { - Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); - } - if (actualLength <= 0) { - if (timeout != 0 && MonotonicClock.millis() >= endTime) { - SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); - ex.bytesTransferred = offset; - throw ex; - } else { - throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); - } - } - offset += actualLength; - } - } - - @Override - public boolean isOpen() { - return mConnection != null; - } - - @Override - public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; - - @Override - public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getRI() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); } - - @Override - public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } - - @Override - public abstract EnumSet getControlLines() throws IOException; - - @Override - public abstract EnumSet getSupportedControlLines() throws IOException; - - @Override - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java deleted file mode 100644 index 0d8009b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java +++ /dev/null @@ -1,335 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Cp21xxSerialDriver implements UsbSerialDriver { - - private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); - - private final UsbDevice mDevice; - private final List mPorts; - - public Cp21xxSerialDriver(UsbDevice device) { - mDevice = device; - mPorts = new ArrayList<>(); - for( int port = 0; port < device.getInterfaceCount(); port++) { - mPorts.add(new Cp21xxSerialPort(mDevice, port)); - } - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return mPorts; - } - - public class Cp21xxSerialPort extends CommonUsbSerialPort { - - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - - /* - * Configuration Request Types - */ - private static final int REQTYPE_HOST_TO_DEVICE = 0x41; - private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; - - /* - * Configuration Request Codes - */ - private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; - private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; - private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; - private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; - private static final int SILABSER_SET_BAUDRATE = 0x1E; - private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; - private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; - - private static final int FLUSH_READ_CODE = 0x0a; - private static final int FLUSH_WRITE_CODE = 0x05; - - /* - * SILABSER_IFC_ENABLE_REQUEST_CODE - */ - private static final int UART_ENABLE = 0x0001; - private static final int UART_DISABLE = 0x0000; - - /* - * SILABSER_SET_MHS_REQUEST_CODE - */ - private static final int DTR_ENABLE = 0x101; - private static final int DTR_DISABLE = 0x100; - private static final int RTS_ENABLE = 0x202; - private static final int RTS_DISABLE = 0x200; - - /* - * SILABSER_GET_MDMSTS_REQUEST_CODE - */ - private static final int STATUS_CTS = 0x10; - private static final int STATUS_DSR = 0x20; - private static final int STATUS_RI = 0x40; - private static final int STATUS_CD = 0x80; - - - private boolean dtr = false; - private boolean rts = false; - - // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity - // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored - private boolean mIsRestrictedPort; - - public Cp21xxSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return Cp21xxSerialDriver.this; - } - - private void setConfigSingle(int request, int value) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, - mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); - } - } - - private byte getStatus() throws IOException { - byte[] buffer = new byte[1]; - int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, - mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 1) { - throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); - } - return buffer[0]; - } - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; - if(mPortNumber >= mDevice.getInterfaceCount()) { - throw new IOException("Unknown port number"); - } - UsbInterface dataIface = mDevice.getInterface(mPortNumber); - if (!mConnection.claimInterface(dataIface, true)) { - throw new IOException("Could not claim interface " + mPortNumber); - } - for (int i = 0; i < dataIface.getEndpointCount(); i++) { - UsbEndpoint ep = dataIface.getEndpoint(i); - if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { - if (ep.getDirection() == UsbConstants.USB_DIR_IN) { - mReadEndpoint = ep; - } else { - mWriteEndpoint = ep; - } - } - } - - setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); - } - - @Override - protected void closeInt() { - try { - setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); - } catch (Exception ignored) {} - try { - mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); - } catch(Exception ignored) {} - } - - private void setBaudRate(int baudRate) throws IOException { - byte[] data = new byte[] { - (byte) ( baudRate & 0xff), - (byte) ((baudRate >> 8 ) & 0xff), - (byte) ((baudRate >> 16) & 0xff), - (byte) ((baudRate >> 24) & 0xff) - }; - int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, - 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); - if (ret < 0) { - throw new IOException("Error setting baud rate"); - } - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudRate(baudRate); - - int configDataBits = 0; - switch (dataBits) { - case DATABITS_5: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0500; - break; - case DATABITS_6: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0600; - break; - case DATABITS_7: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - configDataBits |= 0x0700; - break; - case DATABITS_8: - configDataBits |= 0x0800; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - configDataBits |= 0x0010; - break; - case PARITY_EVEN: - configDataBits |= 0x0020; - break; - case PARITY_MARK: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported parity: mark"); - configDataBits |= 0x0030; - break; - case PARITY_SPACE: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported parity: space"); - configDataBits |= 0x0040; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - if(mIsRestrictedPort) - throw new UnsupportedOperationException("Unsupported stop bits: 2"); - configDataBits |= 2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); - } - - @Override - public boolean getCD() throws IOException { - return (getStatus() & STATUS_CD) != 0; - } - - @Override - public boolean getCTS() throws IOException { - return (getStatus() & STATUS_CTS) != 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & STATUS_DSR) != 0; - } - - @Override - public boolean getDTR() throws IOException { - return dtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - dtr = value; - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); - } - - @Override - public boolean getRI() throws IOException { - return (getStatus() & STATUS_RI) != 0; - } - - @Override - public boolean getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - rts = value; - setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); - } - - @Override - public EnumSet getControlLines() throws IOException { - byte status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); - if((status & STATUS_CD) != 0) set.add(ControlLine.CD); - if((status & STATUS_RI) != 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - // note: only working on some devices, on other devices ignored w/o error - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) - | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); - - if (value != 0) { - setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); - } - } - - @Override - public void setBreak(boolean value) throws IOException { - setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap<>(); - supportedDevices.put(UsbId.VENDOR_SILABS, - new int[] { - UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 - UsbId.SILABS_CP2105, - UsbId.SILABS_CP2108, - }); - return supportedDevices; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java deleted file mode 100644 index 6fc4479..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java +++ /dev/null @@ -1,432 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * Copyright 2020 kai morich - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.util.Log; - -import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/* - * driver is implemented from various information scattered over FTDI documentation - * - * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf - * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf - * device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice - * - */ - -public class FtdiSerialDriver implements UsbSerialDriver { - - private static final String TAG = FtdiSerialPort.class.getSimpleName(); - - private final UsbDevice mDevice; - private final List mPorts; - - public FtdiSerialDriver(UsbDevice device) { - mDevice = device; - mPorts = new ArrayList<>(); - for( int port = 0; port < device.getInterfaceCount(); port++) { - mPorts.add(new FtdiSerialPort(mDevice, port)); - } - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - @Override - public List getPorts() { - return mPorts; - } - - public class FtdiSerialPort extends CommonUsbSerialPort { - - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS - - private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; - private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; - - private static final int RESET_REQUEST = 0; - private static final int MODEM_CONTROL_REQUEST = 1; - private static final int SET_BAUD_RATE_REQUEST = 3; - private static final int SET_DATA_REQUEST = 4; - private static final int GET_MODEM_STATUS_REQUEST = 5; - private static final int SET_LATENCY_TIMER_REQUEST = 9; - private static final int GET_LATENCY_TIMER_REQUEST = 10; - - private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; - private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; - private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; - private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; - private static final int MODEM_STATUS_CTS = 0x10; - private static final int MODEM_STATUS_DSR = 0x20; - private static final int MODEM_STATUS_RI = 0x40; - private static final int MODEM_STATUS_CD = 0x80; - private static final int RESET_ALL = 0; - private static final int RESET_PURGE_RX = 1; - private static final int RESET_PURGE_TX = 2; - - private boolean baudRateWithPort = false; - private boolean dtr = false; - private boolean rts = false; - private int breakConfig = 0; - - public FtdiSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return FtdiSerialDriver.this; - } - - - @Override - protected void openInt(UsbDeviceConnection connection) throws IOException { - if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { - throw new IOException("Could not claim interface " + mPortNumber); - } - if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { - throw new IOException("Not enough endpoints"); - } - mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); - mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); - - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, - RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Reset failed: result=" + result); - } - result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, - (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | - (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), - mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Init RTS,DTR failed: result=" + result); - } - - // mDevice.getVersion() would require API 23 - byte[] rawDescriptors = connection.getRawDescriptors(); - if(rawDescriptors == null || rawDescriptors.length < 14) { - throw new IOException("Could not get device descriptors"); - } - int deviceType = rawDescriptors[13]; - baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices - || mDevice.getInterfaceCount() > 1; // FT2232C - } - - @Override - protected void closeInt() { - try { - mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); - } catch(Exception ignored) {} - } - - @Override - public int read(final byte[] dest, final int timeout) throws IOException { - if(dest.length <= READ_HEADER_LENGTH) { - throw new IllegalArgumentException("Read buffer to small"); - // could allocate larger buffer, including space for 2 header bytes, but this would - // result in buffers not being 64 byte aligned any more, causing data loss at continuous - // data transfer at high baud rates when buffers are fully filled. - } - int nread; - if (timeout != 0) { - long endTime = MonotonicClock.millis() + timeout; - do { - nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); - } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); - if(nread <= 0 && MonotonicClock.millis() < endTime) - testConnection(); - } else { - do { - nread = super.read(dest, timeout, false); - } while (nread == READ_HEADER_LENGTH); - } - return readFilter(dest, nread); - } - - protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException { - final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); - int destPos = 0; - for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { - int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); - if (length < 0) - throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); - System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); - destPos += length; - } - //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); - return destPos; - } - - private void setBaudrate(int baudRate) throws IOException { - int divisor, subdivisor, effectiveBaudRate; - if (baudRate > 3500000) { - throw new UnsupportedOperationException("Baud rate to high"); - } else if(baudRate >= 2500000) { - divisor = 0; - subdivisor = 0; - effectiveBaudRate = 3000000; - } else if(baudRate >= 1750000) { - divisor = 1; - subdivisor = 0; - effectiveBaudRate = 2000000; - } else { - divisor = (24000000 << 1) / baudRate; - divisor = (divisor + 1) >> 1; // round - subdivisor = divisor & 0x07; - divisor >>= 3; - if (divisor > 0x3fff) // exceeds bit 13 at 183 baud - throw new UnsupportedOperationException("Baud rate to low"); - effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); - effectiveBaudRate = (effectiveBaudRate +1) >> 1; - } - double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); - if(baudRateError >= 0.031) // can happen only > 1.5Mbaud - throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); - int value = divisor; - int index = 0; - switch(subdivisor) { - case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 - case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 - case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 - case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 - case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 - case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625 - case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75 - case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 - } - if(baudRateWithPort) { - index <<= 8; - index |= mPortNumber+1; - } - Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", - baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); - - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, - value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Setting baudrate failed: result=" + result); - } - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { - if(baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - setBaudrate(baudRate); - - int config = 0; - switch (dataBits) { - case DATABITS_5: - case DATABITS_6: - throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); - case DATABITS_7: - case DATABITS_8: - config |= dataBits; - break; - default: - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - - switch (parity) { - case PARITY_NONE: - break; - case PARITY_ODD: - config |= 0x100; - break; - case PARITY_EVEN: - config |= 0x200; - break; - case PARITY_MARK: - config |= 0x300; - break; - case PARITY_SPACE: - config |= 0x400; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - switch (stopBits) { - case STOPBITS_1: - break; - case STOPBITS_1_5: - throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); - case STOPBITS_2: - config |= 0x1000; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, - config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Setting parameters failed: result=" + result); - } - breakConfig = config; - } - - private int getStatus() throws IOException { - byte[] data = new byte[2]; - int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, - 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 2) { - throw new IOException("Get modem status failed: result=" + result); - } - return data[0]; - } - - @Override - public boolean getCD() throws IOException { - return (getStatus() & MODEM_STATUS_CD) != 0; - } - - @Override - public boolean getCTS() throws IOException { - return (getStatus() & MODEM_STATUS_CTS) != 0; - } - - @Override - public boolean getDSR() throws IOException { - return (getStatus() & MODEM_STATUS_DSR) != 0; - } - - @Override - public boolean getDTR() throws IOException { - return dtr; - } - - @Override - public void setDTR(boolean value) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, - value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Set DTR failed: result=" + result); - } - dtr = value; - } - - @Override - public boolean getRI() throws IOException { - return (getStatus() & MODEM_STATUS_RI) != 0; - } - - @Override - public boolean getRTS() throws IOException { - return rts; - } - - @Override - public void setRTS(boolean value) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, - value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Set DTR failed: result=" + result); - } - rts = value; - } - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if(rts) set.add(ControlLine.RTS); - if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); - if(dtr) set.add(ControlLine.DTR); - if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); - if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD); - if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - if (purgeWriteBuffers) { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, - RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Purge write buffer failed: result=" + result); - } - } - - if (purgeReadBuffers) { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, - RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Purge read buffer failed: result=" + result); - } - } - } - - @Override - public void setBreak(boolean value) throws IOException { - int config = breakConfig; - if(value) config |= 0x4000; - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, - config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Setting BREAK failed: result=" + result); - } - } - - public void setLatencyTimer(int latencyTime) throws IOException { - int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST, - latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); - if (result != 0) { - throw new IOException("Set latency timer failed: result=" + result); - } - } - - public int getLatencyTimer() throws IOException { - byte[] data = new byte[1]; - int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, - 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); - if (result != 1) { - throw new IOException("Get latency timer failed: result=" + result); - } - return data[0]; - } - - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap<>(); - supportedDevices.put(UsbId.VENDOR_FTDI, - new int[] { - UsbId.FTDI_FT232R, - UsbId.FTDI_ZERO, - UsbId.FTDI_FT232H, - UsbId.FTDI_FT2232H, - UsbId.FTDI_FT4232H, - UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD - }); - return supportedDevices; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java deleted file mode 100644 index 4174729..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.util.Pair; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Maps (vendor id, product id) pairs to the corresponding serial driver. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class ProbeTable { - - private final Map, Class> mProbeTable = - new LinkedHashMap<>(); - - /** - * Adds or updates a (vendor, product) pair in the table. - * - * @param vendorId the USB vendor id - * @param productId the USB product id - * @param driverClass the driver class responsible for this pair - * @return {@code this}, for chaining - */ - public ProbeTable addProduct(int vendorId, int productId, - Class driverClass) { - mProbeTable.put(Pair.create(vendorId, productId), driverClass); - return this; - } - - /** - * Internal method to add all supported products from - * {@code getSupportedProducts} static method. - * - * @param driverClass - * @return - */ - @SuppressWarnings("unchecked") - ProbeTable addDriver(Class driverClass) { - final Method method; - - try { - method = driverClass.getMethod("getSupportedDevices"); - } catch (SecurityException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - - final Map devices; - try { - devices = (Map) method.invoke(null); - } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - - for (Map.Entry entry : devices.entrySet()) { - final int vendorId = entry.getKey(); - for (int productId : entry.getValue()) { - addProduct(vendorId, productId, driverClass); - } - } - - return this; - } - - /** - * Returns the driver for the given (vendor, product) pair, or {@code null} - * if no match. - * - * @param vendorId the USB vendor id - * @param productId the USB product id - * @return the driver class matching this pair, or {@code null} - */ - public Class findDriver(int vendorId, int productId) { - final Pair pair = Pair.create(vendorId, productId); - return mProbeTable.get(pair); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java deleted file mode 100644 index 531fc0c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Ported to usb-serial-for-android by Felix Hädicke - * - * Based on the pyprolific driver written by Emmanuel Blot - * See https://github.com/eblot/pyftdi - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.util.Log; - -import com.bg7yoz.ft8cn.BuildConfig; -import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; - -import java.io.IOException; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class ProlificSerialDriver implements UsbSerialDriver { - - private final String TAG = ProlificSerialDriver.class.getSimpleName(); - - private final static int[] standardBaudRates = { - 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, - 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, - 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 - }; - protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN } - - private final UsbDevice mDevice; - private final UsbSerialPort mPort; - - public ProlificSerialDriver(UsbDevice device) { - mDevice = device; - mPort = new ProlificSerialPort(mDevice, 0); - } - - @Override - public List getPorts() { - return Collections.singletonList(mPort); - } - - @Override - public UsbDevice getDevice() { - return mDevice; - } - - class ProlificSerialPort extends CommonUsbSerialPort { - - private static final int USB_READ_TIMEOUT_MILLIS = 1000; - private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; - - private static final int USB_RECIP_INTERFACE = 0x01; - - private static final int VENDOR_READ_REQUEST = 0x01; - private static final int VENDOR_WRITE_REQUEST = 0x01; - private static final int VENDOR_READ_HXN_REQUEST = 0x81; - private static final int VENDOR_WRITE_HXN_REQUEST = 0x80; - - private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; - private static final int VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR; - private static final int CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; - - private static final int WRITE_ENDPOINT = 0x02; - private static final int READ_ENDPOINT = 0x83; - private static final int INTERRUPT_ENDPOINT = 0x81; - - private static final int RESET_HXN_REQUEST = 0x07; - private static final int FLUSH_RX_REQUEST = 0x08; - private static final int FLUSH_TX_REQUEST = 0x09; - private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING - private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE - private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK - private static final int GET_CONTROL_HXN_REQUEST = 0x80; - private static final int GET_CONTROL_REQUEST = 0x87; - private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length - - /* RESET_HXN_REQUEST */ - private static final int RESET_HXN_RX_PIPE = 1; - private static final int RESET_HXN_TX_PIPE = 2; - - /* SET_CONTROL_REQUEST */ - private static final int CONTROL_DTR = 0x01; - private static final int CONTROL_RTS = 0x02; - - /* GET_CONTROL_REQUEST */ - private static final int GET_CONTROL_FLAG_CD = 0x02; - private static final int GET_CONTROL_FLAG_DSR = 0x04; - private static final int GET_CONTROL_FLAG_RI = 0x01; - private static final int GET_CONTROL_FLAG_CTS = 0x08; - - /* GET_CONTROL_HXN_REQUEST */ - private static final int GET_CONTROL_HXN_FLAG_CD = 0x40; - private static final int GET_CONTROL_HXN_FLAG_DSR = 0x20; - private static final int GET_CONTROL_HXN_FLAG_RI = 0x80; - private static final int GET_CONTROL_HXN_FLAG_CTS = 0x08; - - /* interrupt endpoint read */ - private static final int STATUS_FLAG_CD = 0x01; - private static final int STATUS_FLAG_DSR = 0x02; - private static final int STATUS_FLAG_RI = 0x08; - private static final int STATUS_FLAG_CTS = 0x80; - - private static final int STATUS_BUFFER_SIZE = 10; - private static final int STATUS_BYTE_IDX = 8; - - protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; - private UsbEndpoint mInterruptEndpoint; - private int mControlLinesValue = 0; - private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; - - private int mStatus = 0; - private volatile Thread mReadStatusThread = null; - private final Object mReadStatusThreadLock = new Object(); - private boolean mStopReadStatusThread = false; - private IOException mReadStatusException = null; - - - public ProlificSerialPort(UsbDevice device, int portNumber) { - super(device, portNumber); - } - - @Override - public UsbSerialDriver getDriver() { - return ProlificSerialDriver.this; - } - - private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException { - byte[] buffer = new byte[length]; - int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); - if (result != length) { - throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result)); - } - return buffer; - } - - private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException { - int length = (data == null) ? 0 : data.length; - int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); - if (result != length) { - throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result)); - } - } - - private byte[] vendorIn(int value, int index, int length) throws IOException { - int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST; - return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length); - } - - private void vendorOut(int value, int index, byte[] data) throws IOException { - int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST; - outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data); - } - - private void resetDevice() throws IOException { - purgeHwBuffers(true, true); - } - - private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { - outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data); - } - - private boolean testHxStatus() { - try { - inControlTransfer(VENDOR_IN_REQTYPE, VENDOR_READ_REQUEST, 0x8080, 0, 1); - return true; - } catch(IOException ignored) { - return false; - } - } - - private void doBlackMagic() throws IOException { - if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) - return; - vendorIn(0x8484, 0, 1); - vendorOut(0x0404, 0, null); - vendorIn(0x8484, 0, 1); - vendorIn(0x8383, 0, 1); - vendorIn(0x8484, 0, 1); - vendorOut(0x0404, 1, null); - vendorIn(0x8484, 0, 1); - vendorIn(0x8383, 0, 1); - vendorOut(0, 1, null); - vendorOut(1, 0, null); - vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null); - } - - private void setControlLines(int newControlLinesValue) throws IOException { - ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); - mControlLinesValue = newControlLinesValue; - } - - private void readStatusThreadFunction() { - try { - while (!mStopReadStatusThread) { - byte[] buffer = new byte[STATUS_BUFFER_SIZE]; - long endTime = MonotonicClock.millis() + 500; - int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); - if(readBytesCount == -1 && MonotonicClock.millis() < endTime) - testConnection(); - if (readBytesCount > 0) { - if (readBytesCount != STATUS_BUFFER_SIZE) { - throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); - } else if(buffer[0] != (byte)STATUS_NOTIFICATION ) { - throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]); - } else { - mStatus = buffer[STATUS_BYTE_IDX] & 0xff; - } - } - } - } catch (IOException e) { - mReadStatusException = e; - } - //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); - } - - private int getStatus() throws IOException { - if ((mReadStatusThread == null) && (mReadStatusException == null)) { - synchronized (mReadStatusThreadLock) { - if (mReadStatusThread == null) { - mStatus = 0; - if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) { - byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1); - if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; - if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; - if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; - if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; - } else { - byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1); - if ((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; - if ((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; - if ((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; - if ((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; - } - //Log.d(TAG, "start control line status thread " + mStatus); - mReadStatusThread = new Thread(this::readStatusThreadFunction); - mReadStatusThread.setDaemon(true); - mReadStatusThread.start(); - } - } - } - - /* throw and clear an exception which occured in the status read thread */ - IOException readStatusException = mReadStatusException; - if (mReadStatusException != null) { - mReadStatusException = null; - throw new IOException(readStatusException); - } - - return mStatus; - } - - private boolean testStatusFlag(int flag) throws IOException { - return ((getStatus() & flag) == flag); - } - - @Override - public void openInt(UsbDeviceConnection connection) throws IOException { - UsbInterface usbInterface = mDevice.getInterface(0); - - if (!connection.claimInterface(usbInterface, true)) { - throw new IOException("Error claiming Prolific interface 0"); - } - - for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { - UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); - - switch (currentEndpoint.getAddress()) { - case READ_ENDPOINT: - mReadEndpoint = currentEndpoint; - break; - - case WRITE_ENDPOINT: - mWriteEndpoint = currentEndpoint; - break; - - case INTERRUPT_ENDPOINT: - mInterruptEndpoint = currentEndpoint; - break; - } - } - - byte[] rawDescriptors = connection.getRawDescriptors(); - if(rawDescriptors == null || rawDescriptors.length < 14) { - throw new IOException("Could not get device descriptors"); - } - int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2]; - int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12]; - byte maxPacketSize0 = rawDescriptors[7]; - if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) { - mDeviceType = DeviceType.DEVICE_TYPE_01; - } else if(deviceVersion == 0x300 && usbVersion == 0x200) { - mDeviceType = DeviceType.DEVICE_TYPE_T; // TA - } else if(deviceVersion == 0x500) { - mDeviceType = DeviceType.DEVICE_TYPE_T; // TB - } else if(usbVersion == 0x200 && !testHxStatus()) { - mDeviceType = DeviceType.DEVICE_TYPE_HXN; - } else { - mDeviceType = DeviceType.DEVICE_TYPE_HX; - } - Log.d(TAG, String.format("usbVersion=%x, deviceVersion=%x, deviceClass=%d, packetSize=%d => deviceType=%s", - usbVersion, deviceVersion, mDevice.getDeviceClass(), maxPacketSize0, mDeviceType.name())); - resetDevice(); - doBlackMagic(); - setControlLines(mControlLinesValue); - } - - @Override - public void closeInt() { - try { - synchronized (mReadStatusThreadLock) { - if (mReadStatusThread != null) { - try { - mStopReadStatusThread = true; - mReadStatusThread.join(); - } catch (Exception e) { - Log.w(TAG, "An error occured while waiting for status read thread", e); - } - mStopReadStatusThread = false; - mReadStatusThread = null; - mReadStatusException = null; - } - } - resetDevice(); - } catch(Exception ignored) {} - try { - mConnection.releaseInterface(mDevice.getInterface(0)); - } catch(Exception ignored) {} - } - - private int filterBaudRate(int baudRate) { - if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) { - return baudRate & ~(1<<29); // for testing purposes accept without further checks - } - if (baudRate <= 0) { - throw new IllegalArgumentException("Invalid baud rate: " + baudRate); - } - if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { - return baudRate; - } - for(int br : standardBaudRates) { - if (br == baudRate) { - return baudRate; - } - } - /* - * Formula taken from Linux + FreeBSD. - * - * For TA+TB devices - * baudrate = baseline / (mantissa * 2^exponent) - * where - * mantissa = buf[10:0] - * exponent = buf[15:13 16] - * - * For other devices - * baudrate = baseline / (mantissa * 4^exponent) - * where - * mantissa = buf[8:0] - * exponent = buf[11:9] - * - */ - int baseline, mantissa, exponent, buf, effectiveBaudRate; - baseline = 12000000 * 32; - mantissa = baseline / baudRate; - if (mantissa == 0) { // > unrealistic 384 MBaud - throw new UnsupportedOperationException("Baud rate to high"); - } - exponent = 0; - if (mDeviceType == DeviceType.DEVICE_TYPE_T) { - while (mantissa >= 2048) { - if (exponent < 15) { - mantissa >>= 1; /* divide by 2 */ - exponent++; - } else { // < 7 baud - throw new UnsupportedOperationException("Baud rate to low"); - } - } - buf = mantissa + ((exponent & ~1) << 12) + ((exponent & 1) << 16) + (1 << 31); - effectiveBaudRate = (baseline / mantissa) >> exponent; - } else { - while (mantissa >= 512) { - if (exponent < 7) { - mantissa >>= 2; /* divide by 4 */ - exponent++; - } else { // < 45.8 baud - throw new UnsupportedOperationException("Baud rate to low"); - } - } - buf = mantissa + (exponent << 9) + (1 << 31); - effectiveBaudRate = (baseline / mantissa) >> (exponent << 1); - } - double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); - if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud - throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); - - Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d", - baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent)); - return buf; - } - - @Override - public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { - baudRate = filterBaudRate(baudRate); - if ((mBaudRate == baudRate) && (mDataBits == dataBits) - && (mStopBits == stopBits) && (mParity == parity)) { - // Make sure no action is performed if there is nothing to change - return; - } - - byte[] lineRequestData = new byte[7]; - lineRequestData[0] = (byte) (baudRate & 0xff); - lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); - lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); - lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); - - switch (stopBits) { - case STOPBITS_1: - lineRequestData[4] = 0; - break; - case STOPBITS_1_5: - lineRequestData[4] = 1; - break; - case STOPBITS_2: - lineRequestData[4] = 2; - break; - default: - throw new IllegalArgumentException("Invalid stop bits: " + stopBits); - } - - switch (parity) { - case PARITY_NONE: - lineRequestData[5] = 0; - break; - case PARITY_ODD: - lineRequestData[5] = 1; - break; - case PARITY_EVEN: - lineRequestData[5] = 2; - break; - case PARITY_MARK: - lineRequestData[5] = 3; - break; - case PARITY_SPACE: - lineRequestData[5] = 4; - break; - default: - throw new IllegalArgumentException("Invalid parity: " + parity); - } - - if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { - throw new IllegalArgumentException("Invalid data bits: " + dataBits); - } - lineRequestData[6] = (byte) dataBits; - - ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); - - resetDevice(); - - mBaudRate = baudRate; - mDataBits = dataBits; - mStopBits = stopBits; - mParity = parity; - } - - @Override - public boolean getCD() throws IOException { - return testStatusFlag(STATUS_FLAG_CD); - } - - @Override - public boolean getCTS() throws IOException { - return testStatusFlag(STATUS_FLAG_CTS); - } - - @Override - public boolean getDSR() throws IOException { - return testStatusFlag(STATUS_FLAG_DSR); - } - - @Override - public boolean getDTR() throws IOException { - return (mControlLinesValue & CONTROL_DTR) != 0; - } - - @Override - public void setDTR(boolean value) throws IOException { - int newControlLinesValue; - if (value) { - newControlLinesValue = mControlLinesValue | CONTROL_DTR; - } else { - newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; - } - setControlLines(newControlLinesValue); - } - - @Override - public boolean getRI() throws IOException { - return testStatusFlag(STATUS_FLAG_RI); - } - - @Override - public boolean getRTS() throws IOException { - return (mControlLinesValue & CONTROL_RTS) != 0; - } - - @Override - public void setRTS(boolean value) throws IOException { - int newControlLinesValue; - if (value) { - newControlLinesValue = mControlLinesValue | CONTROL_RTS; - } else { - newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; - } - setControlLines(newControlLinesValue); - } - - - @Override - public EnumSet getControlLines() throws IOException { - int status = getStatus(); - EnumSet set = EnumSet.noneOf(ControlLine.class); - if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS); - if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS); - if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR); - if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR); - if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD); - if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI); - return set; - } - - @Override - public EnumSet getSupportedControlLines() throws IOException { - return EnumSet.allOf(ControlLine.class); - } - - @Override - public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { - if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { - int index = 0; - if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE; - if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE; - if(index != 0) - vendorOut(RESET_HXN_REQUEST, index, null); - } else { - if (purgeWriteBuffers) - vendorOut(FLUSH_RX_REQUEST, 0, null); - if (purgeReadBuffers) - vendorOut(FLUSH_TX_REQUEST, 0, null); - } - } - - @Override - public void setBreak(boolean value) throws IOException { - ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null); - } - } - - public static Map getSupportedDevices() { - final Map supportedDevices = new LinkedHashMap<>(); - supportedDevices.put(UsbId.VENDOR_PROLIFIC, - new int[] { - UsbId.PROLIFIC_PL2303, - UsbId.PROLIFIC_PL2303GC, - UsbId.PROLIFIC_PL2303GB, - UsbId.PROLIFIC_PL2303GT, - UsbId.PROLIFIC_PL2303GL, - UsbId.PROLIFIC_PL2303GE, - UsbId.PROLIFIC_PL2303GS, - }); - return supportedDevices; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java deleted file mode 100644 index 271ea51..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.bg7yoz.ft8cn.serialport; - -import java.io.InterruptedIOException; - -/** - * Signals that a timeout has occurred on serial write. - * Similar to SocketTimeoutException. - * - * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred - */ -public class SerialTimeoutException extends InterruptedIOException { - public SerialTimeoutException(String s) { - super(s); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java deleted file mode 100644 index 73ab2a2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java +++ /dev/null @@ -1,95 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -/** - * Registry of USB vendor/product ID constants. - * - * Culled from various sources; see - * usb.ids for one listing. - * - * @author mike wakerly (opensource@hoho.com) - */ -public final class UsbId { - - public static final int VENDOR_FTDI = 0x0403; - public static final int FTDI_FT232R = 0x6001; - public static final int FTDI_ZERO = 0x0; - public static final int FTDI_FT2232H = 0x6010; - public static final int FTDI_FT4232H = 0x6011; - public static final int FTDI_FT232H = 0x6014; - public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD - - public static final int VENDOR_ATMEL = 0x03EB; - public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; - - public static final int VENDOR_ARDUINO = 0x2341; - public static final int ARDUINO_UNO = 0x0001; - public static final int ARDUINO_MEGA_2560 = 0x0010; - public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; - public static final int ARDUINO_MEGA_ADK = 0x003f; - public static final int ARDUINO_MEGA_2560_R3 = 0x0042; - public static final int ARDUINO_UNO_R3 = 0x0043; - public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; - public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; - public static final int ARDUINO_LEONARDO = 0x8036; - public static final int ARDUINO_MICRO = 0x8037; - - public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; - public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; - - public static final int CDC_WOLF_PID= 0xF001;// ST CDC WOLF RIG - - public static final int VENDOR_LEAFLABS = 0x1eaf; - public static final int LEAFLABS_MAPLE = 0x0004; - - public static final int VENDOR_SILABS = 0x10c4; - public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 - public static final int SILABS_CP2105 = 0xea70; - public static final int SILABS_CP2108 = 0xea71; - - public static final int VENDOR_PROLIFIC = 0x067b; - public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX - public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN - public static final int PROLIFIC_PL2303GB = 0x23b3; // " - public static final int PROLIFIC_PL2303GT = 0x23c3; // " - public static final int PROLIFIC_PL2303GL = 0x23d3; // " - public static final int PROLIFIC_PL2303GE = 0x23e3; // " - public static final int PROLIFIC_PL2303GS = 0x23f3; // " - - public static final int VENDOR_QINHENG = 0x1a86; - public static final int QINHENG_CH340 = 0x7523; - public static final int QINHENG_CH341A = 0x5523; - public static final int QINHENG_CH341K = 29986; - - - // at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids - public static final int VENDOR_ARM = 0x0d28; - public static final int ARM_MBED = 0x0204; - - public static final int VENDOR_ST = 0x0483; - public static final int ST_CDC = 0x5740; - public static final int ST_CDC2 = 0xA34C; - public static final int ST_CDC3 = 0x5732; - - public static final int VENDOR_RASPBERRY_PI = 0x2e8a; - public static final int RASPBERRY_PI_PICO_MICROPYTHON = 0x0005; - - public static final int VENDOR_GUOHE1 = 0x5678; - public static final int PID_GUOHE1 = 0x6789; - - public static final int VENDOR_ICOM = 0x0c26; - public static final int IC_R30 = 0x002b; - public static final int IC_705 = 0x0036; - public static final int ICOM_USB_SERIAL_CONTROL = 0x0018; - public static final int XIEGU_X6100 = 0x55D2;//xiegu - - private UsbId() { - throw new IllegalAccessError("Non-instantiable class"); - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java deleted file mode 100644 index 49b1690..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbDevice; - -import java.util.List; - -/** - * - * @author mike wakerly (opensource@hoho.com) - */ -public interface UsbSerialDriver { - - /** - * Returns the raw {@link UsbDevice} backing this port. - * - * @return the device - */ - UsbDevice getDevice(); - - /** - * Returns all available ports for this device. This list must have at least - * one entry. - * - * @return the ports - */ - List getPorts(); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java deleted file mode 100644 index f294da3..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java +++ /dev/null @@ -1,261 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbManager; - -import androidx.annotation.IntDef; - -import java.io.Closeable; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.EnumSet; - -/** - * Interface for a single serial port. - * - * @author mike wakerly (opensource@hoho.com) - */ -public interface UsbSerialPort extends Closeable { - - /** 5 data bits. */ - int DATABITS_5 = 5; - /** 6 data bits. */ - int DATABITS_6 = 6; - /** 7 data bits. */ - int DATABITS_7 = 7; - /** 8 data bits. */ - int DATABITS_8 = 8; - - /** Values for setParameters(..., parity) */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) - @interface Parity {} - /** No parity. */ - int PARITY_NONE = 0; - /** Odd parity. */ - int PARITY_ODD = 1; - /** Even parity. */ - int PARITY_EVEN = 2; - /** Mark parity. */ - int PARITY_MARK = 3; - /** Space parity. */ - int PARITY_SPACE = 4; - - /** 1 stop bit. */ - int STOPBITS_1 = 1; - /** 1.5 stop bits. */ - int STOPBITS_1_5 = 3; - /** 2 stop bits. */ - int STOPBITS_2 = 2; - - /** Values for get[Supported]ControlLines() */ - enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } - - /** - * Returns the driver used by this port. - */ - UsbSerialDriver getDriver(); - - /** - * Returns the currently-bound USB device. - */ - UsbDevice getDevice(); - - /** - * Port number within driver. - */ - int getPortNumber(); - - /** - * Returns the write endpoint. - * @return write endpoint - */ - UsbEndpoint getWriteEndpoint(); - - /** - * Returns the read endpoint. - * @return read endpoint - */ - UsbEndpoint getReadEndpoint(); - - /** - * The serial number of the underlying UsbDeviceConnection, or {@code null}. - * - * @return value from {@link UsbDeviceConnection#getSerial()} - * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted - */ - String getSerial(); - - /** - * Opens and initializes the port. Upon success, caller must ensure that - * {@link #close()} is eventually called. - * - * @param connection an open device connection, acquired with - * {@link UsbManager#openDevice(UsbDevice)} - * @throws IOException on error opening or initializing the port. - */ - void open(UsbDeviceConnection connection) throws IOException; - - /** - * Closes the port and {@link UsbDeviceConnection} - * - * @throws IOException on error closing the port. - */ - void close() throws IOException; - - /** - * Reads as many bytes as possible into the destination buffer. - * - * @param dest the destination byte buffer - * @param timeout the timeout for reading in milliseconds, 0 is infinite - * @return the actual number of bytes read - * @throws IOException if an error occurred during reading - */ - int read(final byte[] dest, final int timeout) throws IOException; - - /** - * Writes as many bytes as possible from the source buffer. - * - * @param src the source byte buffer - * @param timeout the timeout for writing in milliseconds, 0 is infinite - * @throws SerialTimeoutException if timeout reached before sending all data. - * ex.bytesTransferred may contain bytes transferred - * @throws IOException if an error occurred during writing - */ - void write(final byte[] src, final int timeout) throws IOException; - - /** - * Sets various serial port parameters. - * - * @param baudRate baud rate as an integer, for example {@code 115200}. - * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, - * {@link #DATABITS_7}, or {@link #DATABITS_8}. - * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. - * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, - * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. - * @throws IOException on error setting the port parameters - * @throws UnsupportedOperationException if values are not supported by a specific device - */ - void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; - - /** - * Gets the CD (Carrier Detect) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getCD() throws IOException; - - /** - * Gets the CTS (Clear To Send) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getCTS() throws IOException; - - /** - * Gets the DSR (Data Set Ready) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getDSR() throws IOException; - - /** - * Gets the DTR (Data Terminal Ready) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getDTR() throws IOException; - - /** - * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. - * - * @param value the value to set - * @throws IOException if an error occurred during writing - * @throws UnsupportedOperationException if not supported - */ - void setDTR(boolean value) throws IOException; - - /** - * Gets the RI (Ring Indicator) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getRI() throws IOException; - - /** - * Gets the RTS (Request To Send) bit from the underlying UART. - * - * @return the current state - * @throws IOException if an error occurred during reading - * @throws UnsupportedOperationException if not supported - */ - boolean getRTS() throws IOException; - - /** - * Sets the RTS (Request To Send) bit on the underlying UART, if supported. - * - * @param value the value to set - * @throws IOException if an error occurred during writing - * @throws UnsupportedOperationException if not supported - */ - void setRTS(boolean value) throws IOException; - - /** - * Gets all control line values from the underlying UART, if supported. - * Requires less USB calls than calling getRTS() + ... + getRI() individually. - * - * @return EnumSet.contains(...) is {@code true} if set, else {@code false} - * @throws IOException if an error occurred during reading - */ - EnumSet getControlLines() throws IOException; - - /** - * Gets all control line supported flags. - * - * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} - * @throws IOException if an error occurred during reading - */ - EnumSet getSupportedControlLines() throws IOException; - - /** - * Purge non-transmitted output data and / or non-read input data. - * - * @param purgeWriteBuffers {@code true} to discard non-transmitted output data - * @param purgeReadBuffers {@code true} to discard non-read input data - * @throws IOException if an error occurred during flush - * @throws UnsupportedOperationException if not supported - */ - void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; - - /** - * send BREAK condition. - * - * @param value set/reset - */ - void setBreak(boolean value) throws IOException; - - /** - * Returns the current state of the connection. - */ - boolean isOpen(); - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java deleted file mode 100644 index 9189997..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author mike wakerly (opensource@hoho.com) - */ -public class UsbSerialProber { - - private final ProbeTable mProbeTable; - - public UsbSerialProber(ProbeTable probeTable) { - mProbeTable = probeTable; - } - - public static UsbSerialProber getDefaultProber() { - return new UsbSerialProber(getDefaultProbeTable()); - } - - public static ProbeTable getDefaultProbeTable() { - final ProbeTable probeTable = new ProbeTable(); - probeTable.addDriver(CdcAcmSerialDriver.class); - probeTable.addDriver(Cp21xxSerialDriver.class); - probeTable.addDriver(FtdiSerialDriver.class); - probeTable.addDriver(ProlificSerialDriver.class); - probeTable.addDriver(Ch34xSerialDriver.class); - return probeTable; - } - - /** - * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} - * from the currently-attached {@link UsbDevice} hierarchy. This method does - * not require permission from the Android USB system, since it does not - * open any of the devices. - * - * @param usbManager usb manager - * @return a list, possibly empty, of all compatible drivers - */ - public List findAllDrivers(final UsbManager usbManager) { - final List result = new ArrayList<>(); - - for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { - final UsbSerialDriver driver = probeDevice(usbDevice); - if (driver != null) { - result.add(driver); - } - } - return result; - } - - /** - * Probes a single device for a compatible driver. - * - * @param usbDevice the usb device to probe - * @return a new {@link UsbSerialDriver} compatible with this device, or - * {@code null} if none available. - */ - public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { - final int vendorId = usbDevice.getVendorId(); - final int productId = usbDevice.getProductId(); - - final Class driverClass = - mProbeTable.findDriver(vendorId, productId); - if (driverClass != null) { - final UsbSerialDriver driver; - try { - final Constructor ctor = - driverClass.getConstructor(UsbDevice.class); - driver = ctor.newInstance(usbDevice); - } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | - IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - return driver; - } - return null; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java deleted file mode 100644 index 1fd8688..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.bg7yoz.ft8cn.serialport.util; - -public final class MonotonicClock { - - private static final long NS_PER_MS = 1_000_000; - - private MonotonicClock() { - } - - public static long millis() { - return System.nanoTime() / NS_PER_MS; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java deleted file mode 100644 index 9c014d9..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java +++ /dev/null @@ -1,257 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.bg7yoz.ft8cn.serialport.util; - -import android.os.Process; -import android.util.Log; - -import com.bg7yoz.ft8cn.serialport.UsbSerialPort; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Utility class which services a {@link ../UsbSerialPort} in its {@link #run()} method. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class SerialInputOutputManager implements Runnable { - - public enum State { - STOPPED, - RUNNING, - STOPPING - } - - public static boolean DEBUG = false; - - private static final String TAG = SerialInputOutputManager.class.getSimpleName(); - private static final int BUFSIZ = 4096; - - /** - * default read timeout is infinite, to avoid data loss with bulkTransfer API - */ - private int mReadTimeout = 0; - private int mWriteTimeout = 0; - - private final Object mReadBufferLock = new Object(); - private final Object mWriteBufferLock = new Object(); - - private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() - private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); - - private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; - private State mState = State.STOPPED; // Synchronized by 'this' - private Listener mListener; // Synchronized by 'this' - private final UsbSerialPort mSerialPort; - - public interface Listener { - /** - * Called when new incoming data is available. - */ - void onNewData(byte[] data); - - /** - * Called when {@link SerialInputOutputManager#run()} aborts due to an error. - */ - void onRunError(Exception e); - } - - public SerialInputOutputManager(UsbSerialPort serialPort) { - mSerialPort = serialPort; - mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); - } - - public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { - mSerialPort = serialPort; - mListener = listener; - mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); - } - - public synchronized void setListener(Listener listener) { - mListener = listener; - } - - public synchronized Listener getListener() { - return mListener; - } - - /** - * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss - * - * @param threadPriority see {@link Process#setThreadPriority(int)} - * */ - public void setThreadPriority(int threadPriority) { - if (mState != State.STOPPED) - throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); - mThreadPriority = threadPriority; - } - - /** - * read/write timeout - */ - public void setReadTimeout(int timeout) { - // when set if already running, read already blocks and the new value will not become effective now - if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) - throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); - mReadTimeout = timeout; - } - - public int getReadTimeout() { - return mReadTimeout; - } - - public void setWriteTimeout(int timeout) { - mWriteTimeout = timeout; - } - - public int getWriteTimeout() { - return mWriteTimeout; - } - - /** - * read/write buffer size - */ - public void setReadBufferSize(int bufferSize) { - if (getReadBufferSize() == bufferSize) - return; - synchronized (mReadBufferLock) { - mReadBuffer = ByteBuffer.allocate(bufferSize); - } - } - - public int getReadBufferSize() { - return mReadBuffer.capacity(); - } - - public void setWriteBufferSize(int bufferSize) { - if(getWriteBufferSize() == bufferSize) - return; - synchronized (mWriteBufferLock) { - ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); - if(mWriteBuffer.position() > 0) - newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); - mWriteBuffer = newWriteBuffer; - } - } - - public int getWriteBufferSize() { - return mWriteBuffer.capacity(); - } - - /** - * when using writeAsync, it is recommended to use readTimeout != 0, - * else the write will be delayed until read data is available - */ - public void writeAsync(byte[] data) { - synchronized (mWriteBufferLock) { - mWriteBuffer.put(data); - } - } - - /** - * start SerialInputOutputManager in separate thread - */ - public void start() { - if(mState != State.STOPPED) - throw new IllegalStateException("already started"); - new Thread(this, this.getClass().getSimpleName()).start(); - } - - /** - * stop SerialInputOutputManager thread - * - * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to - * interrupt blocking read - */ - public synchronized void stop() { - if (getState() == State.RUNNING) { - Log.i(TAG, "Stop requested"); - mState = State.STOPPING; - } - } - - public synchronized State getState() { - return mState; - } - - /** - * Continuously services the read and write buffers until {@link #stop()} is - * called, or until a driver exception is raised. - */ - @Override - public void run() { - synchronized (this) { - if (getState() != State.STOPPED) { - throw new IllegalStateException("Already running"); - } - mState = State.RUNNING; - } - Log.i(TAG, "Running ..."); - try { - if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) - Process.setThreadPriority(mThreadPriority); - while (true) { - if (getState() != State.RUNNING) { - Log.i(TAG, "Stopping mState=" + getState()); - break; - } - step(); - } - } catch (Exception e) { - Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); - final Listener listener = getListener(); - if (listener != null) { - listener.onRunError(e); - } - } finally { - synchronized (this) { - mState = State.STOPPED; - Log.i(TAG, "Stopped"); - } - } - } - - private void step() throws IOException { - // Handle incoming data. - byte[] buffer; - synchronized (mReadBufferLock) { - buffer = mReadBuffer.array(); - } - int len = mSerialPort.read(buffer, mReadTimeout); - if (len > 0) { - if (DEBUG) { - Log.d(TAG, "Read data len=" + len); - } - final Listener listener = getListener(); - if (listener != null) { - final byte[] data = new byte[len]; - System.arraycopy(buffer, 0, data, 0, len); - listener.onNewData(data); - } - } - - // Handle outgoing data. - buffer = null; - synchronized (mWriteBufferLock) { - len = mWriteBuffer.position(); - if (len > 0) { - buffer = new byte[len]; - mWriteBuffer.rewind(); - mWriteBuffer.get(buffer, 0, len); - mWriteBuffer.clear(); - } - } - if (buffer != null) { - if (DEBUG) { - Log.d(TAG, "Writing data len=" + len); - } - mSerialPort.write(buffer, mWriteTimeout); - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java deleted file mode 100644 index 1637c92..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.bg7yoz.ft8cn.spectrum; -/** - * 用于瀑布图的音频接收。以一个FT8符号为颗粒度。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import androidx.lifecycle.MutableLiveData; - -import com.bg7yoz.ft8cn.wave.HamRecorder; -import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; - -public class SpectrumListener { - private static final String TAG = "SpectrumListener"; - private HamRecorder hamRecorder; - - private float[] dataBuffer=new float[0]; - public MutableLiveData mutableDataBuffer = new MutableLiveData<>(); - - - private final OnGetVoiceDataDone onGetVoiceDataDone=new OnGetVoiceDataDone() { - @Override - public void onGetDone(float[] data) { - mutableDataBuffer.postValue(data); - } - }; - - public SpectrumListener(HamRecorder hamRecorder) { - this.hamRecorder = hamRecorder; - doReceiveData(); - } - - - private void doReceiveData(){ - hamRecorder.getVoiceData(160,false,onGetVoiceDataDone); - } - - public float[] getDataBuffer() { - return dataBuffer; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java deleted file mode 100644 index 813b458..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.bg7yoz.ft8cn.timer; -/** - * UtcTimer类的一个接口,用于UtcTimer的回调。 - * UtcTimer是一个动作触发器,在一个时钟周期到来时触发动作,触发动作的回调函数是DoOnSecTimer。 - * UtcTimer在以一个固定的频率循环(目前默认时100毫秒),在每一个频率下的回调函数是doHeartBeatTimer。 - * 注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 - * - * @author BG7YOZ - * @date 2022.5.6 - */ -public interface OnUtcTimer { - void doHeartBeatTimer(long utc);//心跳回调,在触发器每一个循环时触发,心跳的只处理简单事务,不要过多占用CPU,防止线程叠加 - void doOnSecTimer(long utc);//当指定的时间间隔时触发 -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java deleted file mode 100644 index 85eab93..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.bg7yoz.ft8cn.timer; -/** - * UtcTimer类,用于实现FT8在各通联周期开始时触发的动作。FT8的通联因为需要时钟同步,以UTC时间为基准,每15秒一个周期(FT4为7.5秒)。 - * 该类采用Timer和TimerTask来实现定时触发动作。 - * 由于FT8需要时钟同步(精度为秒),在每一个周期开始触发动作,所以,目前以100毫秒为心跳,检测是否处于周期(对UTC时间以周期的秒数取模)的开始, - * 如果是,则回调doHeartBeatTimer函数,为防止重复动作,触发后会等待1秒钟后再进入新的心跳周期(因为是以秒数取模)。 - * 注意!!为防止回调动作占用时间过长,影响下一个动作的触发,所以,回调都是以多线程的方式调用,在使用时要注意线程安全。 - *

- * @author BG7YOZ - * @date 2022.5.7 - */ - -import android.annotation.SuppressLint; - -import com.bg7yoz.ft8cn.ui.ToastMessage; - -import org.apache.commons.net.ntp.NTPUDPClient; -import org.apache.commons.net.ntp.TimeInfo; - -import java.io.IOException; -import java.net.InetAddress; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - - -public class UtcTimer { - private final int sec; - private final boolean doOnce; - private final OnUtcTimer onUtcTimer; - - - private long utc; - public static int delay = 0;//时钟总的延时,(毫秒) - private boolean running = false;//用来判断是否触发周期的动作 - - private final Timer secTimer = new Timer(); - private final Timer heartBeatTimer = new Timer(); - private int time_sec = 0;//时间的偏移量; - private final ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); - private final Runnable doSomething = new Runnable() { - @Override - public void run() { - onUtcTimer.doOnSecTimer(utc); - } - }; - private final ExecutorService heartBeatThreadPool = Executors.newCachedThreadPool(); - private final Runnable doHeartBeat = new Runnable() { - @Override - public void run() { - onUtcTimer.doHeartBeatTimer(utc); - } - }; - - /** - * 类方法。获得UTC时间的字符串表示结果。 - * - * @param time 时间。 - * @return String 以字符串方式显示UTC时间。 - */ - @SuppressLint("DefaultLocale") - public static String getTimeStr(long time) { - long curtime = time / 1000; - long hour = ((curtime) / (60 * 60)) % 24;//小时 - long sec = (curtime) % 60;//秒 - long min = ((curtime) % 3600) / 60;//分 - return String.format("UTC : %02d:%02d:%02d", hour, min, sec); - } - - /** - * 以HHMMSS格式显示UTC时间 - * - * @param time - * @return - */ - @SuppressLint("DefaultLocale") - public static String getTimeHHMMSS(long time) { - long curtime = time / 1000; - long hour = ((curtime) / (60 * 60)) % 24;//小时 - long sec = (curtime) % 60;//秒 - long min = ((curtime) % 3600) / 60;//分 - return String.format("%02d%02d%02d", hour, min, sec); - } - - public static String getYYYYMMDD(long time) { - @SuppressLint("SimpleDateFormat") - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); - simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - return simpleDateFormat.format(new Date(time)); - } - - public static String getDatetimeStr(long time) { - @SuppressLint("SimpleDateFormat") - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - return simpleDateFormat.format(new Date(time)); - } - - public static String getDatetimeYYYYMMDD_HHMMSS(long time) { - @SuppressLint("SimpleDateFormat") - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss"); - simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - return simpleDateFormat.format(new Date(time)); - } - - /** - * 时钟触发器的构建方法。需要确定时钟的周期,周期一般是15秒或7.5秒,因为周期的参数是int,所以参数的单位是十分之一秒。 - * 由于心跳频率较快(暂时定为100毫秒),心跳的动作越简练越好,要在下一个心跳开始之前处理完,防止造成线程叠加,影响性能。 - * 心跳动作不会因周期动作不触发(running==false)而影响,只要UtcTimer的实例存在,心跳动作就运行(方便显示时钟数据)。 - * 该触发器需要调用delete函数彻底停止(心跳动作也停止了)。 - * - * @param sec 时钟的周期,单位是十分之一秒,如:15秒,值150,7.5秒,值75。 - * @param doOnce 是否只触发一次。 - * @param onUtcTimer 回调函数,包括心跳回调,和周期起始触发动作的回调。 - */ - public UtcTimer(int sec, boolean doOnce, OnUtcTimer onUtcTimer) { - this.sec = sec; - this.doOnce = doOnce; - this.onUtcTimer = onUtcTimer; - - //初始化Timer的任务。 - //TimerTask timerTask = initTask(); - //执行timer,延时0执行,周期100毫秒 - - secTimer.schedule(secTask(), 0, 10); - heartBeatTimer.schedule(heartBeatTask(), 0, 1000); - } - - /** - * 定义时钟触发的动作。 - * 时钟触发器的构建方法。需要确定时钟的周期,周期一般是15秒或7.5秒,因为周期的参数是int,所以参数的单位是十分之一秒。 - * 由于心跳频率较快(暂时定为100毫秒),心跳的动作越简练越好,要在下一个心跳开始之前处理完,防止造成线程叠加,影响性能。 - * 心跳动作不会因周期动作不触发(running==false)而影响,只要UtcTimer的实例存在,心跳动作就运行(方便显示时钟数据)。 - * - * @return TimerTask 返回动作的实例。 - */ - - - private TimerTask heartBeatTask() { - return new TimerTask() { - @Override - public void run() { - //心跳动作 - doHeartBeatEvent(onUtcTimer); - } - }; - } - - private TimerTask secTask() { - return new TimerTask() { - - - @Override - public void run() { - - try { - utc = getSystemTime();//获取当前的UTC时间 - //utc/100是取十分之一秒为单位,所以取模应该是600,而非60,切记! - //running是判断是否需要触发周期动作。 - //+80是因为触发后一些动作影响,补偿的时间 - //time_sec是时间的偏移量 - if (running && (((utc - time_sec) / 100) % 600) % sec == 0) { - //周期动作 - //注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 - cachedThreadPool.execute(doSomething);//用线程池的方式调用,减少系统消耗 - //thread.run(); - - //如果只执行一次触发动作 - if (doOnce) { - running = false; - return; - } - - //等待1秒钟,防止重复触发动作。 - Thread.sleep(1000); - } - - - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - } - - /** - * 触发心跳时的动作。由Timer调用,写此函数是方便阅读。动作是在新创建的线程中执行。 - * - * @param onUtcTimer 触发时钟的回调函数。 - */ - private void doHeartBeatEvent(OnUtcTimer onUtcTimer) { - //心跳动作 - heartBeatThreadPool.execute(doHeartBeat); -// new Thread(new Runnable() { -// @Override -// public void run() { -// //注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 -// onUtcTimer.doHeartBeatTimer(utc); -// } -// }).start(); - } - - - public void stop() { - running = false; - } - - public void start() { - running = true; - } - - public boolean isRunning() { - return running; - } - - public void delete() { - secTimer.cancel(); - heartBeatTimer.cancel(); - } - - /** - * 设置时间偏移量,正值是向后偏移 - * - * @param time_sec 向前的偏移量 - */ - public void setTime_sec(int time_sec) { - this.time_sec = time_sec; - } - - /** - * 获取时间偏移 - * - * @return 时间偏移值(毫秒) - */ - public int getTime_sec() { - return time_sec; - } - - public long getUtc() { - return utc; - } - - /** - * 根据UTC时间计算时序 - * - * @param utc UTC时间 - * @return 时序:0,1 - */ - public static int sequential(long utc) { - return (int) ((((utc) / 1000) / 15) % 2); - } - - public static int getNowSequential() { - return sequential(getSystemTime()); - } - - public static long getSystemTime() { - return delay + System.currentTimeMillis(); - } - - /** - * 使用微软的时间服务器同步时间 - */ - public static void syncTime(AfterSyncTime afterSyncTime) { - new Thread(new Runnable() { - @Override - public void run() { - NTPUDPClient timeClient = new NTPUDPClient(); - InetAddress inetAddress = null; - TimeInfo timeInfo = null; - try { - inetAddress = InetAddress.getByName("time.windows.com"); - timeInfo = timeClient.getTime(inetAddress); - long serverTime = timeInfo.getMessage().getTransmitTimeStamp().getTime(); - int trueDelay = (int) ((serverTime - System.currentTimeMillis())); - UtcTimer.delay = trueDelay % 15000;//延迟的周期 - if (afterSyncTime != null) { - afterSyncTime.doAfterSyncTimer(trueDelay); - } - } catch (IOException e) { - if (afterSyncTime != null) { - afterSyncTime.syncFailed(e); - } - } - - //long localDeviceTime = timeInfo.getReturnTime(); - - } - }).start(); - } - - public interface AfterSyncTime { - void doAfterSyncTimer(int secTime); - - void syncFailed(IOException e); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java deleted file mode 100644 index 44925c4..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 频段列表界面 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.OperationBand; - -public class BandsSpinnerAdapter extends BaseAdapter { - private final Context mContext; - //private final OperationBand operationBand; - public BandsSpinnerAdapter(Context context) { - //operationBand= - OperationBand.getInstance(context); - mContext=context; - } - - @Override - public int getCount() { - return OperationBand.bandList.size(); - } - - @Override - public Object getItem(int i) { - return OperationBand.bandList.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.operation_band_spinner_item, null); - if (view!=null){ - TextView textView=view.findViewById(R.id.operationBandItemTextView); - textView.setText(OperationBand.getBandInfo(i)); - } - return view; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java deleted file mode 100644 index bf48f65..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 波特率列表界面 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.R; - - -public class BauRateSpinnerAdapter extends BaseAdapter { - private final Context mContext; - private final int[] bauRates= {4800,9600,14400,19200,38400,43000,56000,57600,115200}; - public BauRateSpinnerAdapter(Context context) { - mContext=context; - } - - @Override - public int getCount() { - return bauRates.length; - } - - @Override - public Object getItem(int i) { - return bauRates[i]; - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.bau_rate_spinner_item, null); - if (view!=null){ - TextView textView=(TextView)view.findViewById(R.id.bauRateItemTextView); - textView.setText(String.valueOf(bauRates[i])); - } - return view; - } - public int getPosition(int i){ - for (int j = 0; j < bauRates.length; j++) { - if (bauRates[j]==i){ - return j; - } - } - return 2; - } - public int getValue(int position){ - return bauRates[position]; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java deleted file mode 100644 index ddfb0f9..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java +++ /dev/null @@ -1,426 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 消息列表Adapter。使用此Adapter有解码界面、呼叫界面、网格追踪界面。 - * 不同周期背景不同。为了区分,共有4种背景颜色。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Paint; -import android.opengl.Visibility; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.rigs.BaseRigOperation; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; - -public class CallingListAdapter extends RecyclerView.Adapter { - public enum ShowMode{CALLING_LIST,MY_CALLING,TRACKER} - private static final String TAG = "CallingListAdapter"; - private final MainViewModel mainViewModel; - private final ArrayList ft8MessageArrayList; - private final Context context; - - //private boolean isCallingList = true; - private final ShowMode showMode; - private View.OnClickListener onItemClickListener; - - private final View.OnCreateContextMenuListener menuListener=new View.OnCreateContextMenuListener() { - @Override - public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { - - //view.setTag(ft8Message);//把消息对象传递给上一级界面 - int postion= (int) view.getTag(); - if (postion==-1) return; - if (postion>ft8MessageArrayList.size()-1) return; - Ft8Message ft8Message=ft8MessageArrayList.get(postion); - - //添加菜单的参数i1:组,i2:id值,i3:显示顺序 - if (!ft8Message.getCallsignTo().contains("...")//目标不能是自己 - && !ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) - && !(ft8Message.i3==0&&ft8Message.n3==0)) { - if (!ft8Message.checkIsCQ()) { - if (showMode==ShowMode.CALLING_LIST) {//在消息列表中就可以显示这个菜单了 - contextMenu.add(0, 0, 0, String.format( - GeneralVariables.getStringFromResource(R.string.tracking_receiver) - , ft8Message.getCallsignTo(), ft8Message.toWhere)) - .setActionView(view); - } - if (!mainViewModel.ft8TransmitSignal.isSynFrequency()) {//如果同频率的话,会与发送者同频,会影响发送者!!! - contextMenu.add(0, 1, 0, String.format( - GeneralVariables.getStringFromResource(R.string.calling_receiver) - , ft8Message.getCallsignTo(), ft8Message.toWhere)) - .setActionView(view); - } - //说明是对我呼叫,加上回复菜单 - if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign)) { - contextMenu.add(0, 4, 0, String.format( - GeneralVariables.getStringFromResource(R.string.reply_to) - , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) - .setActionView(view); - - } - if (showMode!=ShowMode.TRACKER) { - contextMenu.add(0, 5, 0 - , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) - , ft8Message.getCallsignTo())).setActionView(view); - } - - } - } - - if (!ft8Message.getCallsignFrom().contains("...") - && !ft8Message.getCallsignFrom().equals(GeneralVariables.myCallsign) - && !(ft8Message.i3==0&&ft8Message.n3==0)) { - if (showMode==ShowMode.CALLING_LIST) {//在消息列表中就可以显示这个菜单了 - contextMenu.add(1, 2, 0, String.format( - GeneralVariables.getStringFromResource(R.string.tracking) - , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) - .setActionView(view); - } - contextMenu.add(1, 3, 0, String.format( - GeneralVariables.getStringFromResource(R.string.calling) - , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) - .setActionView(view); - if (showMode!=ShowMode.TRACKER) { - contextMenu.add(1, 6, 0 - , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) - , ft8Message.getCallsignFrom())).setActionView(view); - } - } - - } - }; - - - - public CallingListAdapter(Context context, MainViewModel mainViewModel - , ArrayList messages, ShowMode showMode) { - this.mainViewModel = mainViewModel; - this.context = context; - //this.isCallingList = isCallingList; - this.showMode=showMode; - ft8MessageArrayList = messages; - } - - @NonNull - @Override - public CallingListItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.call_list_holder_item, parent, false); - return new CallingListItemHolder(view,onItemClickListener,menuListener); - } - - /** - * 删除消息 - * - * @param position 在列表中的位置 - */ - public void deleteMessage(int position) { - if (position >= 0) { - ft8MessageArrayList.remove(position); - } - } - - public Ft8Message getMessageByPosition(int position){ - if (ft8MessageArrayList==null) return null; - if (position<0) return null; - if (position>ft8MessageArrayList.size()-1) return null; - return ft8MessageArrayList.get(position); - } - - /** - * 通过holder获取消息 - * - * @param holder holder - * @return ft8message - */ - public Ft8Message getMessageByViewHolder(RecyclerView.ViewHolder holder) { - if (holder.getAdapterPosition() == -1) { - return null; - } - return ft8MessageArrayList.get(holder.getAdapterPosition()); - } - - @SuppressLint("ResourceAsColor") - @Override - public void onBindViewHolder(@NonNull CallingListItemHolder holder, int position) { - holder.callListHolderConstraintLayout.setTag(position);//设置layout的tag,为了识别消息的定位 - holder.ft8Message = ft8MessageArrayList.get(position); - holder.showMode = showMode;//确定是消息列表还是关注消息的列表 - holder.isSyncFreq = mainViewModel.ft8TransmitSignal.isSynFrequency();//如果同频发射,就不显示呼叫接收者 - - holder.callingUtcTextView.setText(UtcTimer.getTimeHHMMSS(holder.ft8Message.utcTime)); - //时序,包括颜色, - holder.callingListSequenceTextView.setText(holder.ft8Message.getSequence() == 0 ? "0" : "1"); - holder.isWeakSignalImageView.setVisibility(holder.ft8Message.isWeakSignal ? View.VISIBLE:View.INVISIBLE); - - if (showMode==ShowMode.MY_CALLING) {//在呼叫界面 - holder.callingListSequenceTextView.setTextColor(context.getColor(R.color.follow_call_text_color)); - } - - //根据1分钟内的4个时序区分颜色 - switch (holder.ft8Message.getSequence4()) { - case 0: - holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); - break; - case 1: - holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); - break; - case 2: - holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_2_style); - break; - case 3: - holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_3_style); - break; - } - - holder.callingListIdBTextView.setText(holder.ft8Message.getdB()); - //时间偏移,如果超过1.0秒,-0.05秒,红色提示 - holder.callListDtTextView.setText(holder.ft8Message.getDt()); - if (holder.ft8Message.time_sec > 1.0f || holder.ft8Message.time_sec < -0.05) { - holder.callListDtTextView.setTextColor(context.getResources().getColor( - R.color.message_in_my_call_text_color)); - } else { - holder.callListDtTextView.setTextColor(context.getResources().getColor( - R.color.text_view_color)); - } - - - holder.callingListFreqTextView.setText(holder.ft8Message.getFreq_hz()); - - //查是不是通联过的呼号,获取是否存在holder.otherBandIsQso中 - setQueryHolderQSL_Callsign(holder); - - //是否有与我呼号有关的消息 - if (holder.ft8Message.inMyCall()) { - holder.callListMessageTextView.setTextColor(context.getResources().getColor( - R.color.message_in_my_call_text_color)); - } else if (holder.otherBandIsQso) { - //设置在别的波段通联过的消息颜色 - holder.callListMessageTextView.setTextColor(context.getResources().getColor( - R.color.fromcall_is_qso_text_color)); - } else { - holder.callListMessageTextView.setTextColor(context.getResources().getColor( - R.color.message_text_color)); - } - - - holder.callListMessageTextView.setText(holder.ft8Message.getMessageText(true)); - - //载波频率 - holder.bandItemTextView.setText(BaseRigOperation.getFrequencyStr(holder.ft8Message.band)); - //计算距离 - holder.callingListDistTextView.setText(MaidenheadGrid.getDistStr( - GeneralVariables.getMyMaidenheadGrid() - , holder.ft8Message.getMaidenheadGrid(mainViewModel.databaseOpr))); - holder.callingListCallsignToTextView.setText("");//被呼叫者 - holder.callingListCallsignFromTextView.setText("");//呼叫者 - - //消息类型 - holder.callingListCommandIInfoTextView.setText(holder.ft8Message.getCommandInfo()); - if (holder.ft8Message.i3 == 1 || holder.ft8Message.i3 == 2) { - holder.callingListCommandIInfoTextView.setTextColor(context.getResources().getColor( - R.color.text_view_color)); - } else { - holder.callingListCommandIInfoTextView.setTextColor(context.getResources().getColor( - R.color.message_in_my_call_text_color)); - } - - //设置是否CQ的颜色 - if (holder.ft8Message.checkIsCQ()) { - holder.callListMessageTextView.setBackgroundResource(R.color.textview_cq_color); - holder.ft8Message.toWhere = ""; - } else { - holder.callListMessageTextView.setBackgroundResource(R.color.textview_none_color); - } - - - if (holder.ft8Message.fromWhere != null) { - holder.callingListCallsignFromTextView.setText(holder.ft8Message.fromWhere); - } else { - holder.callingListCallsignFromTextView.setText(""); - } - - if (holder.ft8Message.toWhere != null) { - holder.callingListCallsignToTextView.setText(holder.ft8Message.toWhere); - } else { - holder.callingListCallsignToTextView.setText(""); - } - - //给没有通联过的分区打标记 - setToDxcc(holder); - setFromDxcc(holder); - - - //查询呼号归属地,为防止占用太多运算资源,当from为空是再做查询的工作 -// if (holder.ft8Message.fromWhere == null) { -// setQueryHolderCallsign(holder);//查询呼号归属地 -// } - - if (holder.ft8Message.freq_hz <= 0.01f) {//这是发射 - //holder.callingListSequenceTextView.setVisibility(View.GONE); - holder.callingListIdBTextView.setVisibility(View.GONE); - holder.callListDtTextView.setVisibility(View.GONE); - holder.callingListFreqTextView.setText("TX"); - holder.bandItemTextView.setVisibility(View.GONE); - holder.callingListDistTextView.setVisibility(View.GONE); - holder.callingListCommandIInfoTextView.setVisibility(View.GONE); - holder.callingUtcTextView.setVisibility(View.GONE); - holder.callingListCallsignToTextView.setVisibility(View.GONE); - holder.callingListCallsignFromTextView.setVisibility(View.GONE); - holder.dxccToImageView.setVisibility(View.GONE); - holder.ituToImageView.setVisibility(View.GONE); - holder.cqToImageView.setVisibility(View.GONE); - holder.dxccFromImageView.setVisibility(View.GONE); - holder.ituFromImageView.setVisibility(View.GONE); - holder.cqFromImageView.setVisibility(View.GONE); - } else { - //holder.callingListSequenceTextView.setVisibility(View.VISIBLE); - holder.callingListIdBTextView.setVisibility(View.VISIBLE); - holder.callListDtTextView.setVisibility(View.VISIBLE); - holder.bandItemTextView.setVisibility(View.VISIBLE); - holder.callingListDistTextView.setVisibility(View.VISIBLE); - holder.callingListCommandIInfoTextView.setVisibility(View.VISIBLE); - holder.callingUtcTextView.setVisibility(View.VISIBLE); - holder.callingListCallsignToTextView.setVisibility(View.VISIBLE); - holder.callingListCallsignFromTextView.setVisibility(View.VISIBLE); - } - } - - private void setFromDxcc(@NonNull CallingListItemHolder holder) { - - if (holder.ft8Message.fromDxcc && holder.ft8Message.freq_hz > 0.01f) { - holder.dxccFromImageView.setVisibility(View.VISIBLE); - } else { - holder.dxccFromImageView.setVisibility(View.GONE); - } - - if (holder.ft8Message.fromCq && holder.ft8Message.freq_hz > 0.01f) { - holder.cqFromImageView.setVisibility(View.VISIBLE); - } else { - holder.cqFromImageView.setVisibility(View.GONE); - } - - if (holder.ft8Message.fromItu && holder.ft8Message.freq_hz > 0.01f) { - holder.ituFromImageView.setVisibility(View.VISIBLE); - } else { - holder.ituFromImageView.setVisibility(View.GONE); - } - } - - private void setToDxcc(@NonNull CallingListItemHolder holder) { - if (holder.ft8Message.toDxcc && holder.ft8Message.freq_hz > 0.01f) { - holder.dxccToImageView.setVisibility(View.VISIBLE); - } else { - holder.dxccToImageView.setVisibility(View.GONE); - } - - if (holder.ft8Message.toCq && holder.ft8Message.freq_hz > 0.01f) { - holder.cqToImageView.setVisibility(View.VISIBLE); - } else { - holder.cqToImageView.setVisibility(View.GONE); - } - - if (holder.ft8Message.toItu && holder.ft8Message.freq_hz > 0.01f) { - holder.ituToImageView.setVisibility(View.VISIBLE); - } else { - holder.ituToImageView.setVisibility(View.GONE); - } - } - - //检查是不是通联过的呼号 - private void setQueryHolderQSL_Callsign(@NonNull CallingListItemHolder holder) { - //查是不是在本波段内通联成功过的呼号 - if (GeneralVariables.checkQSLCallsign(holder.ft8Message.getCallsignFrom())) {//如果在数据库中,划线 - holder.callListMessageTextView.setPaintFlags( - holder.callListMessageTextView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else {//如果不在数据库中,去掉划线 - holder.callListMessageTextView.setPaintFlags( - holder.callListMessageTextView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); - } - holder.otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(holder.ft8Message.getCallsignFrom()); - } - - @Override - public int getItemCount() { - return ft8MessageArrayList.size(); - } - - public void setOnItemClickListener(View.OnClickListener onItemClickListener) { - this.onItemClickListener = onItemClickListener; - } - - static class CallingListItemHolder extends RecyclerView.ViewHolder { - private static final String TAG = "CallingListItemHolder"; - ConstraintLayout callListHolderConstraintLayout; - TextView callingListIdBTextView, callListDtTextView, callingListFreqTextView, - callListMessageTextView, callingListDistTextView, callingListSequenceTextView, - callingListCallsignFromTextView, callingListCallsignToTextView - , callingListCommandIInfoTextView, - bandItemTextView, callingUtcTextView; - ImageView dxccToImageView, ituToImageView, cqToImageView, dxccFromImageView - , ituFromImageView, cqFromImageView,isWeakSignalImageView; - public Ft8Message ft8Message; - //boolean showFollow; - ShowMode showMode; - boolean isSyncFreq; - boolean otherBandIsQso = false; - - - public CallingListItemHolder(@NonNull View itemView, View.OnClickListener listener - ,View.OnCreateContextMenuListener menuListener) { - super(itemView); - callListHolderConstraintLayout = itemView.findViewById(R.id.callListHolderConstraintLayout); - callingListIdBTextView = itemView.findViewById(R.id.callingListIdBTextView); - callListDtTextView = itemView.findViewById(R.id.callListDtTextView); - callingListFreqTextView = itemView.findViewById(R.id.callingListFreqTextView); - callListMessageTextView = itemView.findViewById(R.id.callListMessageTextView); - callingListDistTextView = itemView.findViewById(R.id.callingListDistTextView); - callingListSequenceTextView = itemView.findViewById(R.id.callingListSequenceTextView); - callingListCallsignFromTextView = itemView.findViewById(R.id.callingListCallsignFromTextView); - callingListCallsignToTextView = itemView.findViewById(R.id.callToItemTextView); - callingListCommandIInfoTextView = itemView.findViewById(R.id.callingListCommandIInfoTextView); - bandItemTextView = itemView.findViewById(R.id.bandItemTextView); - callingUtcTextView = itemView.findViewById(R.id.callingUtcTextView); - - dxccToImageView = itemView.findViewById(R.id.dxccToImageView); - ituToImageView = itemView.findViewById(R.id.ituToImageView); - cqToImageView = itemView.findViewById(R.id.cqToImageView); - dxccFromImageView = itemView.findViewById(R.id.dxccFromImageView); - ituFromImageView = itemView.findViewById(R.id.ituFromImageView); - cqFromImageView = itemView.findViewById(R.id.cqFromImageView); - isWeakSignalImageView=itemView.findViewById(R.id.isWeakSignalImageView); - - dxccToImageView.setVisibility(View.GONE); - ituToImageView.setVisibility(View.GONE); - cqToImageView.setVisibility(View.GONE); - dxccFromImageView.setVisibility(View.GONE); - ituFromImageView.setVisibility(View.GONE); - cqFromImageView.setVisibility(View.GONE); - itemView.setTag(-1); - itemView.setOnClickListener(listener); - itemView.setOnCreateContextMenuListener(menuListener); - - } - - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java deleted file mode 100644 index d561c86..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java +++ /dev/null @@ -1,416 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 解码界面 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.navigation.NavController; -import androidx.navigation.Navigation; -import androidx.navigation.fragment.NavHostFragment; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.databinding.FragmentCallingListBinding; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; - -public class CallingListFragment extends Fragment { - private static final String TAG = "CallingListFragment"; - - private FragmentCallingListBinding binding; - private RecyclerView callListRecyclerView; - private CallingListAdapter callingListAdapter; - private MainViewModel mainViewModel; - - - @SuppressLint("NotifyDataSetChanged") - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - mainViewModel = MainViewModel.getInstance(this); - binding = FragmentCallingListBinding.inflate(inflater, container, false); - callListRecyclerView = binding.callingListRecyclerView; - - callingListAdapter = new CallingListAdapter(this.getContext(), mainViewModel - , mainViewModel.ft8Messages, CallingListAdapter.ShowMode.CALLING_LIST); - callListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - callListRecyclerView.setAdapter(callingListAdapter); - callingListAdapter.notifyDataSetChanged(); - callListRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); - - - requireActivity().registerForContextMenu(callListRecyclerView); - - //当横屏时显示频谱图 - if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - assert binding.spectrumView != null; - binding.spectrumView.run(mainViewModel, this); - } - //设置呼号滑动,用于快速呼叫 - initRecyclerViewAction(); - - //监听按钮 - binding.timerImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (mainViewModel.hamRecorder.isRunning()) { - mainViewModel.hamRecorder.stopRecord(); - mainViewModel.ft8TransmitSignal.setActivated(false); - } else { - mainViewModel.hamRecorder.startRecord(); - } - } - }); - //清除按钮 - binding.clearCallingListImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mainViewModel.clearFt8MessageList(); - callingListAdapter.notifyDataSetChanged(); - mainViewModel.mutable_Decoded_Counter.setValue(0); - } - }); - //观察解码数量 - mainViewModel.mutable_Decoded_Counter.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Integer integer) { - binding.decoderCounterTextView.setText( - String.format(GeneralVariables.getStringFromResource(R.string.message_count_count) - , mainViewModel.currentDecodeCount, mainViewModel.ft8Messages.size())); - - - } - }); - - mainViewModel.mutableFt8MessageList.observe(getViewLifecycleOwner(), new Observer>() { - @Override - public void onChanged(ArrayList messages) { - callingListAdapter.notifyDataSetChanged(); - //当列表下部稍微多出一些,自动上移 - if (callListRecyclerView.computeVerticalScrollRange() - - callListRecyclerView.computeVerticalScrollExtent() - - callListRecyclerView.computeVerticalScrollOffset() < 500) { - callListRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); - } - } - }); - - //观察UTC时间 - mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Long aLong) { - binding.timerTextView.setText(UtcTimer.getTimeStr(aLong)); - } - }); - - //观察时间偏移 - mainViewModel.mutableTimerOffset.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Float aFloat) { - binding.timeOffsetTextView.setText(String.format( - getString(R.string.average_offset_seconds), aFloat)); - } - }); - - //显示梅登海德网格 - GeneralVariables.mutableMyMaidenheadGrid.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(String s) { - binding.maidenheadTextView.setText(String.format( - getString(R.string.my_grid), s)); - } - }); - - //观察是否处于解码状态 - mainViewModel.mutableIsDecoding.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - binding.isDecodingTextView.setText(getString(R.string.decoding)); - } - } - }); - - //观察解码的时长 - mainViewModel.ft8SignalListener.decodeTimeSec.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Long aLong) { - binding.isDecodingTextView.setText(String.format( - getString(R.string.decoding_takes_milliseconds), aLong)); - } - }); - - //以闪烁动画的方式显示录音状态 - mainViewModel.mutableIsRecording.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (aBoolean) { - binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_red_48); - binding.timerImageButton.setAnimation(AnimationUtils.loadAnimation(getContext() - , R.anim.view_blink)); - } else { - if (mainViewModel.hamRecorder.isRunning()) { - binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_48); - } else { - binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_off_48); - } - binding.timerImageButton.setAnimation(null); - } - } - }); - - return binding.getRoot(); - } - - /** - * 设置列表滑动动作 - */ - private void initRecyclerViewAction() { - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG - , ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder - , @NonNull RecyclerView.ViewHolder target) { - return false; - } - - //@RequiresApi(api = Build.VERSION_CODES.N) - @SuppressLint("NotifyDataSetChanged") - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - if (direction == ItemTouchHelper.START) {//呼叫 - Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); - if (message != null) { - //呼叫的目标不能是自己 - if (!message.getCallsignFrom().equals("<...>") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) - && !(message.i3 == 0 && message.n3 == 0)) { - doCallNow(message); - } else { - callingListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); - } - } - } - if (direction == ItemTouchHelper.END) {//删除 - callingListAdapter.deleteMessage(viewHolder.getAdapterPosition()); - callingListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); - } - - - } - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView - , @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY - , int actionState, boolean isCurrentlyActive) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - //制作呼叫背景的图标显示 - final Drawable callIcon = ContextCompat.getDrawable(requireActivity() - , R.drawable.ic_baseline_send_red_48); - final Drawable delIcon = ContextCompat.getDrawable(requireActivity() - , R.drawable.log_item_delete_icon); - final Drawable background = new ColorDrawable(Color.LTGRAY); - Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); - if (message == null) { - return; - } - if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 - return; - } - Drawable icon; - if (dX > 0) { - icon = delIcon; - } else { - icon = callIcon; - } - View itemView = viewHolder.itemView; - int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - int iconLeft, iconRight, iconTop, iconBottom; - int backTop, backBottom, backLeft, backRight; - backTop = itemView.getTop(); - backBottom = itemView.getBottom(); - iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - iconBottom = iconTop + icon.getIntrinsicHeight(); - if (dX > 0) { - backLeft = itemView.getLeft(); - backRight = itemView.getLeft() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconLeft = itemView.getLeft() + iconMargin; - iconRight = iconLeft + icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else if (dX < 0) { - backRight = itemView.getRight(); - backLeft = itemView.getRight() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconRight = itemView.getRight() - iconMargin; - iconLeft = iconRight - icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else { - background.setBounds(0, 0, 0, 0); - icon.setBounds(0, 0, 0, 0); - } - background.draw(c); - icon.draw(c); - - } - }).attachToRecyclerView(binding.callingListRecyclerView); - } - - /** - * 马上对发起者呼叫 - * - * @param message 消息 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - private boolean doCallNow(Ft8Message message) { - - mainViewModel.addFollowCallsign(message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() - , 1, message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - navigateToMyCallFragment();//跳转到发射界面 - return true; - } - - /** - * 跳转到发射界面 - */ - private void navigateToMyCallFragment() { - NavController navController = Navigation.findNavController(requireActivity() - , R.id.fragmentContainerView); - navController.navigate(R.id.action_menu_nav_calling_list_to_menu_nav_mycalling);//跳转到发射界面 - } - - /** - * 菜单选项 - * - * @param item 菜单 - * @return 是否 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public boolean onContextItemSelected(@NonNull MenuItem item) { - - //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); - int position = (int) item.getActionView().getTag(); - Ft8Message ft8Message = callingListAdapter.getMessageByPosition(position); - if (ft8Message == null) return super.onContextItemSelected(item); - ; - switch (item.getItemId()) { - case 0: - Log.d(TAG, "关注:" + ft8Message.getCallsignTo()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignTo()); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - break; - case 1://时序与发送者相反!!! - Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignTo()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - //呼叫被呼叫对象 - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() - , 1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - - navigateToMyCallFragment();//跳转到发射界面 - break; - case 2: - Log.d(TAG, "关注:" + ft8Message.getCallsignFrom()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - break; - case 3: - Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); - doCallNow(ft8Message); - break; - - case 4://回复 - Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() - , -1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - navigateToMyCallFragment();//跳转到发射界面 - break; - case 5://to 的QRZ - showQrzFragment(ft8Message.getCallsignTo()); - break; - case 6://from 的QRZ - showQrzFragment(ft8Message.getCallsignFrom()); - break; - - } - - return super.onContextItemSelected(item); - } - - /** - * 显示QRZ查询界面 - * - * @param callsign 呼号 - */ - private void showQrzFragment(String callsign) { - NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); - assert navHostFragment != null;//断言不为空 - Bundle bundle = new Bundle(); - bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); - navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java deleted file mode 100644 index 86ec2ed..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 清除缓存的对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ScrollView; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.BuildConfig; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.DatabaseOpr; -import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Timer; -import java.util.TimerTask; - -public class ClearCacheDataDialog extends Dialog { - public static enum CACHE_MODE {FOLLOW_DATA, SWL_MSG,SWL_QSO} - - private static final String TAG = "HelpDialog"; - private final Context context; - private final Activity activity; - private ImageView upImageView; - private ImageView downImageView; - private ScrollView scrollView; - private TextView cacheHelpMessage; - private TextView appNameTextView; - private TextView buildVersionTextView; - private CACHE_MODE cache_mode; - private DatabaseOpr db; - private final Timer timer = new Timer(); - - private TimerTask timeEvent() { - return new TimerTask() { - @Override - public void run() { - //心跳动作 - //doHeartBeatEvent(onUtcTimer); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - setImageVisible(); - } - }); - - } - }; - } - - - public ClearCacheDataDialog(@NonNull Context context, Activity activity, DatabaseOpr db, CACHE_MODE cache_mode) { - super(context, R.style.HelpDialog); - this.context = context; - this.activity = activity; - this.cache_mode = cache_mode; - this.db=db; - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.clear_cache_dialog_layout); - cacheHelpMessage = (TextView) findViewById(R.id.cacheHelpMessage); - appNameTextView = (TextView) findViewById(R.id.appNameTextView); - buildVersionTextView = (TextView) findViewById(R.id.buildVersionTextView); - //cacheHelpMessage.setText(msg); - upImageView = (ImageView) findViewById(R.id.scrollUpImageView); - downImageView = (ImageView) findViewById(R.id.scrollDownImageView); - scrollView = (ScrollView) findViewById(R.id.helpScrollView); - upImageView.setVisibility(View.INVISIBLE); - downImageView.setVisibility(View.INVISIBLE); - appNameTextView.setText(GeneralVariables.getStringFromResource(R.string.app_name)); - buildVersionTextView.setText(BuildConfig.VERSION_NAME); - - Button cancelButton=(Button) findViewById(R.id.cancelClearButton); - cancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dismiss(); - } - }); - - StringBuilder msg = new StringBuilder(); - if (cache_mode == CACHE_MODE.FOLLOW_DATA) { - msg.append(GeneralVariables.getStringFromResource(R.string.html_tracking_callsign)); - for (int i = 0; i < GeneralVariables.followCallsign.size(); i++) { - msg.append("\n" + GeneralVariables.followCallsign.get(i)); - } - cacheHelpMessage.setText(msg.toString()); - } - //解码的消息 - if (cache_mode==CACHE_MODE.SWL_MSG){ - db.getMessageLogTotal(new OnAfterQueryFollowCallsigns() { - @Override - public void doOnAfterQueryFollowCallsigns(ArrayList callsigns) { - StringBuilder msg=new StringBuilder(); - msg.append(GeneralVariables.getStringFromResource(R.string.log_statistics_decode_msg)); - for (int i = 0; i callsigns) { - StringBuilder msg=new StringBuilder(); - msg.append(GeneralVariables.getStringFromResource(R.string.log_statistics_swl_qso)); - for (int i = 0; i height) { - params.width = (int) (width * 0.6); - params.height = (int) (height * 0.9); - } else { - params.width = (int) (width * 0.8); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - - } - - - public String getTextFromAssets(String fileName) { - AssetManager assetManager = context.getAssets(); - try { - InputStream inputStream = assetManager.open(fileName); - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - inputStream.close(); - - return new String(bytes); - - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java deleted file mode 100644 index 470feb5..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.bg7yoz.ft8cn.ui; - -import static android.graphics.Bitmap.Config.ARGB_8888; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.view.View; - -import java.util.ArrayList; -import java.util.List; - -/** - * 柱状频谱图。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class ColumnarView extends View { - private static final String TAG = "ColumnarView"; - //每一个能量柱的宽度 - private int width; - //每一个能量柱之间的间距 - private final int spacing = 1; - //能量块高度 - private final int blockHeight = 5; - //能量块下将速度 - private int blockSpeed = 5; - //能量块与能量柱之间的距离 - private final int distance = 2; - - private boolean drawblock = false; - private final Paint paint = new Paint(); - private final List newData = new ArrayList<>(); - private final List blockData = new ArrayList<>(); - - private Bitmap lastBitMap=null; - private Canvas _canvas; - private Paint linePaint; - private int touch_x = -1; - private Paint touchPaint; - private int freq_hz=-1; - - public void setBlockSpeed(int blockSpeed) { - this.blockSpeed = blockSpeed; - } - - public ColumnarView(Context context) { - super(context); - - } - - public ColumnarView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ColumnarView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public void setShowBlock(boolean showBlock) { - drawblock = showBlock; - } - - public void setWaveData(int[] data) { - if (data == null) { - return; - } - if (data.length <= 0) { - return; - } - width = getWidth() / (data.length / 2);// 960/2=480,480比较合理,906无法显示了。 - if (drawblock) {//是否显示能量块 - if (newData.size() > 0) { - if (blockData.size() == 0 || newData.size() != blockData.size()) { - blockData.clear(); - for (int i = 0; i < data.length / 2; i++) { - Rect blockRect = new Rect(); - blockRect.top =getHeight()- blockHeight; - blockRect.bottom = getHeight(); - blockData.add(blockRect); - } - } - for (int i = 0; i < blockData.size(); i++) { - blockData.get(i).left = newData.get(i).left; - blockData.get(i).right = newData.get(i).right; - if (newData.get(i).top < blockData.get(i).top) { - blockData.get(i).top = newData.get(i).top - blockHeight - distance; - } else { - blockData.get(i).top = blockData.get(i).top + blockSpeed; - } - blockData.get(i).bottom = blockData.get(i).top + blockHeight; - } - } - } - newData.clear(); - float rateHeight = 0.95f * getHeight() / 256;//0.95是比率,柱形的最大高度不超过95% - for (int i = 0; i < data.length / 2; i++) { - Rect colRect = new Rect(); - if (newData.size() == 0) { - colRect.left = 0; - } else { - colRect.left = i * getWidth() / (data.length / 2); - } - colRect.top = getHeight() - Math.round(Math.max(data[i], data[i + 1]) * rateHeight); - colRect.right = colRect.left + width - spacing; - colRect.bottom = getHeight(); - newData.add(colRect); - } - } - - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - setClickable(true); - super.onSizeChanged(w, h, oldw, oldh); - - lastBitMap= Bitmap.createBitmap(w,h, ARGB_8888); - _canvas=new Canvas(lastBitMap); - LinearGradient linearGradient=new LinearGradient(0f, 0f, 0f, getHeight(), - new int[]{0xff00ffff,0xff00ffff, Color.BLUE} - , new float[]{0f, 0.6f, 1f}, Shader.TileMode.CLAMP); - paint.setShader(linearGradient); - linePaint = new Paint(); - linePaint.setColor(0xff990000); - touchPaint = new Paint(); - touchPaint.setColor(0xff00ffff); - touchPaint.setStrokeWidth(2); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - _canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - for (int i = 0; i < newData.size(); i++) { - _canvas.drawRect(newData.get(i), paint); - } - if (drawblock) { - for (int i = 0; i < blockData.size(); i++) { - _canvas.drawRect(blockData.get(i), paint); - } - } - canvas.drawBitmap(lastBitMap,0,0,null); - if (touch_x>0) { - //计算频率 - freq_hz = Math.round(3000f * (float) touch_x / (float) getWidth()); - canvas.drawLine(touch_x, 0, touch_x, getHeight(), touchPaint); - } - invalidate(); - } - public void setTouch_x(int touch_x) { - this.touch_x = touch_x; - } - - public int getFreq_hz() { - return freq_hz; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java deleted file mode 100644 index 9e69fa1..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java +++ /dev/null @@ -1,1235 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 设置界面。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.CompoundButton; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; - -import com.bg7yoz.ft8cn.FAQActivity; -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.connector.ConnectMode; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.database.OperationBand; -import com.bg7yoz.ft8cn.database.RigNameList; -import com.bg7yoz.ft8cn.databinding.FragmentConfigBinding; -import com.bg7yoz.ft8cn.ft8signal.FT8Package; -import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; -import com.bg7yoz.ft8cn.rigs.InstructionSet; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.io.IOException; - -/** - * A simple {@link Fragment} subclass. - * create an instance of this fragment. - */ -public class ConfigFragment extends Fragment { - private static final String TAG = "ConfigFragment"; - private MainViewModel mainViewModel; - private FragmentConfigBinding binding; - private BandsSpinnerAdapter bandsSpinnerAdapter; - private BauRateSpinnerAdapter bauRateSpinnerAdapter; - private RigNameSpinnerAdapter rigNameSpinnerAdapter; - private LaunchSupervisionSpinnerAdapter launchSupervisionSpinnerAdapter; - private PttDelaySpinnerAdapter pttDelaySpinnerAdapter; - private NoReplyLimitSpinnerAdapter noReplyLimitSpinnerAdapter; - //private SerialPortSpinnerAdapter serialPortSpinnerAdapter; - - public ConfigFragment() { - // Required empty public constructor - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - //我的网格位置 - private final TextWatcher onGridEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - //String s = ""; - StringBuilder s=new StringBuilder(); - for (int j = 0; j < binding.inputMyGridEdit.getText().length(); j++) { - if (j < 2) { - //s = s + Character.toUpperCase(binding.inputMyGridEdit.getText().charAt(j)); - s.append(Character.toUpperCase(binding.inputMyGridEdit.getText().charAt(j))); - } else { - //s = s + Character.toLowerCase(binding.inputMyGridEdit.getText().charAt(j)); - s.append(Character.toLowerCase(binding.inputMyGridEdit.getText().charAt(j))); - } - } - writeConfig("grid", s.toString()); - GeneralVariables.setMyMaidenheadGrid(s.toString()); - } - }; - //我的呼号 - private final TextWatcher onMyCallEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - writeConfig("callsign", editable.toString().toUpperCase().trim()); - String callsign = editable.toString().toUpperCase().trim(); - if (callsign.length() > 0) { - Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); - } - GeneralVariables.myCallsign = (editable.toString().toUpperCase().trim()); - } - }; - //发射频率 - private final TextWatcher onFreqEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - setfreq(editable.toString()); - } - }; - //发射延迟时间 - private final TextWatcher onTransDelayEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - int transDelay = 1000; - if (editable.toString().matches("^\\d{1,4}$")) { - transDelay = Integer.parseInt(editable.toString()); - } - GeneralVariables.transmitDelay = transDelay; - mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay); - writeConfig("transDelay", Integer.toString(transDelay)); - } - }; - - //排除的呼号前缀 - private final TextWatcher onExcludedCallsigns=new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - GeneralVariables.addExcludedCallsigns(editable.toString()); - writeConfig("excludedCallsigns", GeneralVariables.getExcludeCallsigns()); - } - }; - - //修饰符 - private final TextWatcher onModifierEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - if (editable.toString().toUpperCase().trim().matches("[0-9]{3}|[A-Z]{1,4}") - ||editable.toString().trim().length()==0){ - binding.modifierEdit.setTextColor(requireContext().getColor(R.color.text_view_color)); - GeneralVariables.toModifier=editable.toString().toUpperCase().trim(); - writeConfig("toModifier", GeneralVariables.toModifier); - }else{ - binding.modifierEdit.setTextColor(requireContext().getColor(R.color.text_view_error_color)); - } - } - }; - - //CI-V地址 - private final TextWatcher onCIVAddressEditorChanged = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - if (editable.toString().length() < 2) { - return; - } - String s = "0x" + editable.toString(); - if (s.matches("\\b0[xX][0-9a-fA-F]+\\b")) {//匹配十六进制 - String temp = editable.toString().substring(0, 2).toUpperCase(); - writeConfig("civ", temp); - GeneralVariables.civAddress = Integer.parseInt(temp, 16); - mainViewModel.setCivAddress(); - } - } - }; - - - @SuppressLint("DefaultLocale") - private void setfreq(String sFreq) { - float freq; - try { - freq = Float.parseFloat(sFreq); - if (freq < 100) { - freq = 100; - } - if (freq > 2900) { - freq = 2900; - } - } catch (Exception e - ) { - freq = 1000; - } - - - writeConfig("freq", String.format("%.0f", freq)); - GeneralVariables.setBaseFrequency(freq); - } - - @SuppressLint("SetTextI18n") - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mainViewModel = MainViewModel.getInstance(this); - binding = FragmentConfigBinding.inflate(inflater, container, false); - - //只对中国开方问题收集 -// if (GeneralVariables.isChina) { -// binding.faqButton.setVisibility(View.VISIBLE); -// } else { -// binding.faqButton.setVisibility(View.GONE); -// } - - - //设置时间偏移 - setUtcTimeOffsetSpinner(); - - //设置PTT延时 - setPttDelaySpinner(); - - //设置操作频段 - setBandsSpinner(); - - //设置波特率列表 - setBauRateSpinner(); - - //设置电台名称,参数列表 - setRigNameSpinner(); - - //设置解码模式 - setDecodeMode(); - - //设置音频输出的位数 - setAudioOutputBitsMode(); - - //设置音频输出采样率 - setAudioOutputRateMode(); - - //设置控制模式 VOX CAT - setControlMode(); - - //设置连线的方式 - setConnectMode(); - - //设置发射监管列表 - setLaunchSupervision(); - - //设置帮助对话框 - setHelpDialog(); - - //设置无回应次数中断 - setNoReplyLimitSpinner(); - - //显示滚动箭头 - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - setScrollImageVisible(); - } - }, 1000); - binding.scrollView3.setOnScrollChangeListener(new View.OnScrollChangeListener() { - @Override - public void onScrollChange(View view, int i, int i1, int i2, int i3) { - setScrollImageVisible(); - } - }); - - //FAQ按钮的onClick - binding.faqButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(requireContext(), FAQActivity.class); - startActivity(intent); - } - }); - - //梅登海德网格 - binding.inputMyGridEdit.removeTextChangedListener(onGridEditorChanged); - binding.inputMyGridEdit.setText(GeneralVariables.getMyMaidenheadGrid()); - binding.inputMyGridEdit.addTextChangedListener(onGridEditorChanged); - - //我的呼号 - binding.inputMycallEdit.removeTextChangedListener(onMyCallEditorChanged); - binding.inputMycallEdit.setText(GeneralVariables.myCallsign); - binding.inputMycallEdit.addTextChangedListener(onMyCallEditorChanged); - - //修饰符 - binding.modifierEdit.removeTextChangedListener(onModifierEditorChanged); - binding.modifierEdit.setText(GeneralVariables.toModifier); - binding.modifierEdit.addTextChangedListener(onModifierEditorChanged); - - //发射频率 - binding.inputFreqEditor.removeTextChangedListener(onFreqEditorChanged); - binding.inputFreqEditor.setText(GeneralVariables.getBaseFrequencyStr()); - binding.inputFreqEditor.addTextChangedListener(onFreqEditorChanged); - - - - //CIV地址 - binding.civAddressEdit.removeTextChangedListener(onCIVAddressEditorChanged); - binding.civAddressEdit.setText(GeneralVariables.getCivAddressStr()); - binding.civAddressEdit.addTextChangedListener(onCIVAddressEditorChanged); - - //发射延迟 - binding.inputTransDelayEdit.removeTextChangedListener(onTransDelayEditorChanged); - binding.inputTransDelayEdit.setText(GeneralVariables.getTransmitDelayStr()); - binding.inputTransDelayEdit.addTextChangedListener(onTransDelayEditorChanged); - - binding.excludedCallsignEdit.removeTextChangedListener(onExcludedCallsigns); - binding.excludedCallsignEdit.setText(GeneralVariables.getExcludeCallsigns()); - binding.excludedCallsignEdit.addTextChangedListener(onExcludedCallsigns); - - - //设置同频发射开关 - binding.synFrequencySwitch.setOnCheckedChangeListener(null); - binding.synFrequencySwitch.setChecked(GeneralVariables.synFrequency); - setSyncFreqText();//设置开关的文本 - binding.synFrequencySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - if (binding.synFrequencySwitch.isChecked()) { - mainViewModel.databaseOpr.writeConfig("synFreq", "1", null); - } else { - mainViewModel.databaseOpr.writeConfig("synFreq", "0", null); - setfreq(binding.inputFreqEditor.getText().toString()); - } - GeneralVariables.synFrequency = binding.synFrequencySwitch.isChecked(); - setSyncFreqText(); - binding.inputFreqEditor.setEnabled(!binding.synFrequencySwitch.isChecked()); - } - }); - - //设置PTT延迟 - binding.pttDelayOffsetSpinner.setOnItemSelectedListener(null); - binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); - binding.pttDelayOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.pttDelay = i * 10; - writeConfig("pttDelay", String.valueOf(GeneralVariables.pttDelay)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - - //获取操作的波段 - binding.operationBandSpinner.setOnItemSelectedListener(null); - binding.operationBandSpinner.setSelection(GeneralVariables.bandListIndex); - binding.operationBandSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.bandListIndex = i; - GeneralVariables.band = OperationBand.getBandFreq(i);//把当前的频段保存下来 - - mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 - writeConfig("bandFreq", String.valueOf(GeneralVariables.band)); - if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - //如果在CAT、RTS模式下,修改电台的频率 - mainViewModel.setOperationBand(); - } - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - - //获取电台型号 - binding.rigNameSpinner.setOnItemSelectedListener(null); - binding.rigNameSpinner.setSelection(GeneralVariables.modelNo); - new Handler().postDelayed(new Runnable() {//延迟2秒修改OnItemSelectedListener - @Override - public void run() { - binding.rigNameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.modelNo = i; - writeConfig("model", String.valueOf(i)); - setAddrAndBauRate(rigNameSpinnerAdapter.getRigName(i)); - - //指令集 - GeneralVariables.instructionSet = rigNameSpinnerAdapter.getRigName(i).instructionSet; - writeConfig("instruction", String.valueOf(GeneralVariables.instructionSet)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - } - }); - } - }, 2000); - - - //获取波特率 - binding.baudRateSpinner.setOnItemSelectedListener(null); - binding.baudRateSpinner.setSelection(bauRateSpinnerAdapter.getPosition( - GeneralVariables.baudRate)); - binding.baudRateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.baudRate = bauRateSpinnerAdapter.getValue(i); - writeConfig("baudRate", String.valueOf(GeneralVariables.baudRate)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - } - }); - - //设置发射监管 - binding.launchSupervisionSpinner.setOnItemSelectedListener(null); - binding.launchSupervisionSpinner.setSelection(launchSupervisionSpinnerAdapter - .getPosition(GeneralVariables.launchSupervision)); - binding.launchSupervisionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.launchSupervision = LaunchSupervisionSpinnerAdapter.getTimeOut(i); - writeConfig("launchSupervision", String.valueOf(GeneralVariables.launchSupervision)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - //设置无回应中断 - binding.noResponseCountSpinner.setOnItemSelectedListener(null); - binding.noResponseCountSpinner.setSelection(GeneralVariables.noReplyLimit); - binding.noResponseCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.noReplyLimit = i; - writeConfig("noReplyLimit", String.valueOf(GeneralVariables.noReplyLimit)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - - //设置自动关注CQ - binding.followCQSwitch.setOnCheckedChangeListener(null); - binding.followCQSwitch.setChecked(GeneralVariables.autoFollowCQ); - setAutoFollowCQText(); - binding.followCQSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - GeneralVariables.autoFollowCQ = binding.followCQSwitch.isChecked(); - if (binding.followCQSwitch.isChecked()) { - mainViewModel.databaseOpr.writeConfig("autoFollowCQ", "1", null); - } else { - mainViewModel.databaseOpr.writeConfig("autoFollowCQ", "0", null); - } - setAutoFollowCQText(); - } - }); - - - - //设置自动呼叫关注的呼号 - binding.autoCallfollowSwitch.setOnCheckedChangeListener(null); - binding.autoCallfollowSwitch.setChecked(GeneralVariables.autoCallFollow); - setAutoCallFollow(); - binding.autoCallfollowSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - GeneralVariables.autoCallFollow = binding.autoCallfollowSwitch.isChecked(); - if (binding.autoCallfollowSwitch.isChecked()) { - mainViewModel.databaseOpr.writeConfig("autoCallFollow", "1", null); - } else { - mainViewModel.databaseOpr.writeConfig("autoCallFollow", "0", null); - } - setAutoCallFollow(); - } - }); - - //设置保存SWL选项 - binding.saveSWLSwitch.setOnCheckedChangeListener(null); - binding.saveSWLSwitch.setChecked(GeneralVariables.saveSWLMessage); - setSaveSwl(); - binding.saveSWLSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - GeneralVariables.saveSWLMessage = binding.saveSWLSwitch.isChecked(); - if (binding.saveSWLSwitch.isChecked()) { - mainViewModel.databaseOpr.writeConfig("saveSWL", "1", null); - } else { - mainViewModel.databaseOpr.writeConfig("saveSWL", "0", null); - } - setSaveSwl(); - } - }); - - //设置保存SWL选项 - binding.saveSWLQSOSwitch.setOnCheckedChangeListener(null); - binding.saveSWLQSOSwitch.setChecked(GeneralVariables.saveSWLMessage); - setSaveSwlQSO(); - binding.saveSWLQSOSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - GeneralVariables.saveSWL_QSO = binding.saveSWLQSOSwitch.isChecked(); - if (binding.saveSWLQSOSwitch.isChecked()) { - mainViewModel.databaseOpr.writeConfig("saveSWLQSO", "1", null); - } else { - mainViewModel.databaseOpr.writeConfig("saveSWLQSO", "0", null); - } - setSaveSwlQSO(); - } - }); - - - //获取梅登海德网格 - binding.configGetGridImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - String grid = MaidenheadGrid.getMyMaidenheadGrid(getContext()); - if (!grid.equals("")) { - binding.inputMyGridEdit.setText(grid); - } - } - }); - - - return binding.getRoot(); - } - - /** - * 设置地址和波特率,指令集 - * - * @param rigName 电台型号 - */ - private void setAddrAndBauRate(RigNameList.RigName rigName) { - //mainViewModel.setCivAddress(rigName.address); - GeneralVariables.civAddress = rigName.address; - mainViewModel.setCivAddress(); - GeneralVariables.baudRate = rigName.bauRate; - binding.civAddressEdit.setText(String.format("%X", rigName.address)); - binding.baudRateSpinner.setSelection( - bauRateSpinnerAdapter.getPosition(rigName.bauRate)); - } - - - /** - * 设置同频发射开关的显示文本 - */ - private void setSyncFreqText() { - if (binding.synFrequencySwitch.isChecked()) { - binding.synFrequencySwitch.setText(getString(R.string.freq_syn)); - } else { - binding.synFrequencySwitch.setText(getString(R.string.freq_asyn)); - } - } - - /** - * 设置自动关注CQ开关的文本 - */ - private void setAutoFollowCQText() { - if (binding.followCQSwitch.isChecked()) { - binding.followCQSwitch.setText(getString(R.string.auto_follow_cq)); - } else { - binding.followCQSwitch.setText(getString(R.string.not_concerned_about_CQ)); - } - } - - //设置自动呼叫关注的呼号 - private void setAutoCallFollow() { - if (binding.autoCallfollowSwitch.isChecked()) { - binding.autoCallfollowSwitch.setText(getString(R.string.automatic_call_following)); - } else { - binding.autoCallfollowSwitch.setText(getString(R.string.do_not_call_the_following_callsign)); - } - } - private void setSaveSwl() { - if (binding.saveSWLSwitch.isChecked()) { - binding.saveSWLSwitch.setText(getString(R.string.config_save_swl)); - } else { - binding.saveSWLSwitch.setText(getString(R.string.config_donot_save_swl)); - } - } - private void setSaveSwlQSO() { - if (binding.saveSWLQSOSwitch.isChecked()) { - binding.saveSWLQSOSwitch.setText(getString(R.string.config_save_swl_qso)); - } else { - binding.saveSWLQSOSwitch.setText(getString(R.string.config_donot_save_swl_qso)); - } - } - /** - * 设置UTC时间偏移的spinner - */ - private void setUtcTimeOffsetSpinner() { - UtcOffsetSpinnerAdapter adapter = new UtcOffsetSpinnerAdapter(requireContext()); - - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - binding.utcTimeOffsetSpinner.setAdapter(adapter); - adapter.notifyDataSetChanged(); - binding.utcTimeOffsetSpinner.setSelection((UtcTimer.delay / 100 + 75) / 5); - } - }); - binding.utcTimeOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - UtcTimer.delay = i * 500 - 7500;//设置延迟 - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - } - - /** - * 设置操作频段的spinner - */ - private void setBandsSpinner() { - GeneralVariables.mutableBandChange.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Integer integer) { - binding.operationBandSpinner.setSelection(integer); - } - }); - - - bandsSpinnerAdapter = new BandsSpinnerAdapter(requireContext()); - binding.operationBandSpinner.setAdapter(bandsSpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - bandsSpinnerAdapter.notifyDataSetChanged(); - } - }); - - } - - /** - * 设置波特率列表 - */ - private void setBauRateSpinner() { - bauRateSpinnerAdapter = new BauRateSpinnerAdapter(requireContext()); - binding.baudRateSpinner.setAdapter(bauRateSpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - bauRateSpinnerAdapter.notifyDataSetChanged(); - } - }); - } - - /** - * 设置无回应次数中断 - */ - private void setNoReplyLimitSpinner() { - noReplyLimitSpinnerAdapter = new NoReplyLimitSpinnerAdapter(requireContext()); - binding.noResponseCountSpinner.setAdapter(noReplyLimitSpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - noReplyLimitSpinnerAdapter.notifyDataSetChanged(); - } - }); - } - - /** - * 设置发射监管列表 - */ - private void setLaunchSupervision() { - launchSupervisionSpinnerAdapter = new LaunchSupervisionSpinnerAdapter(requireContext()); - binding.launchSupervisionSpinner.setAdapter(launchSupervisionSpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - launchSupervisionSpinnerAdapter.notifyDataSetChanged(); - } - }); - } - - /** - * 设置电台名称,参数列表 - */ - private void setRigNameSpinner() { - rigNameSpinnerAdapter = new RigNameSpinnerAdapter(requireContext()); - binding.rigNameSpinner.setAdapter(rigNameSpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - rigNameSpinnerAdapter.notifyDataSetChanged(); - } - }); - - } - - /** - * 设置PTT延时 - */ - private void setPttDelaySpinner() { - pttDelaySpinnerAdapter = new PttDelaySpinnerAdapter(requireContext()); - binding.pttDelayOffsetSpinner.setAdapter(pttDelaySpinnerAdapter); - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - pttDelaySpinnerAdapter.notifyDataSetChanged(); - binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); - } - }); - } - - - private void setDecodeMode() { - binding.decodeModeRadioGroup.clearCheck(); - binding.fastDecodeRadioButton.setChecked(!GeneralVariables.deepDecodeMode); - binding.deepDecodeRadioButton.setChecked(GeneralVariables.deepDecodeMode); - - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - int buttonId = binding.decodeModeRadioGroup.getCheckedRadioButtonId(); - GeneralVariables.deepDecodeMode= buttonId ==binding.deepDecodeRadioButton.getId(); - writeConfig("deepMode", GeneralVariables.deepDecodeMode? "1" : "0"); - } - }; - - binding.fastDecodeRadioButton.setOnClickListener(listener); - binding.deepDecodeRadioButton.setOnClickListener(listener); - - } - - - /** - * 设置音频输出的位数 - */ - private void setAudioOutputBitsMode() { - //binding.controlModeRadioGroup.setOnCheckedChangeListener(null); - binding.audioBitsRadioGroup.clearCheck(); - binding.audio32BitsRadioButton.setChecked(GeneralVariables.audioOutput32Bit); - binding.audio16BitsRadioButton.setChecked(!GeneralVariables.audioOutput32Bit); - - - - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - int buttonId = binding.audioBitsRadioGroup.getCheckedRadioButtonId(); - GeneralVariables.audioOutput32Bit= buttonId ==binding.audio32BitsRadioButton.getId(); - writeConfig("audioBits", GeneralVariables.audioOutput32Bit? "1" : "0"); - } - }; - - binding.audio32BitsRadioButton.setOnClickListener(listener); - binding.audio16BitsRadioButton.setOnClickListener(listener); - - } - - /** - * 输出音频的采样率设置 - */ - private void setAudioOutputRateMode() { - binding.audioRateRadioGroup.clearCheck(); - binding.audio12kRadioButton.setChecked(GeneralVariables.audioSampleRate==12000); - binding.audio24kRadioButton.setChecked(GeneralVariables.audioSampleRate==24000); - binding.audio48kRadioButton.setChecked(GeneralVariables.audioSampleRate==48000); - - - - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - if (binding.audio12kRadioButton.isChecked()) GeneralVariables.audioSampleRate=12000; - if (binding.audio24kRadioButton.isChecked()) GeneralVariables.audioSampleRate=24000; - if (binding.audio48kRadioButton.isChecked()) GeneralVariables.audioSampleRate=48000; - writeConfig("audioRate", String.valueOf(GeneralVariables.audioSampleRate)); - } - }; - - binding.audio12kRadioButton.setOnClickListener(listener); - binding.audio24kRadioButton.setOnClickListener(listener); - binding.audio48kRadioButton.setOnClickListener(listener); - - } - - - - /** - * 设置控制模式VOX CAT - */ - private void setControlMode() { - //binding.controlModeRadioGroup.setOnCheckedChangeListener(null); - binding.controlModeRadioGroup.clearCheck(); - - switch (GeneralVariables.controlMode) { - case ControlMode.CAT: - case ConnectMode.NETWORK: - binding.ctrCATradioButton.setChecked(true); - break; - case ControlMode.RTS: - binding.ctrRTSradioButton.setChecked(true); - break; - case ControlMode.DTR: - binding.ctrDTRradioButton.setChecked(true); - break; - default: - binding.ctrVOXradioButton.setChecked(true); - } - - - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - int buttonId = binding.controlModeRadioGroup.getCheckedRadioButtonId(); - - if (buttonId == binding.ctrVOXradioButton.getId()) { - GeneralVariables.controlMode = ControlMode.VOX; - } else if (buttonId == binding.ctrCATradioButton.getId()) {//CAT模式 - GeneralVariables.controlMode = ControlMode.CAT; - } else if (buttonId == binding.ctrRTSradioButton.getId()) {//RTS模式 - GeneralVariables.controlMode = ControlMode.RTS; - } else if (buttonId == binding.ctrDTRradioButton.getId()) {//RTS模式 - GeneralVariables.controlMode = ControlMode.DTR; - } - mainViewModel.setControlMode();//通知一下电台控制模式改变 - //无论CAT还是RTS,CI-V指令还是有效的,都是串口 - if (GeneralVariables.controlMode == ControlMode.CAT - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - if (!mainViewModel.isRigConnected()) { - mainViewModel.getUsbDevice(); - } else { - mainViewModel.setOperationBand(); - } - } - writeConfig("ctrMode", String.valueOf(GeneralVariables.controlMode)); - setConnectMode(); - } - }; - - binding.ctrCATradioButton.setOnClickListener(listener); - binding.ctrVOXradioButton.setOnClickListener(listener); - binding.ctrRTSradioButton.setOnClickListener(listener); - binding.ctrDTRradioButton.setOnClickListener(listener); - } - - /** - * 设置连线的方式,可以是USB,也可以是BLUE_TOOTH - */ - private void setConnectMode() { - if (GeneralVariables.controlMode == ControlMode.CAT - //&& BluetoothConstants.checkBluetoothIsOpen() - ) { - //此处要改成VISIBLE - binding.connectModeLayout.setVisibility(View.VISIBLE); - } else { - binding.connectModeLayout.setVisibility(View.GONE); - } - binding.connectModeRadioGroup.clearCheck(); - switch (GeneralVariables.connectMode) { - case ConnectMode.USB_CABLE: - binding.cableConnectRadioButton.setChecked(true); - break; - case ConnectMode.BLUE_TOOTH: - binding.bluetoothConnectRadioButton.setChecked(true); - break; - case ConnectMode.NETWORK: - binding.networkConnectRadioButton.setChecked(true); - break; - } - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View view) { - int buttonId = binding.connectModeRadioGroup.getCheckedRadioButtonId(); - if (buttonId == binding.cableConnectRadioButton.getId()) { - GeneralVariables.connectMode = ConnectMode.USB_CABLE; - } else if (buttonId == binding.bluetoothConnectRadioButton.getId()) { - GeneralVariables.connectMode = ConnectMode.BLUE_TOOTH; - }else if (buttonId==binding.networkConnectRadioButton.getId()){ - GeneralVariables.connectMode=ConnectMode.NETWORK; - } - //------显示蓝牙列表,并选择,然后建立蓝牙连接 - if (GeneralVariables.connectMode == ConnectMode.BLUE_TOOTH) { - //根据安卓12,要判断一下蓝牙权限: - new SelectBluetoothDialog(requireContext(), mainViewModel).show(); - } - - //-----显示网络上的电台,目前是flex电台,------------------- - if (GeneralVariables.connectMode==ConnectMode.NETWORK){ - //打开网络电台列表对话框 - if (GeneralVariables.instructionSet== InstructionSet.FLEX_NETWORK) { - new SelectFlexRadioDialog(requireContext(), mainViewModel).show(); - }else if(GeneralVariables.instructionSet== InstructionSet.ICOM) { - new LoginIcomRadioDialog(requireContext(), mainViewModel).show(); - }else { - ToastMessage.show(GeneralVariables.getStringFromResource(R.string.only_flex_supported)); - } - } - - } - }; - binding.cableConnectRadioButton.setOnClickListener(listener); - binding.bluetoothConnectRadioButton.setOnClickListener(listener); - binding.networkConnectRadioButton.setOnClickListener(listener); - } - - - /** - * 把配置信息写到数据库 - * - * @param KeyName 关键词 - * @param Value 值 - */ - private void writeConfig(String KeyName, String Value) { - mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); - } - - private void setHelpDialog() { - //呼号帮助 - binding.callsignHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.callsign_help) - , true).show(); - } - }); - //梅登海德网格的帮助 - binding.maidenGridImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.maidenhead_help) - , true).show(); - } - }); - //发射频率的帮助 - binding.frequencyImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.frequency_help) - , true).show(); - } - }); - //延迟发射帮助 - binding.transDelayImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.transDelay_help) - , true).show(); - } - }); - //时间偏移帮助 - binding.timeOffsetImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.timeoffset_help) - , true).show(); - } - }); - //PTT延时帮助 - binding.pttDelayImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.pttdelay_help) - , true).show(); - } - }); - //设置ABOUT - binding.aboutButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity(), "readme.txt", true).show(); - } - }); - //设置操作频段 - binding.operationHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.operationBand_help) - , true).show(); - } - }); - //设置操作模式 - binding.controlModeHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.controlMode_help) - , true).show(); - } - }); - //设置CI-V地址和波特率帮助 - binding.baudRateHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.civ_help) - , true).show(); - } - }); - //电台型号列表 - binding.rigNameHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.rig_model_help) - , true).show(); - } - }); - //发射监管 - binding.launchSupervisionImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.launch_supervision_help) - , true).show(); - } - }); - //无回应次数 - binding.noResponseCountButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.no_response_help) - , true).show(); - } - }); - //自动呼叫 - binding.autoFollowCountButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.auto_follow_help) - , true).show(); - } - }); - //连接模式 - binding.connectModeHelpImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.connectMode_help) - , true).show(); - } - }); - //排除选项 - binding.excludedHelpButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.excludeCallsign_help) - , true).show(); - } - }); - - binding.swlHelpButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.swlMode_help) - , true).show(); - } - }); - - //解码模式 - binding.decodeModeHelpButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(),requireActivity() - ,GeneralVariables.getStringFromResource(R.string.deep_mode_help) - ,true).show(); - } - }); - - //音频输出帮助 - binding.audioOutputImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.audio_output_help) - , true).show(); - } - }); - - //清除缓存 - binding.clearCacheHelpButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.clear_cache_data_help) - , true).show(); - } - }); - binding.clearFollowButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new ClearCacheDataDialog(requireContext(), requireActivity() - ,mainViewModel.databaseOpr - ,ClearCacheDataDialog.CACHE_MODE.FOLLOW_DATA).show(); - } - }); - binding.clearLogCacheButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new ClearCacheDataDialog(requireContext(), requireActivity() - ,mainViewModel.databaseOpr - ,ClearCacheDataDialog.CACHE_MODE.SWL_MSG).show(); - } - }); - binding.clearSWlQsoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new ClearCacheDataDialog(requireContext(), requireActivity() - ,mainViewModel.databaseOpr - ,ClearCacheDataDialog.CACHE_MODE.SWL_QSO).show(); - } - }); - - binding.synTImeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - UtcTimer.syncTime(new UtcTimer.AfterSyncTime() { - @Override - public void doAfterSyncTimer(int secTime) { - setUtcTimeOffsetSpinner(); - if (secTime>100) {//正数时慢了 - ToastMessage.show(String.format(GeneralVariables - .getStringFromResource(R.string.utc_time_sync_delay_slow), secTime)); - }else if (secTime<-100){ - ToastMessage.show(String.format(GeneralVariables - .getStringFromResource(R.string.utc_time_sync_delay_faster), -secTime)); - }else { - ToastMessage.show(GeneralVariables - .getStringFromResource(R.string.config_clock_is_accurate)); - } - } - - @Override - public void syncFailed(IOException e) { - ToastMessage.show(e.getMessage()); - } - }); - - } - }); - - } - - /** - * 设置界面的上下滚动的图标 - */ - private void setScrollImageVisible() { - - if (binding.scrollView3.getScrollY() == 0) { - binding.configScrollUpImageView.setVisibility(View.GONE); - } else { - binding.configScrollUpImageView.setVisibility(View.VISIBLE); - } - - if (binding.scrollView3.getHeight() + binding.scrollView3.getScrollY() - < binding.scrollLinearLayout.getMeasuredHeight()) { - binding.configScrollDownImageView.setVisibility(View.VISIBLE); - } else { - binding.configScrollDownImageView.setVisibility(View.GONE); - } - } - - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java deleted file mode 100644 index 917bc4f..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 日志查询的过滤对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.widget.RadioButton; - -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; - -public class FilterDialog extends Dialog { - private static final String TAG = "FilterDialog"; - - private MainViewModel mainViewModel; - private RadioButton filterAllButton,filterIsQslButton,filterNoneQslButton; - - public FilterDialog(Context context,MainViewModel mainViewModel) { - super(context, R.style.HelpDialog); - this.mainViewModel=mainViewModel; - - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.filter_dialog_layout); - filterAllButton= (RadioButton) findViewById(R.id.filterAllRadioButton); - filterIsQslButton= (RadioButton) findViewById(R.id.filterIsQSLRadioButton); - filterNoneQslButton= (RadioButton) findViewById(R.id.filterNoneQSLRadioButton); - - View.OnClickListener onClickListener=new View.OnClickListener() { - @Override - public void onClick(View view) { - //Log.e(TAG, "onClick: ---------------->" ); - if (filterAllButton.isChecked()){ - mainViewModel.queryFilter=0; - mainViewModel.mutableQueryFilter.postValue(0); - } - if (filterIsQslButton.isChecked()){ - mainViewModel.queryFilter=1; - mainViewModel.mutableQueryFilter.postValue(1); - } - if (filterNoneQslButton.isChecked()){ - mainViewModel.queryFilter=2; - mainViewModel.mutableQueryFilter.postValue(2); - } - FilterDialog.this.dismiss(); - } - }; - filterAllButton.setOnClickListener(onClickListener); - filterIsQslButton.setOnClickListener(onClickListener); - filterNoneQslButton.setOnClickListener(onClickListener); - } - - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); - params.height = (int) (height * 0.6); - if (width>height) { - params.width = (int) (width * 0.6); - params.height = (int) (height * 0.6); - }else { - params.width= (int) (width * 0.8); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - switch (mainViewModel.queryFilter){ - case 1: - filterIsQslButton.setChecked(true);//只显示QSL的 - break; - case 2: - filterNoneQslButton.setChecked(true);//只显示没有QSL的 - break; - default: - filterAllButton.setChecked(true);//显示全部 - } - } - - - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java deleted file mode 100644 index eca1512..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * flexRadio仪表的自定义控件。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; - -import androidx.annotation.Nullable; - -public class FlexMeterRulerView extends View { - private static final String TAG = "FlexMeterRulerView"; - - private String label="S/Po"; - private String unit="dBm"; - private String valueLabel="5dBm"; - private float lowVal=-150f; - private float highVal=-72f; - private float maxVal=10f; - private int normalCount=9; - private int highCount=3; - private String[] normalLabels=new String[]{"0","1","2","3","4","5","6","7","8","9"}; - private String[] highLabels=new String[]{"20","40","50"}; - private float value=5f; - - - private final int labelDp=12;//标签字体大小dp - private Rect rulerRect = new Rect(); - private Rect valueRect =new Rect(); - private Paint fontPaint = new Paint(); - private Paint rulerPaint = new Paint(); - private Paint valuePaint = new Paint(); - private Paint labelPaint=new Paint(); - private int rulerWidth = getWidth(); - private int labelWidth=dpToPixel(40);//标签宽度 - private int valueWidth=dpToPixel(65);//值标签宽度 - - @SuppressLint("DefaultLocale") - public void setValue(float value) { - valueLabel=String.format("%.1f%s",value,unit); - if (value>maxVal) { - this.value = maxVal; - }else if(value() { - @Override - public void onChanged(FlexMeterList meters) { - //binding.flexInfoTextView.setText(String.format("%f",meters.alcVal)); - binding.sMeterRulerView.setValue(meters.sMeterVal); - binding.alcMeterRulerView.setValue(meters.alcVal); - binding.swrMeterRulerView.setValue(meters.swrVal); - binding.powerMeterRulerView.setValue(meters.pwrVal); - binding.tempMeterRulerView.setValue(meters.tempCVal); - - binding.flexInfoTextView.setText(meters.getMeters()); - } - }); - } - - - binding.sMeterRulerView.initVal(-150f, -72f, 10f, 9, 3); - binding.sMeterRulerView.initLabels("S.Po", "dBm" - , new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} - , new String[]{"20", "40", ""}); - - binding.swrMeterRulerView.initVal(1f, 3f, 20f, 4, 4); - binding.swrMeterRulerView.initLabels("SWR", "" - , new String[]{"1", "1.5", "2", "2.5", "3"} - , new String[]{"", "", "", "∞"}); - - binding.alcMeterRulerView.initVal(-150f, 0f, 20f, 3, 3); - binding.alcMeterRulerView.initLabels("ALC", "dBm" - , new String[]{"", "", ""} - , new String[]{"", "", ""}); - - binding.powerMeterRulerView.initVal(-0f, 50f, 100f, 5, 5); - binding.powerMeterRulerView.initLabels("PWR", "W" - , new String[]{"0", "10", "20", "30", "40", "50"} - , new String[]{"60", "70", "80", "90", "100"}); - - binding.tempMeterRulerView.initVal(-0f, 80f, 100f, 8, 2); - binding.tempMeterRulerView.initLabels("Temp", "°c" - , new String[]{"0", "10", "20", "30", "40", "50", "60", "70", "80"} - , new String[]{"90", "100"}); - - - binding.maxPowerProgress.setValueColor(getContext().getColor(R.color.power_progress_value)); - binding.tunePowerProgress.setValueColor(getContext().getColor(R.color.power_progress_value)); - binding.maxPowerProgress.setRadarColor(getContext().getColor(R.color.power_progress_radar_value)); - binding.tunePowerProgress.setRadarColor(getContext().getColor(R.color.power_progress_radar_value)); - - binding.maxPowerProgress.setAlarmValue(0.51f); - - - binding.maxPowerProgress.setPercent(((float) connector.maxRfPower) / 100f); - binding.maxPowerSeekBar.setProgress(connector.maxRfPower); - binding.maxTxPowerTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), connector.maxRfPower)); - - binding.tunePowerProgress.setPercent(((float) connector.maxTunePower) / 100f); - binding.tunePowerSeekBar.setProgress(connector.maxTunePower); - binding.tunePowerProgress.setAlarmValue((connector.maxTunePower / 100f) + 0.01f); - binding.tunePowerTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.flex_tune_power), connector.maxTunePower)); - - binding.maxPowerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - binding.maxPowerProgress.setPercent(i * 1.0f / 100); - connector.maxRfPower = i; - connector.setMaxRfPower(i); - binding.maxTxPowerTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), i)); - - binding.tunePowerProgress.setAlarmValue((i / 100f) + 0.02f); - setTuneProgress(); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - }); - binding.tunePowerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - connector.maxTunePower=i; - binding.tunePowerProgress.setPercent(seekBar.getProgress() * 1.0f / 100); - binding.tunePowerTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.flex_tune_power) - , connector.maxTunePower)); - - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - setTuneProgress(); - } - }); - - - binding.autStartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - connector.startATU(); - } - }); - binding.pttOnButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - connector.tuneOnOff(true); - } - }); - binding.pttOffButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - connector.tuneOnOff(false); - } - }); - - return binding.getRoot(); - } - - private void setTuneProgress() { - //binding.tunePowerSeekBar.setProgress(connector.maxRfPower); - if (connector.maxTunePower>connector.maxRfPower) { - connector.maxTunePower =connector.maxRfPower; - } - binding.tunePowerTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.flex_tune_power) - , connector.maxTunePower)); - binding.tunePowerProgress.setPercent(connector.maxTunePower * 1.0f / 100); - - binding.tunePowerSeekBar.setProgress(connector.maxTunePower); - connector.setMaxTunePower(connector.maxTunePower); - - mainViewModel.databaseOpr.writeConfig("flexMaxRfPower" - ,String.valueOf(connector.maxRfPower),null); - mainViewModel.databaseOpr.writeConfig("flexMaxTunePower" - ,String.valueOf(connector.maxTunePower),null); - } - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java deleted file mode 100644 index 7fc6f0c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 快速切换频率的对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.ControlMode; -import com.bg7yoz.ft8cn.database.OperationBand; - -public class FreqDialog extends Dialog { - private static final String TAG = "FreqDialog"; - - private MainViewModel mainViewModel; - private RecyclerView freqRecyclerView; - private FreqAdapter freqAdapter; - //private BandsSpinnerAdapter bandsSpinnerAdapter; - - - public FreqDialog(Context context, MainViewModel mainViewModel) { - super(context, R.style.HelpDialog); - this.mainViewModel=mainViewModel; - - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.freq_dialog_layout); - freqRecyclerView=(RecyclerView) findViewById(R.id.freqDialogRecyclerView); - freqRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - - freqAdapter = new FreqAdapter(); - freqRecyclerView.setAdapter(freqAdapter); - - freqRecyclerView.scrollToPosition(OperationBand.getIndexByFreq(GeneralVariables.band)); -// -// View.OnClickListener onClickListener=new View.OnClickListener() { -// @Override -// public void onClick(View view) { -// FreqDialog.this.dismiss(); -// } -// }; - - } - - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); - params.height = (int) (height * 0.6); - if (width>height) { - params.width = (int) (width * 0.5); - params.height = (int) (height * 0.6); - }else { - params.width= (int) (width * 0.6); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - - } - - public class FreqAdapter extends RecyclerView.Adapter{ - - - @NonNull - @Override - public FreqHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.operation_band_dialog_item, parent, false); - final FreqHolder freqHolder=new FreqHolder(view); - return freqHolder; - } - - @Override - public void onBindViewHolder(@NonNull FreqHolder holder, int position) { - holder.band=OperationBand.getBandFreq(position); - //holder.index=position; - holder.operationDialogBandItemTextView.setText(OperationBand.getBandInfo(position)); - if (holder.band==GeneralVariables.band){ - holder.operationDialogBandConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_3_style); - }else { - holder.operationDialogBandConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_style); - } - holder.operationDialogBandConstraintLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(holder.band); - GeneralVariables.band = holder.band; - - mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 - mainViewModel.databaseOpr.writeConfig("bandFreq" - , String.valueOf(GeneralVariables.band) - , null); - if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - //如果在CAT、RTS模式下,修改电台的频率 - mainViewModel.setOperationBand(); - } - dismiss(); - } - }); - - - //OperationBand.bandList.get(i) - } - - @Override - public int getItemCount() { - return OperationBand.bandList.size(); - } - - class FreqHolder extends RecyclerView.ViewHolder{ - long band; - - TextView operationDialogBandItemTextView; - ConstraintLayout operationDialogBandConstraintLayout; - public FreqHolder(@NonNull View itemView) { - super(itemView); - operationDialogBandItemTextView=itemView.findViewById(R.id.operationDialogBandItemTextView); - operationDialogBandConstraintLayout=itemView.findViewById(R.id.operationDialogBandConstraintLayout); - } - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java deleted file mode 100644 index 21443e9..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * FT8通联的6步列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.ft8transmit.FunctionOfTransmit; - -public class FunctionOrderSpinnerAdapter extends BaseAdapter { - private Context mContext; - private MainViewModel mainViewModel; - - public FunctionOrderSpinnerAdapter(Context context, MainViewModel mainViewModel) { - this.mainViewModel = mainViewModel; - mContext = context; - } - - @Override - public int getCount() { - return mainViewModel.ft8TransmitSignal.functionList.size(); - } - - @Override - public Object getItem(int i) { - return mainViewModel.ft8TransmitSignal.functionList.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater = LayoutInflater.from(mContext); - FunctionOfTransmit function; - function = mainViewModel.ft8TransmitSignal.functionList.get(i); - - view = _LayoutInflater.inflate(R.layout.function_order_spinner_item, null); - if (view != null) { - TextView messageTextView = (TextView) view.findViewById(R.id.functionOrderItemTextView); - messageTextView.setText(function.getFunctionMessage()); - TextView numTextView = (TextView) view.findViewById(R.id.functionNumItemTextView); - numTextView.setText(String.valueOf(function.getFunctionOrder())); - -// ImageView completedImageView = (ImageView) view.findViewById(R.id.functionCompletedImageView); - - ImageView currentImageView=(ImageView) view.findViewById(R.id.currentOrderImageView); - if (function.isCurrentOrder()){ - currentImageView.setVisibility(View.VISIBLE); - }else { - currentImageView.setVisibility(View.INVISIBLE); - } - -// if (function.isCompleted()) { -// completedImageView.setVisibility(View.VISIBLE); -// } else { -// completedImageView.setVisibility(View.GONE); -// } - - } - return view; - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java deleted file mode 100644 index 24bc1bf..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 帮助信息的对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.content.res.AssetManager; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ScrollView; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.BuildConfig; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Timer; -import java.util.TimerTask; - -public class HelpDialog extends Dialog { - private static final String TAG = "HelpDialog"; - private final Context context; - private final Activity activity; - private final String msg; - private ImageView upImageView; - private ImageView downImageView; - private ScrollView scrollView; - private TextView messageTextView; - private TextView appNameTextView; - private TextView buildVersionTextView; - private final Timer timer=new Timer(); - private TimerTask timeEvent() { - return new TimerTask() { - @Override - public void run() { - //心跳动作 - //doHeartBeatEvent(onUtcTimer); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - setImageVisible(); - } - }); - - } - }; - } - - - - - public HelpDialog(@NonNull Context context,Activity activity,String str, boolean fromFile) { - super(context, R.style.HelpDialog); - this.context = context; - this.activity=activity; - if (fromFile) { - msg = getTextFromAssets(str); - } else { - msg = str; - } - - } - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.help_dialog_layout); - messageTextView = (TextView) findViewById(R.id.helpMessage); - appNameTextView = (TextView) findViewById(R.id.appNameTextView); - buildVersionTextView = (TextView) findViewById(R.id.buildVersionTextView); - messageTextView.setText(msg); - upImageView = (ImageView) findViewById(R.id.scrollUpImageView); - downImageView = (ImageView) findViewById(R.id.scrollDownImageView); - scrollView = (ScrollView) findViewById(R.id.helpScrollView); - upImageView.setVisibility(View.INVISIBLE); - downImageView.setVisibility(View.INVISIBLE); - appNameTextView.setText(GeneralVariables.getStringFromResource(R.string.app_name)); - buildVersionTextView.setText(BuildConfig.VERSION_NAME); - - scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { - @Override - public void onScrollChange(View view, int i, int i1, int i2, int i3) { - Log.d(TAG, String.format("onCreate: getMeasuredHeight:%d, getHeight:%d scroll height:%d" - , messageTextView.getMeasuredHeight(), messageTextView.getHeight(),scrollView.getHeight())); - - - } - }); - - Button getNewButton=(Button) findViewById(R.id.getNewVersionButton); - getNewButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - jumpUriToBrowser(context,"https://github.com/N0BOY/FT8CN/releases"); - } - }); - - timer.schedule(timeEvent(), 10, 500); - } - - private void jumpUriToBrowser(Context context,String url){ - Intent intent=new Intent(); - intent.setAction(Intent.ACTION_VIEW); - Uri uri=Uri.parse(url); - intent.setData(uri); - context.startActivity(intent); - } - - private void setImageVisible(){ - if (scrollView.getScrollY() == 0) { - upImageView.setVisibility(View.INVISIBLE); - } else { - upImageView.setVisibility(View.VISIBLE); - } - - if (scrollView.getMeasuredHeight() <= messageTextView.getMeasuredHeight()-scrollView.getScrollY()) { - downImageView.setVisibility(View.VISIBLE); - } else { - downImageView.setVisibility(View.INVISIBLE); - } - } - - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); - params.height = (int) (height * 0.6); - if (width>height) { - params.width = (int) (width * 0.6); - params.height = (int) (height * 0.9); - }else { - params.width= (int) (width * 0.8); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - - } - - - - public String getTextFromAssets(String fileName) { - AssetManager assetManager = context.getAssets(); - try { - InputStream inputStream = assetManager.open(fileName); - byte[] bytes = new byte[inputStream.available()]; - inputStream.read(bytes); - inputStream.close(); - - return new String(bytes); - - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java deleted file mode 100644 index f05a4b2..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 发射监管的列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.util.ArrayList; -import java.util.List; - -public class LaunchSupervisionSpinnerAdapter extends BaseAdapter { - private final List timeOutList=new ArrayList<>(); - private final Context mContext; - - public LaunchSupervisionSpinnerAdapter(Context context) { - mContext=context; - timeOutList.add(0); - for (int i = 1; i <= 10; i++) { - timeOutList.add(i*10-5); - } - } - public static int getTimeOut(int index){ - if (index==0) return 0; - return ((index+1) * 10-5) * 60 * 1000; - } - - @Override - public int getCount() { - return timeOutList.size(); - } - - @Override - public Object getItem(int i) { - return timeOutList.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.launch_supervision_spinner_item, null); - if (view!=null){ - TextView textView=(TextView)view.findViewById(R.id.timeOutTextView); - if (i==0){ - textView.setText( - GeneralVariables.getStringFromResource(R.string.launch_supervision_ignore)); - }else { - textView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.minutes), timeOutList.get(i))); - } - } - return view; - } - - public int getPosition(int timeOut){ - if (timeOut==0){ - return 0; - }else if (timeOut<5*60*1000) { - return 1; - }else { - return ((timeOut-5*60*1000)/60/1000/10); - } - - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java deleted file mode 100644 index ad1ab12..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java +++ /dev/null @@ -1,545 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 通联纪录的主界面。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import static android.widget.AbsListView.OnScrollListener.SCROLL_STATE_IDLE; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.navigation.fragment.NavHostFragment; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.databinding.FragmentLogBinding; -import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity; -import com.bg7yoz.ft8cn.html.LogHttpServer; -import com.bg7yoz.ft8cn.log.LogCallsignAdapter; -import com.bg7yoz.ft8cn.log.LogQSLAdapter; -import com.bg7yoz.ft8cn.log.OnQueryQSLCallsign; -import com.bg7yoz.ft8cn.log.OnQueryQSLRecordCallsign; -import com.bg7yoz.ft8cn.log.QSLCallsignRecord; -import com.bg7yoz.ft8cn.log.QSLRecordStr; - -import java.io.Serializable; -import java.util.ArrayList; - -/** - * A simple {@link Fragment} subclass. - * Use the {@link LogFragment#newInstance} factory method to - * create an instance of this fragment. - */ -public class LogFragment extends Fragment { - private static final String TAG = "LogFragment"; - private FragmentLogBinding binding; - private MainViewModel mainViewModel; - - private LogCallsignAdapter logCallsignAdapter; - private LogQSLAdapter logQSLAdapter; - private boolean loading = false;//防止滑动触发多次查询 - private int lastItemPosition; - - - public LogFragment() { - // Required empty public constructor - } - - - public static LogFragment newInstance(String param1, String param2) { - LogFragment fragment = new LogFragment(); - - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mainViewModel = MainViewModel.getInstance(this); - - } - - @SuppressLint({"DefaultLocale", "NotifyDataSetChanged"}) - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - binding = FragmentLogBinding.inflate(getLayoutInflater()); - - logCallsignAdapter = new LogCallsignAdapter(requireContext(), mainViewModel); - logQSLAdapter = new LogQSLAdapter(requireContext(), mainViewModel); - binding.logRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); - - - setShowStyle();//设置显模式 - - - initRecyclerViewAction();//设置列表滑动动作 - - - //设置显示统计页面按钮 - binding.countImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - showCountFragment(); - } - }); - - binding.inputMycallEdit.setText(mainViewModel.queryKey); - queryByCallsign(mainViewModel.queryKey, 0); - - mainViewModel.mutableQueryFilter.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Integer integer) { - queryByCallsign(mainViewModel.queryKey, 0); - } - }); - - //输入条件监听 - binding.inputMycallEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - mainViewModel.queryKey = editable.toString(); - queryByCallsign(mainViewModel.queryKey, 0); - } - }); - - //过滤条件按钮 - binding.filterImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - new FilterDialog(requireContext(), mainViewModel).show(); - } - }); - - //导出按钮 - binding.exportImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (getLocalIp()==null) { - new HelpDialog(requireContext(), requireActivity() - , GeneralVariables.getStringFromResource(R.string.export_null) - ,false).show(); - }else { - new HelpDialog(requireContext(), requireActivity() - , String.format(GeneralVariables.getStringFromResource(R.string.export_info) - , getLocalIp(), LogHttpServer.DEFAULT_PORT) - , false).show(); - } - - } - }); - - binding.logViewStyleimageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mainViewModel.logListShowCallsign = !mainViewModel.logListShowCallsign; - setShowStyle(); - queryByCallsign(binding.inputMycallEdit.getText().toString(), 0);//偏移量0,就是重新查询 - } - }); - - //定位按钮的动作 - binding.locationInMapImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); - intent.putExtra("qslAll", mainViewModel.queryKey); - intent.putExtra("queryFilter", mainViewModel.queryFilter); - startActivity(intent); - } - }); - - return binding.getRoot(); - } - - - /** - * 弹出菜单选项 - * - * @param item item - * @return item - */ - @Override - public boolean onContextItemSelected(@NonNull MenuItem item) { - int position = (Integer) item.getActionView().getTag(); - if (!mainViewModel.logListShowCallsign) { - switch (item.getItemId()) { - case 0: - logQSLAdapter.setRecordIsQSL(position, false); - logQSLAdapter.notifyItemChanged(position); - break; - case 1: - logQSLAdapter.setRecordIsQSL(position, true); - logQSLAdapter.notifyItemChanged(position); - break; - case 2: - showQrzFragment(logQSLAdapter.getRecord(position).getCall()); - break; - case 3: - Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); - //ArrayList records=new ArrayList<>(); - //records.add(logQSLAdapter.getRecord(position)); - intent.putExtra("qslList", logQSLAdapter.getRecord(position)); - startActivity(intent); - break; - - } - } else { - if (item.getItemId() == 2) { - showQrzFragment(logCallsignAdapter.getRecord(position).getCallsign()); - } - } - - return super.onContextItemSelected(item); - } - - private boolean itemIsOnScreen(View view) { - if (view != null) { - int width = view.getWidth(); - int height = view.getHeight(); - Rect rect = new Rect(0, 0, width, height); - return view.getLocalVisibleRect(rect); - } - return false; - } - - private void loadQueryData(RecyclerView recyclerView) { -// if ((!loading)&&(recyclerView.computeVerticalScrollRange() -// - recyclerView.computeVerticalScrollExtent() -// - recyclerView.computeVerticalScrollOffset() < 100)) { - if ((!loading)) { - //ToastMessage.show("查询"); - if (mainViewModel.logListShowCallsign) { - queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); - } else { - queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); - } - } - } - - /** - * 设置列表滑动动作 - */ - private void initRecyclerViewAction() { - - binding.logRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - super.onScrollStateChanged(recyclerView, newState); - - int itemCount; - if (mainViewModel.logListShowCallsign) { - itemCount = logCallsignAdapter.getItemCount(); - } else { - itemCount = logQSLAdapter.getItemCount(); - } - if (newState == SCROLL_STATE_IDLE && - lastItemPosition == itemCount) { - loadQueryData(recyclerView); - - } - } - - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); - if (layoutManager instanceof LinearLayoutManager) { - LinearLayoutManager manager = (LinearLayoutManager) layoutManager; - int firstVisibleItem = manager.findFirstVisibleItemPosition(); - int l = manager.findLastCompletelyVisibleItemPosition(); - lastItemPosition = firstVisibleItem + (l - firstVisibleItem) + 1; - - } - -// if (dy>0){//列表向上移动 -// loadQueryData(recyclerView); -// } - //当列表上滑,接近底部时,开始查询 -// if ((!loading)&&(dy>0)&&(recyclerView.computeVerticalScrollRange() -// - recyclerView.computeVerticalScrollExtent() -// - recyclerView.computeVerticalScrollOffset() < 100)) { -// ToastMessage.show("查询"); -// if (mainViewModel.logListShowCallsign){ -// queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); -// }else { -// queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); -// } -// } - } - }); - - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG - , ItemTouchHelper.END | ItemTouchHelper.START) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView - , @NonNull RecyclerView.ViewHolder viewHolder - , @NonNull RecyclerView.ViewHolder target) { - return false; - } - - @SuppressLint("NotifyDataSetChanged") - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - if (direction == ItemTouchHelper.END) { - if (mainViewModel.logListShowCallsign) {//此时是显示QSL呼号日志 - //logCallsignAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); - } else { - //做一个是否删除确认对话框 - AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); - builder.setIcon(null); - builder.setTitle(GeneralVariables.getStringFromResource(R.string.delete_confirmation)); - builder.setMessage(GeneralVariables.getStringFromResource(R.string.are_you_sure_delete)); - builder.setPositiveButton(GeneralVariables.getStringFromResource(R.string.ok_confirmed) - , new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - logQSLAdapter.deleteRecord(viewHolder.getAdapterPosition());//删除日志 - logQSLAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); - } - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - logQSLAdapter.notifyDataSetChanged(); - } - }); - builder.setNegativeButton(GeneralVariables.getStringFromResource(R.string.cancel) - , new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - logQSLAdapter.notifyDataSetChanged(); - } - }).show(); - - } - } - - if (direction == ItemTouchHelper.START) { - if (mainViewModel.logListShowCallsign) {//修改手工确认 - //logCallsignAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); - } else { - logQSLAdapter.setRecordIsQSL(viewHolder.getAdapterPosition() - , !logQSLAdapter.getRecord(viewHolder.getAdapterPosition()).isQSL); - logQSLAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); - } - } - } - - //判断列表格式,呼号列表 - @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView - , @NonNull RecyclerView.ViewHolder viewHolder) { - int swipeFlag; - if (mainViewModel.logListShowCallsign) { - swipeFlag = 0; - } else { - swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; - } - return makeMovementFlags(0, swipeFlag); - } - - //制作删除背景的图标显示 - Drawable delIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.log_item_delete_icon); - Drawable qslIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.ic_baseline_library_add_check_24); - Drawable background = new ColorDrawable(Color.LTGRAY); - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView - , @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY - , int actionState, boolean isCurrentlyActive) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - Drawable icon; - View itemView = viewHolder.itemView; - if (dX > 0) { - icon = delIcon; - } else { - icon = qslIcon; - } - - int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - int iconLeft, iconRight, iconTop, iconBottom; - int backTop, backBottom, backLeft, backRight; - backTop = itemView.getTop(); - backBottom = itemView.getBottom(); - iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - iconBottom = iconTop + icon.getIntrinsicHeight(); - if (dX > 0) { - backLeft = itemView.getLeft(); - backRight = itemView.getLeft() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconLeft = itemView.getLeft() + iconMargin; - iconRight = iconLeft + icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else if (dX < 0) { - backRight = itemView.getRight(); - backLeft = itemView.getRight() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconRight = itemView.getRight() - iconMargin; - iconLeft = iconRight - icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else { - background.setBounds(0, 0, 0, 0); - icon.setBounds(0, 0, 0, 0); - } - background.draw(c); - icon.draw(c); - - } - }).attachToRecyclerView(binding.logRecyclerView); - } - - - /** - * 设置显示模式。通联的呼号和日志两种表现方式 - */ - @SuppressLint("NotifyDataSetChanged") - private void setShowStyle() { - - if (mainViewModel.logListShowCallsign) { - binding.logViewStyleimageButton.setImageResource(R.drawable.ic_baseline_assignment_ind_24); - binding.logRecyclerView.setAdapter(logCallsignAdapter); - logCallsignAdapter.notifyDataSetChanged(); - binding.locationInMapImageButton.setVisibility(View.GONE);//隐藏定位按钮 - } else { - binding.logViewStyleimageButton.setImageResource(R.drawable.ic_baseline_assignment_24); - binding.logRecyclerView.setAdapter(logQSLAdapter); - logQSLAdapter.notifyDataSetChanged(); - binding.locationInMapImageButton.setVisibility(View.VISIBLE);//显示定位按钮 - } - - } - - /** - * 查询日志 - * - * @param callsign 呼号 - */ - private void queryByCallsign(String callsign, int offset) { - loading = true;//开始读数据 - //分两种查询 - if (mainViewModel.logListShowCallsign) { - if (offset == 0) {//说明是新增记录 - logCallsignAdapter.clearRecords();//清空记录 - } - - mainViewModel.databaseOpr.getQSLCallsignsByCallsign(false, offset, callsign, mainViewModel.queryFilter - , new OnQueryQSLCallsign() { - @Override - public void afterQuery(ArrayList records) { - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - logCallsignAdapter.setQSLCallsignList(records); - loading = false; - } - }); - } - }); - } else { - if (offset == 0) {//说明是新增记录 - logQSLAdapter.clearRecords(); - } - mainViewModel.databaseOpr.getQSLRecordByCallsign(false, offset, callsign, mainViewModel.queryFilter - , new OnQueryQSLRecordCallsign() { - @Override - public void afterQuery(ArrayList records) { - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - logQSLAdapter.setQSLList(records); - loading = false; - } - }); - } - }); - - } - } - - - /** - * 显示统计页面 - */ - private void showCountFragment() { - //用于Fragment的导航。 - NavHostFragment navHostFragment = (NavHostFragment) requireActivity() - .getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); - assert navHostFragment != null;//断言不为空 - navHostFragment.getNavController().navigate(R.id.countFragment); - ; - - } - - /** - * 显示QRZ查询界面 - * - * @param callsign 呼号 - */ - private void showQrzFragment(String callsign) { - NavHostFragment navHostFragment = (NavHostFragment) requireActivity() - .getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); - assert navHostFragment != null;//断言不为空 - Bundle bundle = new Bundle(); - bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); - navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); - } - - - /** - * 获取本机IP地址 - * - * @return IP 地址 - */ - @Nullable - private String getLocalIp() { - WifiManager wifiManager = (WifiManager) requireContext().getApplicationContext() - .getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - int ipAddress = wifiInfo.getIpAddress(); - if (ipAddress == 0) { - return null; - } - return ((ipAddress & 0xff) + "." + (ipAddress >> 8 & 0xff) + "." + (ipAddress >> 16 & 0xff) - + "." + (ipAddress >> 24 & 0xff)); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java deleted file mode 100644 index 3535871..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 网络模式登录ICOM的对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.text.method.HideReturnsTransformationMethod; -import android.text.method.PasswordTransformationMethod; -import android.view.View; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; - -import androidx.annotation.NonNull; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.icom.IComWifiRig; - -public class LoginIcomRadioDialog extends Dialog { - private static final String TAG = "LoginIcomRadioDialog"; - private final MainViewModel mainViewModel; - private EditText inputIcomAddressEdit; - private EditText inputIcomPortEdit; - private EditText inputIcomUserNameEdit; - private EditText inputIcomPasswordEdit; - private Button icomLoginButton; - private boolean passVisible=false; - - - public LoginIcomRadioDialog(@NonNull Context context, MainViewModel mainViewModel) { - super(context); - this.mainViewModel = mainViewModel; - } - - @SuppressLint("NotifyDataSetChanged") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.login_icom_dialog_layout); - inputIcomAddressEdit = (EditText) findViewById(R.id.inputIcomAddressEdit); - inputIcomPortEdit = (EditText) findViewById(R.id.inputIcomPortEdit); - inputIcomUserNameEdit = (EditText) findViewById(R.id.inputIcomUserNameEdit); - inputIcomPasswordEdit = (EditText) findViewById(R.id.inputIcomPasswordEdit); - icomLoginButton = (Button) findViewById(R.id.icomLoginButton); - ImageButton showPassImageButton = (ImageButton) findViewById(R.id.showPassImageButton); - - inputIcomAddressEdit.setText(GeneralVariables.icomIp); - inputIcomPortEdit.setText(String.valueOf(GeneralVariables.icomUdpPort)); - inputIcomUserNameEdit.setText(GeneralVariables.icomUserName); - inputIcomPasswordEdit.setText(GeneralVariables.icomPassword); - checkInput(); - icomLoginButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.connect_icom_ip) - ,inputIcomAddressEdit.getText())); - - //IComWifiRig iComWifiRig=; - mainViewModel.connectIComWifiRig(GeneralVariables.getMainContext() - ,new IComWifiRig(GeneralVariables.icomIp - ,GeneralVariables.icomUdpPort - ,GeneralVariables.icomUserName - ,GeneralVariables.icomPassword)); - dismiss(); - } - }); - - - inputIcomAddressEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - checkInput(); - GeneralVariables.icomIp=inputIcomAddressEdit.getText().toString().trim(); - writeConfig("icomIp", inputIcomAddressEdit.getText().toString().trim()); - } - }); - - inputIcomPortEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - checkInput(); - if (GeneralVariables.isInteger(inputIcomPortEdit.getText().toString().trim())) { - writeConfig("icomPort", inputIcomPortEdit.getText().toString().trim()); - GeneralVariables.icomUdpPort=Integer.parseInt(inputIcomPortEdit.getText().toString().trim()); - } - } - }); - inputIcomUserNameEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - checkInput(); - writeConfig("icomUserName", inputIcomUserNameEdit.getText().toString().trim()); - GeneralVariables.icomUserName=inputIcomUserNameEdit.getText().toString().trim(); - } - }); - inputIcomPasswordEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - checkInput(); - writeConfig("icomPassword", inputIcomPasswordEdit.getText().toString()); - GeneralVariables.icomPassword=inputIcomPasswordEdit.getText().toString(); - } - }); - - showPassImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - passVisible=!passVisible; - if (passVisible) { - inputIcomPasswordEdit.setTransformationMethod( - HideReturnsTransformationMethod.getInstance()); - }else { - inputIcomPasswordEdit.setTransformationMethod( - PasswordTransformationMethod.getInstance()); - } - } - }); - } - - - public void checkInput() { - icomLoginButton.setEnabled(!inputIcomAddressEdit.getText().toString().isEmpty() - && !inputIcomPortEdit.getText().toString().isEmpty() - && !inputIcomUserNameEdit.getText().toString().isEmpty() - && !inputIcomPasswordEdit.getText().toString().isEmpty() - && GeneralVariables.isInteger(inputIcomPortEdit.getText().toString().trim()) - ); - } - - /** - * 把配置信息写到数据库 - * - * @param KeyName 关键词 - * @param Value 值 - */ - private void writeConfig(String KeyName, String Value) { - mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); - } - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); -// params.height = (int) (height * 0.6); - if (width > height) { - params.width = (int) (width * 0.6); - //params.height = (int) (height * 0.6); - } else { - params.width = (int) (width * 0.8); - //params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java deleted file mode 100644 index 0b237ed..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java +++ /dev/null @@ -1,502 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 呼叫界面。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.AdapterView; - -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; -import androidx.navigation.fragment.NavHostFragment; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.databinding.FragmentMyCallingBinding; -import com.bg7yoz.ft8cn.ft8transmit.FunctionOfTransmit; -import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; - - -public class MyCallingFragment extends Fragment { - private static final String TAG = "MyCallingFragment"; - private FragmentMyCallingBinding binding; - private MainViewModel mainViewModel; - - private RecyclerView transmitRecycleView; - - private CallingListAdapter transmitCallListAdapter; - - private FunctionOrderSpinnerAdapter functionOrderSpinnerAdapter; - - - static { - System.loadLibrary("ft8cn"); - } - - - /** - * 马上对发起者呼叫 - * - * @param message 消息 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - private void doCallNow(Ft8Message message) { - mainViewModel.addFollowCallsign(message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() - , 1, message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - - - /** - * 菜单选项 - * - * @param item 菜单 - * @return 是否选择 - */ - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public boolean onContextItemSelected(@NonNull MenuItem item) { - //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); - - int position = (int) item.getActionView().getTag(); - Ft8Message ft8Message = transmitCallListAdapter.getMessageByPosition(position); - if (ft8Message == null) return super.onContextItemSelected(item); - ; - - GeneralVariables.resetLaunchSupervision();//复位自动监管 - switch (item.getItemId()) { - case 1://时序与发送者相反!!! - Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - } - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() - , 1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - break; - - case 3: - Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); - doCallNow(ft8Message); - //if (!mainViewModel.ft8TransmitSignal.isActivated()) { - // mainViewModel.ft8TransmitSignal.setActivated(true); - // } - // mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() - // , 1, ft8Message.extraInfo); - //mainViewModel.ft8TransmitSignal.transmitNow(); - break; - - case 4://回复 - Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); - mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.setActivated(true); - GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 - } - //呼叫发启者 - mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() - , -1, ft8Message.extraInfo); - mainViewModel.ft8TransmitSignal.transmitNow(); - break; - - case 5://to 的QRZ - showQrzFragment(ft8Message.getCallsignTo()); - break; - case 6://from 的QRZ - showQrzFragment(ft8Message.getCallsignFrom()); - break; - - } - - return super.onContextItemSelected(item); - } - - /** - * 查询QRZ信息 - * - * @param callsign 呼号 - */ - private void showQrzFragment(String callsign) { - NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); - assert navHostFragment != null;//断言不为空 - Bundle bundle = new Bundle(); - bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); - navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); - } - - @SuppressLint("NotifyDataSetChanged") - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mainViewModel = MainViewModel.getInstance(this); - binding = FragmentMyCallingBinding.inflate(inflater, container, false); - - //当横屏时显示频谱图 - if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - binding.messageSpectrumView.run(mainViewModel, this); - } - - - //发射消息的列表 - functionOrderSpinnerAdapter = new FunctionOrderSpinnerAdapter(requireContext(), mainViewModel); - binding.functionOrderSpinner.setAdapter(functionOrderSpinnerAdapter); - functionOrderSpinnerAdapter.notifyDataSetChanged(); - - - //关注的消息列表 - transmitRecycleView = binding.transmitRecycleView; - transmitCallListAdapter = new CallingListAdapter(this.getContext(), mainViewModel - , GeneralVariables.transmitMessages, CallingListAdapter.ShowMode.MY_CALLING); - transmitRecycleView.setLayoutManager(new LinearLayoutManager(requireContext())); - transmitRecycleView.setAdapter(transmitCallListAdapter); - - - transmitCallListAdapter.notifyDataSetChanged(); - - - //设置消息列表滑动,用于快速呼叫 - initRecyclerViewAction(); - //菜单 - requireActivity().registerForContextMenu(transmitRecycleView); - - //显示UTC时间 - mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Long aLong) { - binding.timerTextView.setText(UtcTimer.getTimeStr(aLong)); - } - }); - //显示发射频率 - GeneralVariables.mutableBaseFrequency.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Float aFloat) { - binding.baseFrequencyTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.sound_frequency_is), aFloat)); - } - }); - - - //观察发射状态按钮的变化 - Observer transmittingObserver = new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - if (mainViewModel.ft8TransmitSignal.isTransmitting()) { - binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_send_red_48); - binding.setTransmitImageButton.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.view_blink)); - } else { - //录音对象也要处于启动状态才可以有发射的状态 - if (mainViewModel.ft8TransmitSignal.isActivated() && mainViewModel.hamRecorder.isRunning()) { - binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_send_white_48); - } else { - binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_cancel_schedule_send_off); - } - binding.setTransmitImageButton.setAnimation(null); - } - - //暂停播放按键 - if (mainViewModel.ft8TransmitSignal.isTransmitting()) { - binding.pauseTransmittingImageButton.setImageResource(R.drawable.ic_baseline_pause_circle_outline_24); - binding.pauseTransmittingImageButton.setVisibility(View.VISIBLE); - } else { - binding.pauseTransmittingImageButton.setVisibility(View.GONE); - binding.pauseTransmittingImageButton.setImageResource(R.drawable.ic_baseline_pause_disable_circle_outline_24); - } - } - }; - //显示发射状态 - mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(getViewLifecycleOwner(), transmittingObserver); - mainViewModel.ft8TransmitSignal.mutableIsActivated.observe(getViewLifecycleOwner(), transmittingObserver); - - //暂停按钮 - binding.pauseTransmittingImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mainViewModel.ft8TransmitSignal.setTransmitting(false); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - }); - - //监视命令程序 - mainViewModel.ft8TransmitSignal.mutableFunctions.observe(getViewLifecycleOwner() - , new Observer>() { - @Override - public void onChanged(ArrayList functionOfTransmits) { - functionOrderSpinnerAdapter.notifyDataSetChanged(); - } - }); - - //观察指令序号的变化 - mainViewModel.ft8TransmitSignal.mutableFunctionOrder.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Integer integer) { - if (mainViewModel.ft8TransmitSignal.functionList.size() < 6) { - binding.functionOrderSpinner.setSelection(0); - } else { - binding.functionOrderSpinner.setSelection(integer - 1); - } - } - }); - - //设置当指令序号被选择的事件 - binding.functionOrderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - if (mainViewModel.ft8TransmitSignal.functionList.size() > 1) { - mainViewModel.ft8TransmitSignal.setCurrentFunctionOrder(i + 1); - } - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - - //显示当前目标呼号 - mainViewModel.ft8TransmitSignal.mutableToCallsign.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(TransmitCallsign transmitCallsign) { - if (GeneralVariables.toModifier!=null) { - binding.toCallsignTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.target_callsign) - , transmitCallsign.callsign+" "+GeneralVariables.toModifier)); - }else { - binding.toCallsignTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.target_callsign) - , transmitCallsign.callsign)); - } - } - }); - - //显示当前发射的时序 - mainViewModel.ft8TransmitSignal.mutableSequential.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Integer integer) { - binding.transmittingSequentialTextView.setText( - String.format(GeneralVariables.getStringFromResource(R.string.transmission_sequence) - , integer)); - } - }); - - //设置发射按钮 - binding.setTransmitImageButton.setOnClickListener(new View.OnClickListener() { - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public void onClick(View view) { - //如果 - if (!mainViewModel.ft8TransmitSignal.isActivated()) { - mainViewModel.ft8TransmitSignal.restTransmitting(); - } - mainViewModel.ft8TransmitSignal.setActivated(!mainViewModel.ft8TransmitSignal.isActivated()); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - }); - - //观察传输消息列表的变化 - //mainViewModel.mutableTransmitMessages.observe(getViewLifecycleOwner(), new Observer>() { - mainViewModel.mutableTransmitMessagesCount.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Integer count) { - binding.decoderCounterTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.message_count) - , GeneralVariables.transmitMessages.size())); - //if (count == 0) { - transmitCallListAdapter.notifyDataSetChanged(); - //} else { - // transmitCallListAdapter.notifyItemInserted( - // GeneralVariables.transmitMessages.size() - count); - //} - - //当列表下部稍微多出一些,自动上移 - if (transmitRecycleView.computeVerticalScrollRange() - - transmitRecycleView.computeVerticalScrollExtent() - - transmitRecycleView.computeVerticalScrollOffset() < 300) { - transmitRecycleView.scrollToPosition(transmitCallListAdapter.getItemCount() - 1); - } - } - }); - - //清除传输消息列表 - binding.clearMycallListImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mainViewModel.clearTransmittingMessage(); - } - }); - - //复位到CQ按键 - binding.resetToCQImageView.setOnClickListener(new View.OnClickListener() { - //@RequiresApi(api = Build.VERSION_CODES.N) - @Override - public void onClick(View view) { - mainViewModel.ft8TransmitSignal.resetToCQ(); - GeneralVariables.resetLaunchSupervision();//复位自动监管 - } - }); - //自由文本输入框的限定操作 - binding.transFreeTextEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - mainViewModel.ft8TransmitSignal.setFreeText(editable.toString().toUpperCase()); - } - }); - binding.resetToCQImageView.setLongClickable(true); - binding.resetToCQImageView.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - mainViewModel.setTransmitIsFreeText(!mainViewModel.getTransitIsFreeText()); - showFreeTextEdit(); - return true; - } - }); - - showFreeTextEdit(); - return binding.getRoot(); - } - - private void showFreeTextEdit() { - if (mainViewModel.getTransitIsFreeText()) { - binding.transFreeTextEdit.setVisibility(View.VISIBLE); - binding.functionOrderSpinner.setVisibility(View.GONE); - } else { - binding.transFreeTextEdit.setVisibility(View.GONE); - binding.functionOrderSpinner.setVisibility(View.VISIBLE); - } - } - - /** - * 设置列表滑动动作 - */ - private void initRecyclerViewAction() { - new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG - , ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder - , @NonNull RecyclerView.ViewHolder target) { - return false; - } - - //@RequiresApi(api = Build.VERSION_CODES.N) - @SuppressLint("NotifyDataSetChanged") - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - if (direction == ItemTouchHelper.START) { - Ft8Message message = transmitCallListAdapter.getMessageByViewHolder(viewHolder); - if (message != null) { - //呼叫的目标不能是自己 - if (!message.getCallsignFrom().equals("<...>") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) - && !(message.i3 == 0 && message.n3 == 0)) { - doCallNow(message); - } - } - transmitCallListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); - } - if (direction == ItemTouchHelper.END) {//删除 - transmitCallListAdapter.deleteMessage(viewHolder.getAdapterPosition()); - transmitCallListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); - } - } - - - @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); - //制作呼叫背景的图标显示 - Drawable callIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.ic_baseline_send_red_48); - Drawable delIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.log_item_delete_icon); - Drawable background = new ColorDrawable(Color.LTGRAY); - Ft8Message message = transmitCallListAdapter.getMessageByViewHolder(viewHolder); - if (message == null) { - return; - } - if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 - return; - } - Drawable icon; - if (dX > 0) { - icon = delIcon; - } else { - icon = callIcon; - } - View itemView = viewHolder.itemView; - int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - int iconLeft, iconRight, iconTop, iconBottom; - int backTop, backBottom, backLeft, backRight; - backTop = itemView.getTop(); - backBottom = itemView.getBottom(); - iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; - iconBottom = iconTop + icon.getIntrinsicHeight(); - if (dX > 0) { - backLeft = itemView.getLeft(); - backRight = itemView.getLeft() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconLeft = itemView.getLeft() + iconMargin; - iconRight = iconLeft + icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else if (dX < 0) { - backRight = itemView.getRight(); - backLeft = itemView.getRight() + (int) dX; - background.setBounds(backLeft, backTop, backRight, backBottom); - iconRight = itemView.getRight() - iconMargin; - iconLeft = iconRight - icon.getIntrinsicWidth(); - icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); - } else { - background.setBounds(0, 0, 0, 0); - icon.setBounds(0, 0, 0, 0); - } - background.draw(c); - icon.draw(c); - - } - }).attachToRecyclerView(binding.transmitRecycleView); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java deleted file mode 100644 index 8c13f5d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 未回答时间列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.util.ArrayList; -import java.util.List; - -public class NoReplyLimitSpinnerAdapter extends BaseAdapter { - private final List noReplyCount=new ArrayList<>(); - private final Context mContext; - - public NoReplyLimitSpinnerAdapter(Context context) { - mContext=context; - for (int i = 0; i <= 30; i++) { - noReplyCount.add(i); - } - } - - @Override - public int getCount() { - return noReplyCount.size(); - } - - @Override - public Object getItem(int i) { - return noReplyCount.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.no_reply_limit_spinner_item, null); - if (view!=null){ - TextView textView=(TextView)view.findViewById(R.id.noReplyLimitCountItemTextView); - if (i==0){ - textView.setText(GeneralVariables.getStringFromResource(R.string.ignore)); - } - else { - textView.setText(String.format(GeneralVariables.getStringFromResource(R.string.times), i)); - } - } - return view; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java deleted file mode 100644 index 36ef0eb..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * PTT延迟发射列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; - -import java.util.ArrayList; -import java.util.List; - -public class PttDelaySpinnerAdapter extends BaseAdapter { - private final List delayTime=new ArrayList<>(); - private final Context mContext; - - public PttDelaySpinnerAdapter(Context context) { - mContext=context; - for (int i = 0; i < 20; i++) { - delayTime.add(i*10); - } - } - - @Override - public int getCount() { - return delayTime.size(); - } - - @Override - public Object getItem(int i) { - return delayTime.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.ptt_delay_spinner_item, null); - if (view!=null){ - TextView textView=(TextView)view.findViewById(R.id.pttDelayItemTextView); - textView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.milliseconds),delayTime.get(i))); - } - return view; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java deleted file mode 100644 index 021f512..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 查QRZ的web View。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import androidx.fragment.app.Fragment; - -import com.bg7yoz.ft8cn.databinding.FragmentQrzBinding; - - -public class QRZ_Fragment extends Fragment { - private FragmentQrzBinding binding; - - public static final String CALLSIGN_PARAM = "callsign"; - - private String qrzParam; - - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - qrzParam = getArguments().getString(CALLSIGN_PARAM); - } - } - - @SuppressLint("SetJavaScriptEnabled") - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - binding=FragmentQrzBinding.inflate(inflater, container, false); - binding.qrzWebView.getSettings().setJavaScriptEnabled(true); - //binding.qrzWebView.getSettings().setDomStorageEnabled(true); // 这个要加上 - binding.qrzWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); - binding.qrzWebView.getSettings().setUseWideViewPort(true); - - binding.qrzWebView.getSettings().setLoadWithOverviewMode(true); - binding.qrzWebView.getSettings().setSupportZoom(true); - binding.qrzWebView.getSettings().setBuiltInZoomControls(true); - String url = String.format("https://www.qrz.com/db/%s",qrzParam); - WebViewClient webViewClient = new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - super.shouldOverrideUrlLoading(view, url); - view.loadUrl(url); - return true; - } - }; - binding.qrzWebView.setWebViewClient(webViewClient); - - - binding.qrzWebView.loadUrl(url); - return binding.getRoot(); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java deleted file mode 100644 index 00e6080..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 电台型号列表。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.database.RigNameList; - -public class RigNameSpinnerAdapter extends BaseAdapter { - private final Context mContext; - private final RigNameList rigNameList; - public RigNameSpinnerAdapter(Context context) { - rigNameList=RigNameList.getInstance(context); - mContext=context; - } - - public RigNameList.RigName getRigName(int index){ - return rigNameList.getRigNameByIndex(index); - } - @Override - public int getCount() { - return rigNameList.rigList.size(); - } - - @Override - public Object getItem(int i) { - return rigNameList.rigList.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"ViewHolder", "InflateParams", "UseCompatLoadingForDrawables"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.rig_name_spinner_item, null); - if (view!=null){ - TextView textView=view.findViewById(R.id.rigNameItemTextView); - ImageView imageView=view.findViewById(R.id.rigLogoImageView); - if (rigNameList.getRigNameInfo(i).contains("GUOHE")){ - imageView.setImageDrawable(mContext.getDrawable(R.drawable.guohe_logo)); - imageView.setVisibility(View.VISIBLE); - }else { - imageView.setVisibility(View.GONE); - imageView.setImageDrawable(null); - } - textView.setText(rigNameList.getRigNameInfo(i)); - } - return view; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java deleted file mode 100644 index bda7898..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 频率标尺,自定义控件。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; - -import androidx.annotation.Nullable; - -public class RulerFrequencyView extends View { - private static final String TAG = "RulerFrequencyView"; - private int rulerWidth = getWidth(); - private int freq = 1000; - - public RulerFrequencyView(Context context) { - super(context); - } - - public RulerFrequencyView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public RulerFrequencyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onDraw(Canvas canvas) { - //Log.d(TAG, String.format("onDraw: rulerWidth:%d,getWidth:%d", rulerWidth, getWidth())); - drawRuler(canvas); - super.onDraw(canvas); - } - - /** - * 把dp值转换为像素点 - * - * @param dp dp值 - * @return 像素点 - */ - private int dpToPixel(int dp) { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp - , getResources().getDisplayMetrics()); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - //Log.d(TAG, String.format("onSizeChanged: rulerWidth:%d,getWidth:%d", w, getWidth())); - rulerWidth = w; - super.onSizeChanged(w, h, oldw, oldh); - } - - @SuppressLint({"DefaultLocale", "ResourceAsColor"}) - public void drawRuler(Canvas canvas) { - int top = 1; - //rulerWidth=getRight(); - int width_rate = Math.round((float) rulerWidth / 30f); - int lineWidth = (int) (getResources().getDisplayMetrics().density); - int lineHeight = (int) (2 * getResources().getDisplayMetrics().density); - Rect rect = new Rect(); - Paint paint = new Paint(); - paint.setColor(0xff00ffff); - for (int i = 0; i <= 300; i++) { - if (i % 1 == 0) { - rect.top = top; - rect.left = Math.round((float) i * width_rate); - rect.right = rect.left + lineWidth; - if (i % 5 == 0) { - rect.bottom = top + lineHeight * 3; - Paint fontPaint = new Paint(); - fontPaint.setTextSize(dpToPixel(8)); - fontPaint.setColor(0xff00ffff); - fontPaint.setAntiAlias(true); - fontPaint.setDither(true); - if (i == 0) { - fontPaint.setTextAlign(Paint.Align.LEFT); - } else if (i == 300) { - fontPaint.setTextAlign(Paint.Align.RIGHT); - } else { - fontPaint.setTextAlign(Paint.Align.CENTER); - } - canvas.drawText(String.format("%dHz", i * 100), rect.left - , rect.bottom + 8 * getResources().getDisplayMetrics().density - , fontPaint); - - } else { - rect.bottom = top + lineHeight; - } - canvas.drawRect(rect, paint); - - } - } - //主线 - rect.top = 1; - rect.left = 0; - rect.right = rulerWidth; - rect.bottom = (int) (rect.top + 2*getResources().getDisplayMetrics().density); - canvas.drawRect(rect, paint); - - //当前频率范围标记。红色块 - Rect mark = new Rect(); - paint.setColor(0xffff0000); - mark.top = 1; - mark.left = width_rate * (freq - 50) / 100; - mark.right = width_rate * (freq + 50) / 100; - mark.bottom = (int) (mark.top + 3*getResources().getDisplayMetrics().density); - canvas.drawRect(mark, paint); - - } - - public void setFreq(int freq) { - this.freq = freq; - this.postInvalidate(); - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java deleted file mode 100644 index c3a2dbe..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 蓝牙设备选择对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.bluetooth.BluetoothConstants; - -import java.util.ArrayList; - -public class SelectBluetoothDialog extends Dialog { - class BluetoothDeviceInfo { - BluetoothDevice device; - boolean isSPP; - boolean isHeadSet; - - public BluetoothDeviceInfo(BluetoothDevice device, boolean isSPP,boolean isHeadSet) { - this.device = device; - this.isSPP = isSPP; - this.isHeadSet=isHeadSet; - } - } - - private MainViewModel mainViewModel; - private BluetoothAdapter bluetoothAdapter; - private final ArrayList devices = new ArrayList<>(); - private RecyclerView devicesRecyclerView; - private BluetoothDevicesAdapter blueToothListAdapter; - - private ImageView upImage; - private ImageView downImage; - - - public SelectBluetoothDialog(@NonNull Context context, MainViewModel mainViewModel) { - super(context); - this.mainViewModel = mainViewModel; - - } - - - - @SuppressLint({"MissingPermission", "NotifyDataSetChanged"}) - private void getBluetoothDevice() { - devices.clear(); - if (bluetoothAdapter == null) { - return; - } - for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { - if (BluetoothConstants.checkIsSpp(device)){//spp设备放前面 - devices.add(0,new BluetoothDeviceInfo(device,true,BluetoothConstants.checkIsHeadSet(device))); - continue; - } - if (BluetoothConstants.checkIsHeadSet(device)){//headset设备放后面 - devices.add(new BluetoothDeviceInfo(device, false,BluetoothConstants.checkIsHeadSet(device))); - continue; - } - } - - blueToothListAdapter.notifyDataSetChanged(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.select_bluetooth_dialog_layout); - devicesRecyclerView = (RecyclerView) findViewById(R.id.bluetoothListRecyclerView); - bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - blueToothListAdapter = new BluetoothDevicesAdapter(); - devicesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - devicesRecyclerView.setAdapter(blueToothListAdapter); - upImage=(ImageView) findViewById(R.id.bluetoothScrollUpImageView); - downImage=(ImageView)findViewById(R.id.bluetoothScrollDownImageView); - getBluetoothDevice(); - - //显示滚动箭头 - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - setScrollImageVisible(); - } - }, 1000); - devicesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - setScrollImageVisible(); - } - }); - - } - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); -// params.height = (int) (height * 0.6); - if (width > height) { - params.width = (int) (width * 0.6); - params.height = (int) (height * 0.6); - } else { - params.width = (int) (width * 0.8); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - - } - /** - * 设置界面的上下滚动的图标 - */ - private void setScrollImageVisible() { - - if (devicesRecyclerView.canScrollVertically(1)) { - upImage.setVisibility(View.VISIBLE); - } else { - upImage.setVisibility(View.GONE); - } - - if (devicesRecyclerView.canScrollVertically(-1)) { - downImage.setVisibility(View.VISIBLE); - } else { - downImage.setVisibility(View.GONE); - } - } - - class BluetoothDevicesAdapter extends RecyclerView.Adapter { - - @NonNull - @Override - public BluetoothHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.bluetooth_device_list_item, parent, false); - final BluetoothDevicesAdapter.BluetoothHolder holder = new BluetoothDevicesAdapter.BluetoothHolder(view); - return holder; - } - - @SuppressLint("MissingPermission") - @Override - public void onBindViewHolder(@NonNull BluetoothHolder holder, int position) { - holder.device = devices.get(position); - holder.bluetoothNameTextView.setText(holder.device.device.getName()); - if (holder.device.isSPP){ - holder.bluetoothNameTextView.setTextColor(getContext().getResources().getColor( - R.color.bluetooth_device_enable_color)); - }else { - holder.bluetoothNameTextView.setTextColor(getContext().getResources().getColor( - R.color.bluetooth_device_disable_color)); - } - if (BluetoothConstants.checkIsHeadSet(holder.device.device)){ - holder.headsetImageView.setVisibility(View.VISIBLE); - }else { - holder.headsetImageView.setVisibility(View.GONE); - } - if (BluetoothConstants.checkIsSpp(holder.device.device)){ - holder.sppDeviceImageView.setVisibility(View.VISIBLE); - }else { - holder.sppDeviceImageView.setVisibility(View.GONE); - } - holder.bluetoothAddressTextView.setText(holder.device.device.getAddress()); - - holder.bluetoothListConstraintLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.select_bluetooth_device) - ,holder.device.device.getName())); - mainViewModel.connectBluetoothRig(GeneralVariables.getMainContext(), holder.device.device); - - dismiss(); - } - }); - } - - @Override - public int getItemCount() { - return devices.size(); - } - - class BluetoothHolder extends RecyclerView.ViewHolder { - public BluetoothDeviceInfo device; - TextView bluetoothNameTextView, bluetoothAddressTextView; - ConstraintLayout bluetoothListConstraintLayout; - ImageView headsetImageView,sppDeviceImageView; - public BluetoothHolder(@NonNull View itemView) { - super(itemView); - bluetoothNameTextView = itemView.findViewById(R.id.bluetoothNameTextView); - bluetoothAddressTextView = itemView.findViewById(R.id.bluetoothAddressTextView); - bluetoothListConstraintLayout = itemView.findViewById(R.id.bluetoothListConstraintLayout); - headsetImageView = itemView.findViewById(R.id.headsetImageView); - sppDeviceImageView = itemView.findViewById(R.id.sppDeviceImageView); - } - } - } - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java deleted file mode 100644 index 209f185..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * FlexRadio选择对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.flex.FlexRadio; -import com.bg7yoz.ft8cn.flex.FlexRadioFactory; - -public class SelectFlexRadioDialog extends Dialog { - private static final String TAG="SelectFlexRadioDialog"; - private final MainViewModel mainViewModel; - private RecyclerView flexRecyclerView; - private ImageView upImage; - private ImageView downImage; - private FlexRadioFactory flexRadioFactory; - private FlexRadioAdapter flexRadioAdapter; - private ImageButton connectFlexImageButton; - private EditText inputFlexAddressEdit; - - - - public SelectFlexRadioDialog(@NonNull Context context, MainViewModel mainViewModel){ - super(context); - this.mainViewModel = mainViewModel; - } - - @SuppressLint("NotifyDataSetChanged") - @Override - protected void onCreate(Bundle savedInstanceState){ - super.onCreate(savedInstanceState); - setContentView(R.layout.select_flex_dialog_layout); - flexRecyclerView=(RecyclerView) findViewById(R.id.flexRadioListRecyclerView); - flexRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - upImage=(ImageView) findViewById(R.id.flexRadioScrollUpImageView); - downImage=(ImageView)findViewById(R.id.flexRadioScrollDownImageView); - inputFlexAddressEdit=(EditText)findViewById(R.id.inputFlexAddressEdit); - connectFlexImageButton=(ImageButton) findViewById(R.id.connectFlexImageButton); - connectFlexImageButton.setEnabled(false); - connectFlexImageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.connect_flex_ip) - ,inputFlexAddressEdit.getText())); - FlexRadio flexRadio=new FlexRadio(); - Log.e(TAG, "onClick: "+inputFlexAddressEdit.getText().toString()); - flexRadio.setIp(inputFlexAddressEdit.getText().toString()); - flexRadio.setModel("FlexRadio"); - mainViewModel.connectFlexRadioRig(GeneralVariables.getMainContext(),flexRadio); - - dismiss(); - } - }); - - inputFlexAddressEdit.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - connectFlexImageButton.setEnabled(!inputFlexAddressEdit.getText().toString().isEmpty()); - } - }); - - - - - flexRadioAdapter=new FlexRadioAdapter(); - flexRecyclerView.setAdapter(flexRadioAdapter); - - flexRadioFactory = FlexRadioFactory.getInstance(); - - - flexRadioFactory.setOnFlexRadioEvents(new FlexRadioFactory.OnFlexRadioEvents() { - - @Override - public void OnFlexRadioAdded(FlexRadio flexRadio) { - flexRecyclerView.post(new Runnable() { - @Override - public void run() { - flexRadioAdapter.notifyDataSetChanged(); - } - }); - } - - @Override - public void OnFlexRadioInvalid(FlexRadio flexRadio) { - flexRecyclerView.post(new Runnable() { - @Override - public void run() { - flexRadioAdapter.notifyDataSetChanged(); - } - }); - } - }); - - - - //显示滚动箭头 - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - setScrollImageVisible(); - } - }, 1000); - flexRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - setScrollImageVisible(); - } - }); - } - - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); -// params.height = (int) (height * 0.6); - if (width > height) { - params.width = (int) (width * 0.6); - params.height = (int) (height * 0.6); - } else { - params.width = (int) (width * 0.8); - params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - } - - /** - * 设置界面的上下滚动的图标 - */ - private void setScrollImageVisible() { - - if (flexRecyclerView.canScrollVertically(1)) { - upImage.setVisibility(View.VISIBLE); - } else { - upImage.setVisibility(View.GONE); - } - - if (flexRecyclerView.canScrollVertically(-1)) { - downImage.setVisibility(View.VISIBLE); - } else { - downImage.setVisibility(View.GONE); - } - } - - class FlexRadioAdapter extends RecyclerView.Adapter{ - - - @NonNull - @Override - public FlexViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - View view = layoutInflater.inflate(R.layout.flex_device_list_item, parent, false); - final FlexRadioAdapter.FlexViewHolder holder = new FlexRadioAdapter.FlexViewHolder(view); - return holder; - } - - @Override - public void onBindViewHolder(@NonNull FlexViewHolder holder, int position) { - holder.flexRadio=flexRadioFactory.flexRadios.get(position); - holder.flexRadioIpTextView.setText(holder.flexRadio.getIp()); - holder.flexRadioSerialNumTextView.setText(holder.flexRadio.getSerial()); - holder.flexRadioNameTextView.setText(holder.flexRadio.getModel()); - - holder.flexRadioListConstraintLayout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.select_flex_device) - ,holder.flexRadio.getModel())); - - //此处添加连接flex电台的动作 - //mainViewModel.connectBluetoothRig(GeneralVariables.getMainContext(), holder.device.device); - mainViewModel.connectFlexRadioRig(GeneralVariables.getMainContext(),holder.flexRadio); - dismiss(); - } - }); - - } - - @Override - public int getItemCount() { - return flexRadioFactory.flexRadios.size(); - } - - - - class FlexViewHolder extends RecyclerView.ViewHolder{ - public FlexRadio flexRadio; - TextView flexRadioNameTextView,flexRadioIpTextView,flexRadioSerialNumTextView; - ConstraintLayout flexRadioListConstraintLayout; - public FlexViewHolder(@NonNull View itemView) { - super(itemView); - flexRadioNameTextView=itemView.findViewById(R.id.flexRadioNameTextView); - flexRadioIpTextView=itemView.findViewById(R.id.flexRadioIpTextView); - flexRadioSerialNumTextView=itemView.findViewById(R.id.flexRadioSerialNumTextView); - flexRadioListConstraintLayout=itemView.findViewById(R.id.flexRadioListConstraintLayout); - } - - } - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java deleted file mode 100644 index d3c0e60..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 设置信号输出强度的对话框。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.lifecycle.Observer; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; - -public class SetVolumeDialog extends Dialog { - private static final String TAG = "SetVolumeDialog"; - private TextView volumeValueMessage; - private SeekBar volumeSeekBar; - private final MainViewModel mainViewModel; - private VolumeProgress volumeProgress; - - public SetVolumeDialog(@NonNull Context context, MainViewModel mainViewModel) { - super(context); - this.mainViewModel = mainViewModel; - } - - - @SuppressLint({"NotifyDataSetChanged", "MissingInflatedId"}) - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.set_volume_dialog); - volumeValueMessage = (TextView) findViewById(R.id.volumeValueMessage); - volumeSeekBar = (SeekBar) findViewById(R.id.volumeSeekBar); - volumeProgress=(VolumeProgress) findViewById(R.id.volumeProgress); - volumeProgress.setAlarmValue(1.1f); - volumeProgress.setValueColor(getContext().getColor(R.color.volume_progress_value));//白色 - setVolumeText(GeneralVariables.volumePercent); - volumeSeekBar.setProgress((int) (GeneralVariables.volumePercent*100)); - - GeneralVariables.mutableVolumePercent.observeForever(new Observer() { - @Override - public void onChanged(Float aFloat) { - setVolumeText(aFloat); - } - }); - - - volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - GeneralVariables.volumePercent=i/100f; - GeneralVariables.mutableVolumePercent.postValue(i/100f); - mainViewModel.databaseOpr.writeConfig("volumeValue",String.valueOf(i),null); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - }); - - } - - private void setVolumeText(float vol){ - volumeValueMessage.setText(String.format( - GeneralVariables.getStringFromResource(R.string.volume_percent) - , vol*100f)); - volumeProgress.setPercent(vol); - - } - - /** - * 把配置信息写到数据库 - * - * @param Value 值 - */ - private void writeConfig(String Value) { - mainViewModel.databaseOpr.writeConfig("volumeValue", Value, null); - } - - @Override - public void show() { - super.show(); - WindowManager.LayoutParams params = getWindow().getAttributes(); - //设置对话框的大小,以百分比0.6 - int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); - int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); - if (width > height) { - params.width = (int) (width * 0.7); - //params.height = (int) (height * 0.6); - } else { - params.width = (int) (width * 0.95); - //params.height = (int) (height * 0.5); - } - getWindow().setAttributes(params); - } - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java deleted file mode 100644 index 2498bb5..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 频谱图的主界面。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import static android.view.MotionEvent.ACTION_UP; - -import android.annotation.SuppressLint; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; - -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.databinding.FragmentSpectrumBinding; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -/** - * A simple {@link Fragment} subclass. - * create an instance of this fragment. - */ -public class SpectrumFragment extends Fragment { - private static final String TAG = "SpectrumFragment"; - private FragmentSpectrumBinding binding; - private MainViewModel mainViewModel; - - - private int frequencyLineTimeOut = 0;//画频率线的时间量 - - - static { - System.loadLibrary("ft8cn"); - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mainViewModel = MainViewModel.getInstance(this); - binding = FragmentSpectrumBinding.inflate(inflater, container, false); - binding.columnarView.setShowBlock(true); - binding.deNoiseSwitch.setChecked(mainViewModel.deNoise);//噪声抑制 - binding.waterfallView.setDrawMessage(false); - setDeNoiseSwitchState(); - setMarkMessageSwitchState(); - - binding.rulerFrequencyView.setFreq(Math.round(GeneralVariables.getBaseFrequency())); - mainViewModel.currentMessages=null; - - - //原始频谱开关 - binding.deNoiseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - mainViewModel.deNoise = b; - setDeNoiseSwitchState(); - mainViewModel.currentMessages=null; - } - }); - //标记消息开关 - binding.showMessageSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - mainViewModel.markMessage = b; - setMarkMessageSwitchState(); - } - }); - - //当声音变化,画频谱 - mainViewModel.spectrumListener.mutableDataBuffer.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(float[] floats) { - drawSpectrum(floats); - } - }); - - - - //观察解码的时长 - mainViewModel.ft8SignalListener.decodeTimeSec.observe(getViewLifecycleOwner(), new Observer() { - @SuppressLint("DefaultLocale") - @Override - public void onChanged(Long aLong) { - binding.decodeDurationTextView.setText(String.format( - GeneralVariables.getStringFromResource(R.string.decoding_takes_milliseconds), aLong)); - } - }); - //观察解码的变化 - mainViewModel.mutableIsDecoding.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - binding.waterfallView.setDrawMessage(!aBoolean);//false说明解码完毕 - } - }); - - - //显示UTC时间 - mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Long aLong) { - binding.timersTextView.setText(UtcTimer.getTimeStr(aLong)); - binding.freqBandTextView.setText(GeneralVariables.getBandString()); - } - }); - - - //触摸频谱时的动作 - View.OnTouchListener touchListener = new View.OnTouchListener() { - @SuppressLint("DefaultLocale") - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - - frequencyLineTimeOut = 60;//显示频率线的时长:60*0.16 - - binding.waterfallView.setTouch_x(Math.round(motionEvent.getX())); - binding.columnarView.setTouch_x(Math.round(motionEvent.getX())); - - - - if (!mainViewModel.ft8TransmitSignal.isSynFrequency() - && (binding.waterfallView.getFreq_hz() > 0) - && (motionEvent.getAction() == ACTION_UP) - ) {//如果时异频发射 - mainViewModel.databaseOpr.writeConfig("freq", - String.valueOf(binding.waterfallView.getFreq_hz()), - null); - mainViewModel.ft8TransmitSignal.setBaseFrequency( - (float) binding.waterfallView.getFreq_hz()); - - binding.rulerFrequencyView.setFreq(binding.waterfallView.getFreq_hz()); - - requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.sound_frequency_is_set_to) - , binding.waterfallView.getFreq_hz()),true); - } - }); - } - return false; - } - }; - - binding.waterfallView.setOnTouchListener(touchListener); - binding.columnarView.setOnTouchListener(touchListener); - - return binding.getRoot(); - } - - - - public void drawSpectrum(float[] buffer) { - if (buffer.length <= 0) { - return; - } - int[] fft = new int[buffer.length / 2]; - if (mainViewModel.deNoise) { - getFFTDataFloat(buffer, fft); - } else { - getFFTDataRawFloat(buffer, fft); - } - frequencyLineTimeOut--; - if (frequencyLineTimeOut < 0) { - frequencyLineTimeOut = 0; - } - //达到显示的时长,就取取消掉频率线 - if (frequencyLineTimeOut == 0) { - binding.waterfallView.setTouch_x(-1); - binding.columnarView.setTouch_x(-1); - } - binding.columnarView.setWaveData(fft); - if (mainViewModel.markMessage) {//是否标记消息 - binding.waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), mainViewModel.currentMessages); - } else { - binding.waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), null); - } - } - - private void setDeNoiseSwitchState() { - if (mainViewModel.deNoise) { - binding.deNoiseSwitch.setText(getString(R.string.de_noise)); - } else { - binding.deNoiseSwitch.setText(getString(R.string.raw_spectrum_data)); - } - } - private void setMarkMessageSwitchState(){ - if (mainViewModel.markMessage) { - binding.showMessageSwitch.setText(getString(R.string.markMessage)); - } else { - binding.showMessageSwitch.setText(getString(R.string.unMarkMessage)); - } - } - - public native void getFFTData(int[] data, int fftData[]); - - public native void getFFTDataFloat(float[] data ,int fftData[]); - - - - public native void getFFTDataRaw(int[] data, int fftData[]); - public native void getFFTDataRawFloat(float[] data,int fftData[]); - -} \ No newline at end of file diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java deleted file mode 100644 index 6dbd23c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 包含瀑布图、频率柱状图、标尺的自定义控件。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import static android.view.MotionEvent.ACTION_UP; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.CompoundButton; -import android.widget.Switch; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.MainViewModel; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -public class SpectrumView extends ConstraintLayout { - private MainViewModel mainViewModel; - private ColumnarView columnarView; - private Switch controlDeNoiseSwitch; - private Switch controlShowMessageSwitch; - private WaterfallView waterfallView; - private RulerFrequencyView rulerFrequencyView; - private Fragment fragment; - - - private int frequencyLineTimeOut = 0;//画频率线的时间量 - - static { - System.loadLibrary("ft8cn"); - } - - - - public SpectrumView(@NonNull Context context) { - super(context); - } - - public SpectrumView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - View view = (View) View.inflate(context, R.layout.spectrum_layout,this); - } - - - @SuppressLint("ClickableViewAccessibility") - public void run(MainViewModel mainViewModel , Fragment fragment){ - this.mainViewModel = MainViewModel.getInstance(null); - this.fragment=fragment; - columnarView=findViewById(R.id.controlColumnarView); - controlDeNoiseSwitch=findViewById(R.id.controlDeNoiseSwitch); - waterfallView=findViewById(R.id.controlWaterfallView); - rulerFrequencyView=findViewById(R.id.controlRulerFrequencyView); - controlShowMessageSwitch=findViewById(R.id.controlShowMessageSwitch); - - - setDeNoiseSwitchState(); - setMarkMessageSwitchState(); - - rulerFrequencyView.setFreq(Math.round(GeneralVariables.getBaseFrequency())); - mainViewModel.currentMessages=null; - - - //原始频谱开关 - controlDeNoiseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - mainViewModel.deNoise = b; - setDeNoiseSwitchState(); - mainViewModel.currentMessages=null; - } - }); - //标记消息开关 - controlShowMessageSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean b) { - mainViewModel.markMessage = b; - setMarkMessageSwitchState(); - } - }); - - //当声音变化,画频谱 - mainViewModel.spectrumListener.mutableDataBuffer.observe(fragment.getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(float[] ints) { - drawSpectrum(ints); - } - }); - - - //观察解码的变化 - mainViewModel.mutableIsDecoding.observe(fragment.getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Boolean aBoolean) { - waterfallView.setDrawMessage(!aBoolean);//aBoolean==false说明解码完毕 - } - }); - - //触摸频谱时的动作 - View.OnTouchListener touchListener = new View.OnTouchListener() { - @SuppressLint("DefaultLocale") - @Override - public boolean onTouch(View view, MotionEvent motionEvent) { - - frequencyLineTimeOut = 60;//显示频率线的时长:60*0.16 - - waterfallView.setTouch_x(Math.round(motionEvent.getX())); - columnarView.setTouch_x(Math.round(motionEvent.getX())); - - - if (!mainViewModel.ft8TransmitSignal.isSynFrequency() - && (waterfallView.getFreq_hz() > 0) - && (motionEvent.getAction() == ACTION_UP) - ) {//如果时异频发射 - mainViewModel.databaseOpr.writeConfig("freq", - String.valueOf(waterfallView.getFreq_hz()), - null); - mainViewModel.ft8TransmitSignal.setBaseFrequency( - (float) waterfallView.getFreq_hz()); - - rulerFrequencyView.setFreq(waterfallView.getFreq_hz()); - - fragment.requireActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - ToastMessage.show(String.format( - GeneralVariables.getStringFromResource(R.string.sound_frequency_is_set_to) - , waterfallView.getFreq_hz()),true); - } - }); - } - return false; - } - }; - - waterfallView.setOnTouchListener(touchListener); - columnarView.setOnTouchListener(touchListener); - - - } - private void setDeNoiseSwitchState() { - if (mainViewModel==null) return; - controlDeNoiseSwitch.setChecked(mainViewModel.deNoise); - if (mainViewModel.deNoise) { - controlDeNoiseSwitch.setText(GeneralVariables.getStringFromResource(R.string.de_noise)); - } else { - controlDeNoiseSwitch.setText(GeneralVariables.getStringFromResource(R.string.raw_spectrum_data)); - } - } - private void setMarkMessageSwitchState(){ - if (mainViewModel.markMessage) { - controlShowMessageSwitch.setText(GeneralVariables.getStringFromResource(R.string.markMessage)); - } else { - controlShowMessageSwitch.setText(GeneralVariables.getStringFromResource(R.string.unMarkMessage)); - } - } - - - - - public void drawSpectrum(float[] buffer) { - if (buffer.length <= 0) { - return; - } - int[] fft = new int[buffer.length / 2]; - if (mainViewModel.deNoise) { - getFFTDataFloat(buffer, fft); - } else { - getFFTDataRawFloat(buffer, fft); - } - frequencyLineTimeOut--; - if (frequencyLineTimeOut < 0) { - frequencyLineTimeOut = 0; - } - //达到显示的时长,就取取消掉频率线 - if (frequencyLineTimeOut == 0) { - waterfallView.setTouch_x(-1); - columnarView.setTouch_x(-1); - } - columnarView.setWaveData(fft); - if (mainViewModel.markMessage) {//是否标记消息 - waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), mainViewModel.currentMessages); - } else { - waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), null); - } - } - - - public native void getFFTData(int[] data, int fftData[]); - public native void getFFTDataFloat(float[] data, int fftData[]); - - public native void getFFTDataRaw(int[] data, int fftData[]); - public native void getFFTDataRawFloat(float[] data, int fftData[]); - - -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java deleted file mode 100644 index 1a8a8ea..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 提示消息。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Looper; - -import com.bg7yoz.ft8cn.GeneralVariables; - -import java.util.ArrayList; - -public class ToastMessage { - private static final String TAG="ToastMessage"; - //private static Activity activity; - private static ToastMessage toastMessage=null; - private static final ArrayList debugList=new ArrayList<>(); - public static ToastMessage getInstance(){ - if (toastMessage==null){ - toastMessage=new ToastMessage(); - } - return toastMessage; - } - - //public ToastMessage(Activity activity) { - // this.activity = activity; - //} - public ToastMessage() { - //this.activity = activity; - } - - public static void show(String message){ - addDebugInfo(message); - } - public static synchronized void show(String message,boolean clearMessage){ - if (clearMessage) { - debugList.clear(); - } - show(message); - } - @SuppressLint("DefaultLocale") - private static synchronized void addDebugInfo(String s){ - if (debugList.size()>5){ - //if (debugList.size()>20){ - debugList.remove(0); - } - final String info=s; - debugList.add(info); - GeneralVariables.mutableDebugMessage.postValue(getDebugMessage()); - - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - for (int i = 0; i offsetTime=new ArrayList<>(); - private Context mContext; - - public UtcOffsetSpinnerAdapter(Context context) { - mContext=context; - for (int i = 0; i < 30; i++) { - offsetTime.add(i*5-75); - } - } - - @Override - public int getCount() { - return offsetTime.size(); - } - - @Override - public Object getItem(int i) { - return offsetTime.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); - view=_LayoutInflater.inflate(R.layout.utc_time_offset_spinner_item, null); - if (view!=null){ - TextView textView=(TextView)view.findViewById(R.id.serialPortItemTextView); - textView.setText(String.format(GeneralVariables.getStringFromResource(R.string.offset_time_sec) - ,(offsetTime.get(i) /10f))); - } - return view; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java deleted file mode 100644 index 543c03b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 自定义音频强度的图形控件。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.Nullable; - -public class VolumeProgress extends View { - private Paint mRadarPaint; - private Paint mValuePaint; - - private Path mLinePath;//外部容器形状 - private Path mValuePath;//内部填充的形状 - private float mPercent=0.45f; - private int width,high; - - private int radarColor=Color.WHITE; - private int valueColor=Color.WHITE; - private int alarmColor=Color.RED; - private float alarmValue=0.5f; - - public VolumeProgress(Context context) { - super(context); - } - - public VolumeProgress(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(); - } - - public VolumeProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - private int dpToPixel(int dp) { - return (int) (dp * getResources().getDisplayMetrics().density); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - width=w; - high=h; - postInvalidate(); - super.onSizeChanged(w, h, oldw, oldh); - } - - public void setPercent(float percent) { - mPercent = percent; - - init(); - invalidate(); - } - public void reDraw(){ - setPercent(mPercent); - } - - public void init() { - //绘制外部容器 - mRadarPaint = new Paint(); - mRadarPaint.setAntiAlias(true); - mRadarPaint.setStrokeWidth(dpToPixel(2)); - mRadarPaint.setStyle(Paint.Style.STROKE); - mRadarPaint.setColor(radarColor); - - //绘制填充的内容 - mValuePaint = new Paint(); - mValuePaint.setStrokeWidth(dpToPixel(2)); - mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE); - - - //绘制外部容器路径 - mLinePath = new Path(); - mValuePath = new Path(); - - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - //绘制外部容器 - drawLines(canvas); - //绘制每部填充 - drawRegion(canvas,mPercent); - } - - //绘制外部容器,顺时针,O-A-B-C - public void drawLines(Canvas canvas) { - mRadarPaint.setColor(radarColor); - mLinePath.reset(); - - float xa = 0;//(float) (mCenterX + width); - float ya = 0;//(float) (mCenterY); - mLinePath.moveTo(xa,ya); - - float xb = width; - float yb = high; - mLinePath.lineTo(xb, yb); - - float xc =0; - float yc = high; - mLinePath.lineTo(xc, yc); - - mLinePath.close(); - canvas.drawPath(mLinePath, mRadarPaint); - } - - //绘制覆盖图层,顺时针,O-Q-P-C - public void drawRegion(Canvas canvas, float percent) { - if (alarmValue>mPercent) { - mValuePaint.setColor(valueColor); - }else { - mValuePaint.setColor(alarmColor); - } - - //直线CB与QH,求出交点坐标P, - //其中H点=(是以B点的Y坐标,Q点的X坐标),起始就是QP的延长线 - float xa=(1f-percent)*width; - float ya=(1f-percent)*high; - float xb=width; - float yb=high; - float xc=(1f-percent)*width; - float yc=high; - - mValuePath.moveTo(xa, ya); - mValuePath.lineTo(xb, yb); - mValuePath.lineTo(xc, yc); - - - mValuePath.close(); - canvas.drawPath(mValuePath, mValuePaint); - } - - //求两直线相交的坐标 - - public void setRadarColor(int radarColor) { - this.radarColor = radarColor; - } - - public void setValueColor(int valueColor) { - this.valueColor = valueColor; - } - - public void setAlarmColor(int alarmColor) { - this.alarmColor = alarmColor; - } - - public void setAlarmValue(float alarmValue) { - this.alarmValue = alarmValue; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java deleted file mode 100644 index a0fc9ed..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.bg7yoz.ft8cn.ui; -/** - * 瀑布图自定义控件。 - * - * @author BGY70Z - * @date 2023-03-20 - */ - -import static android.graphics.Bitmap.Config.ARGB_8888; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Shader; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bg7yoz.ft8cn.Ft8Message; -import com.bg7yoz.ft8cn.timer.UtcTimer; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -public class WaterfallView extends View { - private int blockHeight = 2;//色块高度 - private float freq_width = 1;//频率的宽度 - private final int cycle = 2; - private final int symbols = 93; - private int lastSequential = 0; - private Bitmap lastBitMap = null; - private Canvas _canvas; - private final Paint linePaint = new Paint(); - private Paint touchPaint = new Paint(); - private final Paint fontPaint = new Paint(); - private final Paint messagePaint = new Paint(); - private final Paint messagePaintBack = new Paint();//消息背景 - private final Paint utcPaint = new Paint(); - Paint linearPaint = new Paint(); - private final Paint utcPainBack = new Paint(); - private float pathStart = 0; - private float pathEnd = 0; - - private int touch_x = -1; - private int freq_hz = -1; - private boolean drawMessage = false;//是否画消息内容 - - public WaterfallView(Context context) { - super(context); - } - - public WaterfallView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - public WaterfallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - ArrayList messages= new ArrayList<>(); - - - /** - * 把dp值转换为像素点 - * - * @param dp dp值 - * @return 像素点 - */ - private int dpToPixel(int dp) { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp - , getResources().getDisplayMetrics()); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - setClickable(true); - blockHeight = getHeight() / (symbols * cycle); - freq_width = (float) getWidth() / 3000f; - lastBitMap = Bitmap.createBitmap(w, h, ARGB_8888); - _canvas = new Canvas(lastBitMap); - Paint blackPaint = new Paint(); - blackPaint.setColor(0xFF000000); - _canvas.drawRect(0, 0, w, h, blackPaint);//先把背景画黑,防止文字重叠 - - //linePaint = new Paint(); - linePaint.setColor(0xff990000); - touchPaint = new Paint(); - touchPaint.setColor(0xff00ffff); - touchPaint.setStrokeWidth(getResources().getDisplayMetrics().density); - - - //fontPaint = new Paint(); - fontPaint.setTextSize(dpToPixel(10)); - fontPaint.setColor(0xff00ffff); - fontPaint.setAntiAlias(true); - fontPaint.setDither(true); - fontPaint.setTextAlign(Paint.Align.LEFT); - - // messagePaint = new Paint(); - messagePaint.setTextSize(dpToPixel(11)); - messagePaint.setColor(0xff00ffff); - messagePaint.setAntiAlias(true); - messagePaint.setDither(true); - messagePaint.setStrokeWidth(0); - messagePaint.setStyle(Paint.Style.FILL_AND_STROKE); - messagePaint.setTextAlign(Paint.Align.CENTER); - - //messagePaintBack = new Paint(); - messagePaintBack.setTextSize(dpToPixel(11)); - messagePaintBack.setColor(0xff000000);//背景不透明 - messagePaintBack.setAntiAlias(true); - messagePaintBack.setDither(true); - messagePaintBack.setStrokeWidth(dpToPixel(3)); - messagePaintBack.setFakeBoldText(true); - messagePaintBack.setStyle(Paint.Style.FILL_AND_STROKE); - messagePaintBack.setTextAlign(Paint.Align.CENTER); - - //utcPaint = new Paint(); - utcPaint.setTextSize(dpToPixel(10)); - utcPaint.setColor(0xff00ffff);// - utcPaint.setAntiAlias(true); - utcPaint.setDither(true); - utcPaint.setStrokeWidth(0); - utcPaint.setStyle(Paint.Style.FILL_AND_STROKE); - utcPaint.setTextAlign(Paint.Align.LEFT); - - //utcPainBack = new Paint(); - utcPainBack.setTextSize(dpToPixel(10)); - utcPainBack.setColor(0xff000000);//背景不透明 - utcPainBack.setAntiAlias(true); - utcPainBack.setDither(true); - utcPainBack.setStrokeWidth(dpToPixel(4)); - utcPainBack.setStyle(Paint.Style.FILL_AND_STROKE); - utcPainBack.setTextAlign(Paint.Align.LEFT); - - - pathStart = blockHeight * 2; - pathEnd = blockHeight * 90; - if (pathEnd < 130 * getResources().getDisplayMetrics().density) {//为了保证能写的下 - pathEnd = 130 * getResources().getDisplayMetrics().density; - } - - super.onSizeChanged(w, h, oldw, oldh); - - } - - @SuppressLint("DefaultLocale") - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - canvas.drawBitmap(lastBitMap, 0, 0, null); - - //计算频率 - if (touch_x > 0) {//画触摸线 - freq_hz = Math.round(3000f * (float) touch_x / (float) getWidth()); - if (freq_hz > 2900) { - freq_hz = 2900; - } - if (freq_hz < 100) { - freq_hz = 100; - } - - if (touch_x > getWidth() / 2) { - fontPaint.setTextAlign(Paint.Align.RIGHT); - canvas.drawText(String.format("%dHz", freq_hz) - , touch_x - 10, 250, fontPaint); - } else { - fontPaint.setTextAlign(Paint.Align.LEFT); - canvas.drawText(String.format("%dHz", freq_hz) - , touch_x + 10, 250, fontPaint); - } - canvas.drawLine(touch_x, 0, touch_x, getHeight(), touchPaint); - - } - invalidate(); - } - - public void setWaveData(int[] data, int sequential, List msgs) { - if (drawMessage&& msgs!=null){//把需要画的消息复制出来防止多线程访问冲突 - messages=new ArrayList<>(msgs); - } - - if (data == null) { - return; - } - if (data.length <= 0) { - return; - } - if (lastBitMap == null) { - return; - } - - int[] colors = new int[data.length]; - - //画分割线 - if (sequential != lastSequential) { - Bitmap bitmap = Bitmap.createBitmap(lastBitMap, 0, 0, getWidth(), getHeight() - blockHeight); - _canvas.drawBitmap(bitmap, 0, blockHeight, linePaint); - bitmap.recycle(); - _canvas.drawRect(0, 0, getWidth(), getResources().getDisplayMetrics().density - , linePaint); - _canvas.drawText(UtcTimer.getTimeStr(UtcTimer.getSystemTime()), 50 - , 15 * getResources().getDisplayMetrics().density, utcPainBack); - _canvas.drawText(UtcTimer.getTimeStr(UtcTimer.getSystemTime()), 50 - , 15 * getResources().getDisplayMetrics().density, utcPaint); - } - lastSequential = sequential; - - //色块分布 - for (int i = 0; i < data.length; i++) { - - - if (data[i] < 128) {//低于一半的音量,用蓝色0~256 - colors[i] = 0xff000000 | (data[i] << 1); - } else if (data[i] < 192) { - colors[i] = 0xff0000ff | (((data[i] - 127)) << 10);//放大4倍 -// colors[i] = 0xff000000 | (data[i] * 2 * 256 + 255); - } else { - colors[i] = 0xff00ffff | (((data[i] - 127)) << 18);//放大4倍 - } - } - LinearGradient linearGradient = new LinearGradient(0, 0, getWidth() * 2, 0, colors - , null, Shader.TileMode.CLAMP); - //Paint linearPaint = new Paint(); - linearPaint.setShader(linearGradient); - Bitmap bitmap = Bitmap.createBitmap(lastBitMap, 0, 0, getWidth(), getHeight() - blockHeight); - _canvas.drawBitmap(bitmap, 0, blockHeight, linearPaint); - bitmap.recycle(); - _canvas.drawRect(0, 0, getWidth(), blockHeight, linearPaint); - - //消息有3种:普通、CQ、有我 - if (drawMessage && messages != null) { - drawMessage = false;//只画一遍 - fontPaint.setTextAlign(Paint.Align.LEFT); - for (Ft8Message msg : messages) { - - if (msg.inMyCall()) {//与我有关 - messagePaint.setColor(0xffffb2b2); - } else if (msg.checkIsCQ()) {//CQ - messagePaint.setColor(0xffeeee00); - } else { - messagePaint.setColor(0xff00ffff); - } - Path path = new Path(); - - path.moveTo(msg.freq_hz * freq_width, pathStart); - path.lineTo(msg.freq_hz * freq_width, pathEnd); - - - _canvas.drawTextOnPath(msg.getMessageText(true), path - , 0, 0, messagePaintBack);//消息背景 - _canvas.drawTextOnPath(msg.getMessageText(true), path - , 0, 0, messagePaint);//消息 - - } - } - - - } - - public void setTouch_x(int touch_x) { - this.touch_x = touch_x; - } - - public void setDrawMessage(boolean drawMessage) { - this.drawMessage = drawMessage; - } - - public int getFreq_hz() { - return freq_hz; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java deleted file mode 100644 index 0755f3d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java +++ /dev/null @@ -1,306 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -import android.annotation.SuppressLint; -import android.media.AudioFormat; -import android.util.Log; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -/** - * 录音类。通过AudioRecord对象来实现录音。 - * HamRecorder录音的数据通过监听类GetVoiceData来实现。HamRecorder实例中有一个监听器列表onGetVoiceList。 - * 当有录音数据后,HamRecorder会触发监听器列表中各监听器的OnReceiveData回调。 - * 制作此类的目的,是防止FT8各录音时序因录音启动时间的问题,造成重叠创建录音对象或录音的时长达不到一个时序的时长(15秒) - *

- * @author BG7YOZ - * @date 2022-05-31 - */ - -public class HamRecorder { - private static final String TAG = "HamRecorder"; - //private int bufferSize = 0;//最小缓冲区大小 - private static final int sampleRateInHz = 12000;//采样率 - private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; //单声道 - //private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //量化位数 - private static final int audioFormat = AudioFormat.ENCODING_PCM_FLOAT; //量化位数 - - //private AudioRecord audioRecord = null;//AudioRecord对象 - private boolean isRunning = false;//是否处于录音的状态。 - - private final ArrayList voiceDataMonitorList = new ArrayList<>();//监听回调列表,在监听回调中获取数据。 - private OnVoiceMonitorChanged onVoiceMonitorChanged=null; - - private boolean isMicRecord=true; - private MicRecorder micRecorder=new MicRecorder(); - - - public HamRecorder(OnVoiceMonitorChanged onVoiceMonitorChanged){ - this.onVoiceMonitorChanged=onVoiceMonitorChanged; - } - - - public void setDataFromMic(){ - isMicRecord=true; - startRecord(); - } - public void setDataFromLan(){ - isMicRecord=false; - micRecorder.stopRecord(); - } - - /** - * 当接收到音频数据,所要处理的事情 - * @param bufferLen 数据的长度 - * @param buffer 数据缓冲区 - */ - public void doOnWaveDataReceived(int bufferLen,float[] buffer){ - if (!isRunning) return; - for (int i = 0; i < voiceDataMonitorList.size(); i++) { - //逐个监听器调用回调,把数据提供给回调函数 - if (voiceDataMonitorList.get(i)!=null) { - voiceDataMonitorList.get(i).onHamRecord.OnReceiveData(buffer, bufferLen); - } - } - - //doDataMonitorChanged(); - } - - - /** - * 是否处于录音状态 - * - * @return boolean,是否处于录音状态 - */ - public boolean isRunning() { - return isRunning; - } - - /** - * 开始录音,此方法使设备一直处于录音状态,录音数据的获取通过监听器类GetVoiceData来实现。 - * 录音对象在读取到数据(audioRecord.read)后,把监听器列表中的所有监听器的OnReceiveData回调都调用一次。 - * 录音的状态在isRecording中。 - */ - @SuppressLint("MissingPermission") - public void startRecord() { - if (isMicRecord){//如果是用MIC采集声音 - micRecorder.start(); - micRecorder.setOnDataListener(new MicRecorder.OnDataListener() { - @Override - public void onDataReceived(float[] data, int len) { - doOnWaveDataReceived(len,data); - } - }); - } - isRunning=true; - - } - - private void doDataMonitorChanged(){ - if (onVoiceMonitorChanged!=null){ - onVoiceMonitorChanged.onMonitorChanged(voiceDataMonitorList.size()); - } - } - /** - * 删除数据监听器 - * @param monitor 数据监听器 - */ - public void deleteVoiceDataMonitor(VoiceDataMonitor monitor) { - voiceDataMonitorList.remove(monitor); - doDataMonitorChanged(); - } - - /** - * 获取监听器的数量 - * @return 返回数量 - */ - public int getVoiceMonitorCount(){ - return voiceDataMonitorList.size(); - } - - /** - * 获取监听器的列表 - * @return 监听器列表 - */ - public ArrayList getVoiceDataMonitors(){ - return this.voiceDataMonitorList; - } - - /** - * 停止录音。当录音停止后,监听列表中的监听器全部删除。 - */ - public void stopRecord() { - micRecorder.stopRecord(); - isRunning = false; - } - - /** - * 获取录音数据的方法,通过加载数据监听器(VoiceDataMonitor)的方法实现。 - * 录音数据在OnGetVoiceDataDone回调中,当录音达到指定的时长(毫秒)触发。 - * 获取录音,是给录音对象加载一个监听器对象,在监听器的OnReceiveData回调中获取数据,当数据达到预期的数量时, - * 触发OnGetVoiceDataDone回调。该回调动作在另一个线程中,要注意UI的处理。 - * 监听有两种模式:一次性、循环。 - * 一次性:获取数据后,此监听器自动删除,不再触发。 - * 循环,监听器始终存在,获取数据后,重新复位数据,进入下一次监听状态。直到录音停止,监听器才被删除。 - * duration毫秒 - * - * @param duration 录音数据的时长(毫秒) - * @param afterDoneRemove 获取录音后是否删除监听器,false:循环获取录音数据。 - * @param getVoiceDataDone 当录音数据达到指定的时长后,触发此回调 - */ - public VoiceDataMonitor getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone) { - if (isRunning) { - VoiceDataMonitor dataMonitor = new VoiceDataMonitor(duration, this - , afterDoneRemove, getVoiceDataDone); - dataMonitor.voiceDataMonitor = dataMonitor;//用于监听器删除自己用。 - voiceDataMonitorList.add(dataMonitor); - doDataMonitorChanged(); - return dataMonitor; - } else { - return null; - } - } - - /** - * 监听器类,用于录音数据的获取。 - * 当监听类,需要设定录音的时长(毫秒),当达到指定的时长后,会产生一个OnGetVoiceDataDone回调,在此回调中,可以获得 - * 该时长的录音数据。可以设定此监听是一次性的(afterDoneRemove=true),还是循环往复的(afterDoneRemove=false)。 - * 一次性的,就是监听达到指定时长后,就不继续监听了,录音实例会把该监听删除。 - * 循环往复,就是监听到指定时长后,复位,继续重新监听。此模式方便形成波表数据。 - */ - static class VoiceDataMonitor { - private final String TAG = "GetVoiceData"; - private final float[] voiceData;//录音数据。大小由时长、采样率、采样位决定的。 - private int dataCount;//计数器,当前数据的获取量 - - //onHamRecord是当录音对象有数据时触发的回调,通过该回调填充voiceData缓冲区,当缓冲区满时,触发OnGetVoiceDataDone回调。 - public OnHamRecord onHamRecord; - //getVoiceData是本监听器的地址,用于在录音对象的监听列表中删除本监听器。 - // 在GetVoiceData构建后,注意!!!一定要对该变量赋值!否则无法删除本监听器。 - public VoiceDataMonitor voiceDataMonitor = null; - - /** - * 监听类,用于录音数据的获取 - * GetVoiceData类的构建方法。此类是用于添加到录音类HamRecorder中onGetVoiceList,当有录音数据返回时,产生回调。 - * 此类的目的就是录音时,可以有多个对象从录音中获取数据,而不产生冲突。 - * - * @param duration 获取录音数据的时长(毫秒)。 - * @param hamRecorder 录音类的实例。方便删除本监听器等操作。 - * @param afterDoneRemove 当达到录音的时长后,是否移除本监听实例,true:移除,false:不移除,循环监听 - * @param onGetVoiceDataDone 达到录音的时长后,触发此回调。为了防止占用太多录音的时间,此回调在另一个线程。 - */ - public VoiceDataMonitor(int duration, HamRecorder hamRecorder, boolean afterDoneRemove - , OnGetVoiceDataDone onGetVoiceDataDone) { - //时长,毫秒 - //宿主对象,方便用词对象调用删除数据获取动作列表中的本实例 - - dataCount = 0;//当前的数据获取量 - //生成预期大小中的数据缓冲区。 - //因为是16Bit采样,所以byte*2。 - //voiceData = new byte[duration * HamRecorder.sampleRateInHz * 2 / 1000]; - voiceData = new float[duration * HamRecorder.sampleRateInHz / 1000]; - - //当有录音数据时触发的回调函数。 - onHamRecord = new OnHamRecord() { - @Override - public void OnReceiveData(float[] data, int size) { - for (int i = 0; (i < size) && (dataCount < voiceData.length); i++) { - voiceData[dataCount] = data[i];//把录音缓冲区的数据搬运到本监听器中来 - dataCount++; - } - if (dataCount >= (voiceData.length)) {//当数据量达到所需要的。发起回调。 -// new Thread(new Runnable() {//以新的线程运行,防止占用过多的录音时间。 -// @Override -// public void run() { - onGetVoiceDataDone.onGetDone(voiceData); -// } -// }).start(); - - if (afterDoneRemove) {//如果是一次性的获取数据,则在录音对象中的监听列表中删除此监听回调。 - hamRecorder.deleteVoiceDataMonitor(voiceDataMonitor); - } else { - dataCount = 0;//如果是循环录音,则复位计数器。 - } - } - } - }; - - } - - } - - /** - * 类方法,把数据保存到文件中去,是临时文件名。 - * @param data 数据 - * @return 返回生成的临时文件名。 - */ - public static String saveDataToFile(byte[] data) { - String audioFileName = null; - File recordingFile; - try { - //生成临时文件名 - recordingFile = File.createTempFile("Audio", ".wav", null); - audioFileName = recordingFile.getPath(); - - //数据流文件 - DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(audioFileName))); - //写Wav文件头 - new WriteWavHeader(data.length, sampleRateInHz, channelConfig, audioFormat).writeHeader(dos); - for (int i = 0; i < data.length; i++) { - dos.write(data[i]); - } - Log.d(TAG, String.format("生成文件结束(%d字节,%.2f秒),文件:%s", data.length + 44 - , ((float) data.length / 2 / sampleRateInHz), audioFileName)); - dos.close();//关闭文件流 - - - } catch (IOException e) { - Log.e(TAG, String.format("生成临时文件出错!%s", e.getMessage())); - } - - return audioFileName; - } - - /** - * 把原始的声音数据转换成16位的数组数据。 - * @param buffer 原始的声音数据(8位) - * @return 返回16位的int格式数组 - */ - public static int[] byteDataTo16BitData(byte[] buffer){ - int[] data=new int[buffer.length /2]; - for (int i = 0; i < buffer.length/2; i++) { - int res = (buffer[i*2] & 0x000000FF) | (((int) buffer[i*2+1]) << 8); - data[i]=res; - } - return data; - } - - /** - * 把原始的声音数据转换成浮点数组数据 - * @param bytes 原始的声音数据(float) - * @return 转换成float数组 - */ - public static float[] getFloatFromBytes(byte[] bytes) { - float[] floats = new float[bytes.length / 4]; - DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); - for (int i = 0; i < floats.length; i++) { - try { - floats[i] = dis.readFloat(); - } catch (IOException e) { - e.printStackTrace(); - break; - } - } - try { - dis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return floats; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java deleted file mode 100644 index d47d79a..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.bg7yoz.ft8cn.wave; -/** - * 使用Mic录音的操作。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.annotation.SuppressLint; -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.MediaRecorder; -import android.util.Log; - -import com.bg7yoz.ft8cn.GeneralVariables; -import com.bg7yoz.ft8cn.R; -import com.bg7yoz.ft8cn.ui.ToastMessage; - -public class MicRecorder { - private static final String TAG = "MicRecorder"; - private int bufferSize = 0;//最小缓冲区大小 - private static final int sampleRateInHz = 12000;//采样率 - private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; //单声道 - //private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //量化位数 - private static final int audioFormat = AudioFormat.ENCODING_PCM_FLOAT; //量化位数 - - private AudioRecord audioRecord = null;//AudioRecord对象 - private boolean isRunning = false;//是否处于录音的状态。 - private OnDataListener onDataListener; - - public interface OnDataListener{ - void onDataReceived(float[] data,int len); - } - - @SuppressLint("MissingPermission") - public MicRecorder(){ - //计算最小缓冲区 - bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); -// audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz - audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRateInHz - , channelConfig, audioFormat, bufferSize);//创建AudioRecorder对象 - } - - public void start(){ - if (isRunning) return; - - float[] buffer = new float[bufferSize]; - try { - audioRecord.startRecording();//开始录音 - }catch (Exception e){ - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - R.string.recorder_cannot_record),e.getMessage())); - Log.d(TAG, "startRecord: "+e.getMessage() ); - } - - isRunning = true; - - new Thread(new Runnable() { - @Override - public void run() { - while (isRunning) { - //判断是否处于录音状态,state!=3,说明没有处于录音的状态 - if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { - isRunning = false; - Log.d(TAG, String.format("录音失败,状态码:%d", audioRecord.getRecordingState())); - break; - } - - //读录音的数据 - int bufferReadResult = audioRecord.read(buffer, 0, bufferSize,AudioRecord.READ_BLOCKING); - - if (onDataListener!=null){ - onDataListener.onDataReceived(buffer,bufferReadResult); - } - } - try { - if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { - audioRecord.stop();//停止录音 - } - }catch (Exception e){ - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - R.string.recorder_stop_record_error),e.getMessage())); - Log.d(TAG, "startRecord: "+e.getMessage() ); - } - } - }).start(); - } - - /** - * 停止录音。当录音停止后,监听列表中的监听器全部删除。 - */ - public void stopRecord() { - isRunning = false; - } - - public OnDataListener getOnDataListener() { - return onDataListener; - } - - public void setOnDataListener(OnDataListener onDataListener) { - this.onDataListener = onDataListener; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java deleted file mode 100644 index 350e75b..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -/** - *定义音频录音结束后的回调接口。 - *回调接口主要有2个,录音开始前,录音开始后。 - * 注意!!!录音采用多线程的方式,此处的回调不在主线程中,如果回调中有UI操作的话,要使用runOnUiThread方法,防止界面锁死。 - * - * @author BG7YOZ - * @date 2022.5.7 - */ - -public interface OnAudioRecorded { - /** - * 录音开始前的回调函数。 - * @param audioFileName 生成的Wav文件名 - */ - void beginAudioRecord(String audioFileName); - - /** - * 录音结束后的回调函数。 - * @param audioFileName Wav文件名 - * @param dataSize 录音数据的大小,byte[]格式,不包括wav文件头的长度,如果要算wav文件长度,在此基础上+44。 - * @param duration 实际录音的时长(秒) - */ - void endAudioRecorded(String audioFileName,long dataSize,float duration); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java deleted file mode 100644 index 5f95c2a..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -/** - * 获取音频的回调 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnGetVoiceDataDone { - void onGetDone(float[] data); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java deleted file mode 100644 index 1d714f8..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -/** - * 接收到音频的回调。 - * @author BGY70Z - * @date 2023-03-20 - */ -public interface OnHamRecord { - void OnReceiveData(float[] data,int size); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java deleted file mode 100644 index 938dc96..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -public interface OnVoiceMonitorChanged { - void onMonitorChanged(int count); -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java deleted file mode 100644 index a80ac1d..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -/** - * 波形文件(.wav)的读写操作,支持16bit和8bitPCM编码的单双声道文件操作 - * 已经弃用,FT8CN目前不采用文件的方式。 - * @author BGY70Z - * @date 2023-03-20 - */ -public class WaveAccess { - public static final String VERSION = "1.1"; -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java deleted file mode 100644 index 19a7243..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.bg7yoz.ft8cn.wave; - -/** - * Wave文件操作所用的常量。 - * 已经弃用。FT8CN目前不采用文件方式处理音频。 - * @author BGY70Z - * @date 2023-03-20 - */ -public final class WaveConstants { - static public int LENCHUNKDESCRIPTOR = 4; - static public int LENCHUNKSIZE = 4; - static public int LENWAVEFLAG = 4; - static public int LENFMTSUBCHUNK = 4; - static public int LENSUBCHUNK1SIZE = 4; - static public int LENAUDIOFORMAT = 2; - static public int LENNUMCHANNELS = 2; - static public int LENSAMPLERATE = 2; - static public int LENBYTERATE = 4; - static public int LENBLOCKLING = 2; - static public int LENBITSPERSAMPLE = 2; - static public int LENDATASUBCHUNK = 4; - static public int LENSUBCHUNK2SIZE = 4; - - public static String CHUNKDESCRIPTOR = "RIFF"; - public static String WAVEFLAG = "WAVE"; - public static String FMTSUBCHUNK = "fmt "; - public static String DATASUBCHUNK = "data"; -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java deleted file mode 100644 index 8bda112..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.bg7yoz.ft8cn.wave; -/** - * 读取Wave文件的操作。 - * 已经弃用,FT8CN不采用音频文件的方法。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; - -@SuppressWarnings("unused") -public class WaveFileReader { - private String filename = null; - private int[][] data = null; - - private int len = 0; - - private String chunkdescriptor = null; - private long chunksize = 0; - private String waveflag = null; - private String fmtsubchunk = null; - private long subchunk1size = 0; - private int audioformat = 0; - private int numchannels = 0; - private long samplerate = 0; - private long byterate = 0; - private int blockalign = 0; - private int bitspersample = 0; - private String datasubchunk = null; - private long subchunk2size = 0; - private FileInputStream fis = null; - private BufferedInputStream bis = null; - - private boolean issuccess = false; - - public WaveFileReader(String filename) { - - this.initReader(filename); - } - - // 判断是否创建wav读取器成功 - public boolean isSuccess() { - return issuccess; - } - - // 获取每个采样的编码长度,8bit或者16bit - public int getBitPerSample() { - return this.bitspersample; - } - - // 获取采样率 - public long getSampleRate() { - return this.samplerate; - } - - // 获取声道个数,1代表单声道 2代表立体声 - public int getNumChannels() { - return this.numchannels; - } - - // 获取数据长度,也就是一共采样多少个 - public int getDataLen() { - return this.len; - } - - // 获取数据 - // 数据是一个二维数组,[n][m]代表第n个声道的第m个采样值 - public int[][] getData() { - return this.data; - } - - private void initReader(String filename) { - this.filename = filename; - - try { - fis = new FileInputStream(this.filename); - bis = new BufferedInputStream(fis); - - this.chunkdescriptor = readString(WaveConstants.LENCHUNKDESCRIPTOR); - if (!chunkdescriptor.endsWith("RIFF")) - throw new IllegalArgumentException("RIFF miss, " + filename + " is not a wave file."); - - this.chunksize = readLong(); - this.waveflag = readString(WaveConstants.LENWAVEFLAG); - if (!waveflag.endsWith("WAVE")) - throw new IllegalArgumentException("WAVE miss, " + filename + " is not a wave file."); - - this.fmtsubchunk = readString(WaveConstants.LENFMTSUBCHUNK); - if (!fmtsubchunk.endsWith("fmt ")) - throw new IllegalArgumentException("fmt miss, " + filename + " is not a wave file."); - - this.subchunk1size = readLong(); - this.audioformat = readInt(); - this.numchannels = readInt(); - this.samplerate = readLong(); - this.byterate = readLong(); - this.blockalign = readInt(); - this.bitspersample = readInt(); - - this.datasubchunk = readString(WaveConstants.LENDATASUBCHUNK); - if (!datasubchunk.endsWith("data")) - throw new IllegalArgumentException("data miss, " + filename + " is not a wave file."); - this.subchunk2size = readLong(); - - this.len = (int) (this.subchunk2size / (this.bitspersample / 8) / this.numchannels); - - this.data = new int[this.numchannels][this.len]; - - // 读取数据 - for (int i = 0; i < this.len; ++i) { - for (int n = 0; n < this.numchannels; ++n) { - if (this.bitspersample == 8) { - this.data[n][i] = bis.read(); - } else if (this.bitspersample == 16) { - this.data[n][i] = this.readInt(); - } - } - } - - issuccess = true; - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (bis != null) - bis.close(); - if (fis != null) - fis.close(); - } catch (Exception e1) { - e1.printStackTrace(); - } - } - } - - private String readString(int len) { - byte[] buf = new byte[len]; - try { - if (bis.read(buf) != len) - throw new IOException("no more data!!!"); - } catch (IOException e) { - e.printStackTrace(); - } - return new String(buf); - } - - private int readInt() { - byte[] buf = new byte[2]; - int res = 0; - try { - if (bis.read(buf) != 2) - throw new IOException("no more data!!!"); - res = (buf[0] & 0x000000FF) | (((int) buf[1]) << 8); - } catch (IOException e) { - e.printStackTrace(); - } - return res; - } - - private long readLong() { - long res = 0; - try { - long[] l = new long[4]; - for (int i = 0; i < 4; ++i) { - l[i] = bis.read(); - if (l[i] == -1) { - throw new IOException("no more data!!!"); - } - } - res = l[0] | (l[1] << 8) | (l[2] << 16) | (l[3] << 24); - } catch (IOException e) { - e.printStackTrace(); - } - return res; - } - - private byte[] readBytes(int len) { - byte[] buf = new byte[len]; - try { - if (bis.read(buf) != len) - throw new IOException("no more data!!!"); - } catch (IOException e) { - e.printStackTrace(); - } - return buf; - } - - public static int[] readSingleChannel(String filename) { - if (filename == null || filename.length() == 0) { - return null; - } - try { - WaveFileReader reader = new WaveFileReader(filename); - int[] res = reader.getData()[0]; - return res; - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java deleted file mode 100644 index dcc46bd..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.bg7yoz.ft8cn.wave; -/** - * 把音频保存成wave文件的操作。 - * 已经弃用,FT8CN目前不做音频的保存操作。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -public class WaveFileWriter { - private String filename = null; - private FileOutputStream fos = null; - private BufferedOutputStream bos = null; - - private long chunksize = 0; - private long subchunk1size = 0; - private int audioformat = 0; - private int numchannels = 0; - private long samplerate = 0; - private long byterate = 0; - private int blockalign = 0; - private int bitspersample = 0; - private long subchunk2size = 0; - - public WaveFileWriter(String filename, int[][] data, long samplerate,int bitspersample) { - this.initWriter(filename, data, 0, data[0].length, samplerate,bitspersample); - } - - public WaveFileWriter(String filename, int[][] data, int offset, int len, long samplerate,int bitspersample) { - this.initWriter(filename, data, offset, len, samplerate,bitspersample); - } - - public void initWriter(String filename, int[][] data, int offset, int len, long samplerate,int bitspersample) { - this.filename = filename; - - try { - fos = new FileOutputStream(this.filename); - bos = new BufferedOutputStream(fos); - - // int datalen = data[0].length; - int datalen = len; - - this.samplerate = samplerate; - // this.bitspersample = bitspersample; - this.bitspersample = bitspersample; - this.numchannels = data.length; - this.subchunk2size = this.numchannels * (this.bitspersample / 8) * datalen; - this.subchunk1size = 16; - this.audioformat = 1; // PCM - this.byterate = this.samplerate * this.bitspersample * this.numchannels / 8; - this.blockalign = this.numchannels * this.bitspersample / 8; - - this.chunksize = this.subchunk2size + 8 + this.subchunk1size + 8 + 4; - - writeString(WaveConstants.CHUNKDESCRIPTOR, WaveConstants.LENCHUNKDESCRIPTOR); - writeLong(this.chunksize); - writeString(WaveConstants.WAVEFLAG, WaveConstants.LENWAVEFLAG); - writeString(WaveConstants.FMTSUBCHUNK, WaveConstants.LENFMTSUBCHUNK); - writeLong(this.subchunk1size); - writeInt(this.audioformat); - writeInt(this.numchannels); - writeLong(this.samplerate); - writeLong(this.byterate); - writeInt(this.blockalign); - writeInt(this.bitspersample); - writeString(WaveConstants.DATASUBCHUNK, WaveConstants.LENDATASUBCHUNK); - writeLong(this.subchunk2size); - for (int i = 0; i < datalen; ++i) { - for (int n = 0; n < this.numchannels; ++n) { - if (this.bitspersample == 16) { - writeInt(data[n][i + offset]); - } else { - writeByte((byte) data[n][i + offset]); - } - } - } - bos.flush(); - fos.flush(); - bos.close(); - fos.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void close() { - - } - - private void writeString(String str, int len) { - if (str.length() != len) { - throw new IllegalArgumentException("length not match!!!"); - } - byte[] bt = str.getBytes(); - try { - bos.write(bt); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void writeByte(byte data) { - try { - bos.write(new byte[] { data }, 0, 1); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void writeInt(int data) { - byte[] buf = new byte[2]; - buf[1] = (byte) (data >>> 8); - buf[0] = (byte) (data & 0xFF); - try { - bos.write(buf); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void writeLong(long data) { - byte[] buf = new byte[4]; - buf[0] = (byte) (data & 0x00ff); - buf[1] = (byte) ((data >> 8) & 0x00ff); - buf[2] = (byte) ((data >> 16) & 0x00ff); - buf[3] = (byte) ((data >> 24) & 0x00ff); - try { - bos.write(buf); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static boolean saveSingleChannel(String filename, int[] data, long samplerate,int bitspersample) { - int[][] datar = new int[1][]; - datar[0] = data; - WaveFileWriter writer = new WaveFileWriter(filename, datar, samplerate,bitspersample); - writer.close(); - return true; - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java deleted file mode 100644 index 243a47c..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.bg7yoz.ft8cn.wave; -/** - * 写WAVE文件的头。 - * 已经弃用。FT8CN不做音频文件的操作。 - * @author BGY70Z - * @date 2023-03-20 - */ - -import android.media.AudioFormat; -import android.util.Log; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.RandomAccessFile; - -public class WriteWavHeader { - private int samplesBits; - private final int channels; - private final int totalAudioLen; - private final int longSampleRate; - String TAG = "HamAudioRecorder";//调试用标志 - - public WriteWavHeader( int totalAudioLen, int longSampleRate, int channels, int samplesBits) { - if (samplesBits == AudioFormat.ENCODING_PCM_16BIT) - this.samplesBits = 16; - else if (samplesBits == AudioFormat.ENCODING_PCM_8BIT) - this.samplesBits = 8; - - if (channels == AudioFormat.CHANNEL_IN_STEREO) - this.channels = 2; - else - this.channels = 1; - this.totalAudioLen = totalAudioLen; - this.longSampleRate = longSampleRate; - - } - - private byte[] makeWaveHeader(){ - int file_size = totalAudioLen + 44 - 8;//文件大小,刨除前面RIFF和file_size - byte[] header = new byte[44]; - header[0] = 'R'; // RIFF/WAVE header - header[1] = 'I'; - header[2] = 'F'; - header[3] = 'F'; - header[4] = (byte) (file_size & 0xff); - header[5] = (byte) ((file_size >> 8) & 0xff); - header[6] = (byte) ((file_size >> 16) & 0xff); - header[7] = (byte) ((file_size >> 24) & 0xff); - header[8] = 'W'; - header[9] = 'A'; - header[10] = 'V'; - header[11] = 'E'; - header[12] = 'f'; // 'fmt ' chunk - header[13] = 'm'; - header[14] = 't'; - header[15] = ' '; - header[16] = 16; // 4 bytes: size of 'fmt ' chunk - header[17] = 0; - header[18] = 0; - header[19] = 0; - header[20] = 1; // format = 1表示PCM编码 - header[21] = 0; - header[22] = (byte) channels;//1为单声道,2是双声道 - header[23] = 0; - header[24] = (byte) (longSampleRate & 0xff); - header[25] = (byte) ((longSampleRate >> 8) & 0xff); - header[26] = (byte) ((longSampleRate >> 16) & 0xff); - header[27] = (byte) ((longSampleRate >> 24) & 0xff); - - header[28] = (byte) (samplesBits & 0xff); - header[29] = (byte) ((samplesBits >> 8) & 0xff); - header[30] = (byte) ((samplesBits >> 16) & 0xff); - header[31] = (byte) ((samplesBits >> 24) & 0xff); - - //2字节数据块长度(每个样本的字节数=通道数*每次采样得到的样本位数/8) - if (samplesBits==AudioFormat.ENCODING_PCM_16BIT) { - header[32] = (byte) (channels * samplesBits); - header[33] = 0; - header[34] = 16; // 每个采样点的位数 - header[35] = 0; - }else if (samplesBits==AudioFormat.ENCODING_PCM_8BIT){ - header[32] = (byte) (channels ); - header[33] = 0; - header[34] = 8; // 每个采样点的位数 - header[35] = 0; - }else { - header[32] = (byte) (channels * samplesBits / 8); - header[33] = 0; - header[34] = (byte) samplesBits; //每个采样点的位数 - header[35] = 0; - } - - - header[36] = 'd'; - header[37] = 'a'; - header[38] = 't'; - header[39] = 'a'; - //pcm音频数据大小 - header[40] = (byte) (totalAudioLen & 0xff); - header[41] = (byte) ((totalAudioLen >> 8) & 0xff); - header[42] = (byte) ((totalAudioLen >> 16) & 0xff); - header[43] = (byte) ((totalAudioLen >> 24) & 0xff); - return header; - } - - public void writeHeader(DataOutputStream dos) { - - try { - dos.write(makeWaveHeader()); - } catch (IOException e) { - Log.e(TAG, String.format("创建wav文件头(WriteWavHeader)错误!%s", e.getMessage())); - } - - } - public void modifyHeader(String fileName) { - - try { - RandomAccessFile raf=new RandomAccessFile(fileName,"rw"); - raf.seek(0); - raf.write(makeWaveHeader()); - raf.close(); - } catch (IOException e) { - Log.e(TAG, String.format("修改wav文件头(modifyHeader)错误!%s", e.getMessage())); - } - - } - -} From a1e5c8c4ebc9b2710a5af80d245eedc4540a7465 Mon Sep 17 00:00:00 2001 From: wangg <115208948+N0BOY@users.noreply.github.com> Date: Tue, 15 Aug 2023 00:49:10 -0400 Subject: [PATCH 3/3] file sync --- .../java/com/bg7yoz/ft8cn/FAQActivity.java | 46 + .../main/java/com/bg7yoz/ft8cn/FT8Common.java | 25 + .../java/com/bg7yoz/ft8cn/Ft8Message.java | 501 +++++ .../com/bg7yoz/ft8cn/GeneralVariables.java | 587 +++++ .../java/com/bg7yoz/ft8cn/MainActivity.java | 660 ++++++ .../java/com/bg7yoz/ft8cn/MainViewModel.java | 921 ++++++++ .../java/com/bg7yoz/ft8cn/MessageHashMap.java | 55 + .../ft8cn/bluetooth/BluetoothConstants.java | 63 + .../bluetooth/BluetoothSerialListener.java | 13 + .../bluetooth/BluetoothSerialService.java | 213 ++ .../bluetooth/BluetoothSerialSocket.java | 127 ++ .../BluetoothStateBroadcastReceive.java | 109 + .../ft8cn/callsign/CallsignDatabase.java | 277 +++ .../ft8cn/callsign/CallsignFileOperation.java | 140 ++ .../bg7yoz/ft8cn/callsign/CallsignInfo.java | 79 + .../OnAfterQueryCallsignLocation.java | 12 + .../ft8cn/connector/BaseRigConnector.java | 103 + .../connector/BluetoothRigConnector.java | 184 ++ .../ft8cn/connector/CableConnector.java | 75 + .../ft8cn/connector/CableSerialPort.java | 374 ++++ .../bg7yoz/ft8cn/connector/ConnectMode.java | 24 + .../bg7yoz/ft8cn/connector/FlexConnector.java | 325 +++ .../ft8cn/connector/IComWifiConnector.java | 112 + .../connector/OnConnectorStateChanged.java | 12 + .../com/bg7yoz/ft8cn/count/CountDbOpr.java | 555 +++++ .../com/bg7yoz/ft8cn/count/CountFragment.java | 93 + .../bg7yoz/ft8cn/count/CountInfoAdapter.java | 260 +++ .../ft8cn/database/AfterInsertQSLData.java | 5 + .../bg7yoz/ft8cn/database/ControlMode.java | 33 + .../bg7yoz/ft8cn/database/DatabaseOpr.java | 1912 ++++++++++++++++ .../com/bg7yoz/ft8cn/database/DxccObject.java | 49 + .../ft8cn/database/OnAfterQueryConfig.java | 11 + .../database/OnAfterQueryFollowCallsigns.java | 12 + .../ft8cn/database/OnAfterWriteConfig.java | 10 + .../bg7yoz/ft8cn/database/OnGetCallsign.java | 10 + .../bg7yoz/ft8cn/database/OperationBand.java | 158 ++ .../bg7yoz/ft8cn/database/RigNameList.java | 151 ++ .../com/bg7yoz/ft8cn/flex/FlexCommand.java | 66 + .../com/bg7yoz/ft8cn/flex/FlexMeterInfos.java | 149 ++ .../com/bg7yoz/ft8cn/flex/FlexMeterList.java | 112 + .../com/bg7yoz/ft8cn/flex/FlexMeterType.java | 10 + .../java/com/bg7yoz/ft8cn/flex/FlexRadio.java | 1412 ++++++++++++ .../bg7yoz/ft8cn/flex/FlexRadioFactory.java | 185 ++ .../bg7yoz/ft8cn/flex/FlexResponseStyle.java | 16 + .../com/bg7yoz/ft8cn/flex/RadioTcpClient.java | 224 ++ .../com/bg7yoz/ft8cn/flex/RadioUdpClient.java | 174 ++ .../main/java/com/bg7yoz/ft8cn/flex/VITA.java | 437 ++++ .../com/bg7yoz/ft8cn/floatview/FloatView.java | 381 ++++ .../ft8cn/floatview/FloatViewButton.java | 39 + .../com/bg7yoz/ft8cn/ft8listener/A91List.java | 31 + .../ft8cn/ft8listener/FT8SignalListener.java | 369 +++ .../bg7yoz/ft8cn/ft8listener/OnFt8Listen.java | 27 + .../ft8cn/ft8listener/ReBuildSignal.java | 28 + .../bg7yoz/ft8cn/ft8signal/FT8Package.java | 378 ++++ .../ft8cn/ft8transmit/FT8TransmitSignal.java | 1106 +++++++++ .../ft8cn/ft8transmit/FunctionOfTransmit.java | 74 + .../bg7yoz/ft8cn/ft8transmit/GenerateFT8.java | 259 +++ .../ft8cn/ft8transmit/OnDoTransmitted.java | 14 + .../ft8cn/ft8transmit/OnTransmitSuccess.java | 12 + .../bg7yoz/ft8cn/ft8transmit/QSLRecord.java | 100 + .../ft8cn/ft8transmit/QslRecordList.java | 100 + .../ft8cn/ft8transmit/TransmitCallsign.java | 59 + .../ft8cn/grid_tracker/GridInfoWindow.java | 118 + .../grid_tracker/GridMarkerInfoWindow.java | 158 ++ .../ft8cn/grid_tracker/GridOsmMapView.java | 951 ++++++++ .../grid_tracker/GridRecordInfoWindow.java | 89 + .../grid_tracker/GridTrackerMainActivity.java | 895 ++++++++ .../com/bg7yoz/ft8cn/html/HtmlContext.java | 219 ++ .../com/bg7yoz/ft8cn/html/ImportTaskList.java | 134 ++ .../com/bg7yoz/ft8cn/html/LogHttpServer.java | 1976 +++++++++++++++++ .../bg7yoz/ft8cn/icom/IComPacketTypes.java | 1033 +++++++++ .../com/bg7yoz/ft8cn/icom/IComWifiRig.java | 152 ++ .../com/bg7yoz/ft8cn/icom/IcomAudioUdp.java | 103 + .../com/bg7yoz/ft8cn/icom/IcomCivUdp.java | 110 + .../com/bg7yoz/ft8cn/icom/IcomControlUdp.java | 244 ++ .../com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java | 78 + .../com/bg7yoz/ft8cn/icom/IcomUdpBase.java | 430 ++++ .../com/bg7yoz/ft8cn/icom/IcomUdpClient.java | 226 ++ .../java/com/bg7yoz/ft8cn/log/HashTable.java | 117 + .../bg7yoz/ft8cn/log/LogCallsignAdapter.java | 199 ++ .../com/bg7yoz/ft8cn/log/LogFileImport.java | 117 + .../com/bg7yoz/ft8cn/log/LogQSLAdapter.java | 241 ++ .../bg7yoz/ft8cn/log/OnQueryQSLCallsign.java | 12 + .../ft8cn/log/OnQueryQSLRecordCallsign.java | 12 + .../bg7yoz/ft8cn/log/QSLCallsignRecord.java | 80 + .../java/com/bg7yoz/ft8cn/log/QSLRecord.java | 386 ++++ .../com/bg7yoz/ft8cn/log/QSLRecordStr.java | 180 ++ .../java/com/bg7yoz/ft8cn/log/SWLQsoList.java | 159 ++ .../ft8cn/maidenhead/MaidenheadGrid.java | 406 ++++ .../java/com/bg7yoz/ft8cn/rigs/BaseRig.java | 133 ++ .../bg7yoz/ft8cn/rigs/BaseRigOperation.java | 138 ++ .../java/com/bg7yoz/ft8cn/rigs/CRC16.java | 93 + .../bg7yoz/ft8cn/rigs/ElecraftCommand.java | 78 + .../com/bg7yoz/ft8cn/rigs/ElecraftRig.java | 174 ++ .../ft8cn/rigs/ElecraftRigConstant.java | 86 + .../bg7yoz/ft8cn/rigs/Flex6000Command.java | 72 + .../com/bg7yoz/ft8cn/rigs/Flex6000Rig.java | 144 ++ .../ft8cn/rigs/Flex6000RigConstant.java | 94 + .../com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java | 113 + .../com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java | 188 ++ .../bg7yoz/ft8cn/rigs/GuoHeRigConstant.java | 112 + .../com/bg7yoz/ft8cn/rigs/IcomCommand.java | 189 ++ .../java/com/bg7yoz/ft8cn/rigs/IcomRig.java | 275 +++ .../bg7yoz/ft8cn/rigs/IcomRigConstant.java | 280 +++ .../com/bg7yoz/ft8cn/rigs/InstructionSet.java | 22 + .../com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java | 144 ++ .../ft8cn/rigs/KenwoodTK90RigConstant.java | 135 ++ .../bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java | 194 ++ .../bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java | 193 ++ .../ft8cn/rigs/OnConnectReceiveData.java | 10 + .../bg7yoz/ft8cn/rigs/OnRigStateChanged.java | 14 + .../bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java | 201 ++ .../com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java | 242 ++ .../java/com/bg7yoz/ft8cn/rigs/XieGuRig.java | 240 ++ .../com/bg7yoz/ft8cn/rigs/Yaesu2Command.java | 22 + .../java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java | 156 ++ .../bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java | 84 + .../com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java | 190 ++ .../com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java | 190 ++ .../com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java | 184 ++ .../com/bg7yoz/ft8cn/rigs/Yaesu3Command.java | 136 ++ .../bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java | 132 ++ .../com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java | 191 ++ .../ft8cn/serialport/CdcAcmSerialDriver.java | 353 +++ .../ft8cn/serialport/Ch34xSerialDriver.java | 388 ++++ .../ft8cn/serialport/CommonUsbSerialPort.java | 318 +++ .../ft8cn/serialport/Cp21xxSerialDriver.java | 335 +++ .../ft8cn/serialport/FtdiSerialDriver.java | 432 ++++ .../bg7yoz/ft8cn/serialport/ProbeTable.java | 87 + .../serialport/ProlificSerialDriver.java | 582 +++++ .../serialport/SerialTimeoutException.java | 15 + .../com/bg7yoz/ft8cn/serialport/UsbId.java | 95 + .../ft8cn/serialport/UsbSerialDriver.java | 33 + .../ft8cn/serialport/UsbSerialPort.java | 261 +++ .../ft8cn/serialport/UsbSerialProber.java | 92 + .../ft8cn/serialport/util/MonotonicClock.java | 14 + .../util/SerialInputOutputManager.java | 257 +++ .../ft8cn/spectrum/SpectrumListener.java | 41 + .../com/bg7yoz/ft8cn/timer/OnUtcTimer.java | 14 + .../java/com/bg7yoz/ft8cn/timer/UtcTimer.java | 301 +++ .../bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java | 54 + .../ft8cn/ui/BauRateSpinnerAdapter.java | 63 + .../bg7yoz/ft8cn/ui/CallingListAdapter.java | 426 ++++ .../bg7yoz/ft8cn/ui/CallingListFragment.java | 416 ++++ .../bg7yoz/ft8cn/ui/ClearCacheDataDialog.java | 234 ++ .../com/bg7yoz/ft8cn/ui/ColumnarView.java | 164 ++ .../com/bg7yoz/ft8cn/ui/ConfigFragment.java | 1235 +++++++++++ .../com/bg7yoz/ft8cn/ui/FilterDialog.java | 95 + .../bg7yoz/ft8cn/ui/FlexMeterRulerView.java | 193 ++ .../ft8cn/ui/FlexRadioInfoFragment.java | 203 ++ .../java/com/bg7yoz/ft8cn/ui/FreqDialog.java | 150 ++ .../ft8cn/ui/FunctionOrderSpinnerAdapter.java | 78 + .../java/com/bg7yoz/ft8cn/ui/HelpDialog.java | 176 ++ .../ui/LaunchSupervisionSpinnerAdapter.java | 81 + .../java/com/bg7yoz/ft8cn/ui/LogFragment.java | 545 +++++ .../bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java | 210 ++ .../bg7yoz/ft8cn/ui/MyCallingFragment.java | 502 +++++ .../ft8cn/ui/NoReplyLimitSpinnerAdapter.java | 64 + .../ft8cn/ui/PttDelaySpinnerAdapter.java | 60 + .../com/bg7yoz/ft8cn/ui/QRZ_Fragment.java | 68 + .../ft8cn/ui/RigNameSpinnerAdapter.java | 65 + .../bg7yoz/ft8cn/ui/RulerFrequencyView.java | 123 + .../ft8cn/ui/SelectBluetoothDialog.java | 220 ++ .../ft8cn/ui/SelectFlexRadioDialog.java | 244 ++ .../com/bg7yoz/ft8cn/ui/SetVolumeDialog.java | 113 + .../com/bg7yoz/ft8cn/ui/SpectrumFragment.java | 219 ++ .../com/bg7yoz/ft8cn/ui/SpectrumView.java | 207 ++ .../com/bg7yoz/ft8cn/ui/ToastMessage.java | 82 + .../ft8cn/ui/UtcOffsetSpinnerAdapter.java | 60 + .../com/bg7yoz/ft8cn/ui/VolumeProgress.java | 162 ++ .../com/bg7yoz/ft8cn/ui/WaterfallView.java | 281 +++ .../com/bg7yoz/ft8cn/wave/HamRecorder.java | 306 +++ .../com/bg7yoz/ft8cn/wave/MicRecorder.java | 102 + .../bg7yoz/ft8cn/wave/OnAudioRecorded.java | 26 + .../bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java | 10 + .../com/bg7yoz/ft8cn/wave/OnHamRecord.java | 10 + .../ft8cn/wave/OnVoiceMonitorChanged.java | 5 + .../com/bg7yoz/ft8cn/wave/WaveAccess.java | 11 + .../com/bg7yoz/ft8cn/wave/WaveConstants.java | 28 + .../com/bg7yoz/ft8cn/wave/WaveFileReader.java | 202 ++ .../com/bg7yoz/ft8cn/wave/WaveFileWriter.java | 144 ++ .../com/bg7yoz/ft8cn/wave/WriteWavHeader.java | 128 ++ 182 files changed, 39115 insertions(+) create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/OnConnectorStateChanged.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountDbOpr.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/AfterInsertQSLData.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/ControlMode.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryConfig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryFollowCallsigns.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexCommand.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterInfos.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexResponseStyle.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/UtcOffsetSpinnerAdapter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java create mode 100644 ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java new file mode 100644 index 0000000..442a387 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java @@ -0,0 +1,46 @@ +package com.bg7yoz.ft8cn; +/** + * 问题收集的WebView。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.appcompat.app.AppCompatActivity; + +public class FAQActivity extends AppCompatActivity { + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_faqactivity); + + + WebView webView = (WebView) findViewById(R.id.faqWebView); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setDomStorageEnabled(true); // 这个要加上 + + /* 获得 webview url,请注意url单词是product而不是products,products是旧版本的参数,用错地址将不能成功提交 */ + //String url = "https://www.qrz.com/db/BG7YOZ"; + String url = "https://support.qq.com/product/415890"; + + /* WebView 内嵌 Client 可以在APP内打开网页而不是跳出到浏览器 */ + WebViewClient webViewClient = new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + super.shouldOverrideUrlLoading(view, url); + view.loadUrl(url); + return true; + } + }; + webView.setWebViewClient(webViewClient); + + webView.loadUrl(url); + } + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java new file mode 100644 index 0000000..67e2312 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java @@ -0,0 +1,25 @@ +package com.bg7yoz.ft8cn; + +/** + * FT8有关的常量。 + * @author BGY70Z + * @date 2023-03-20 + */ +public final class FT8Common { + public static final int FT8_MODE=0; + public static final int FT4_MODE=1; + public static final int SAMPLE_RATE=12000; + public static final int FT8_SLOT_TIME=15; + public static final int FT8_SLOT_TIME_MILLISECOND=15000;//一个周期的毫秒数 + public static final int FT4_SLOT_TIME_MILLISECOND=7500; + public static final int FT8_5_SYMBOLS_MILLISECOND=800;//5个符号所需的 + + + public static final float FT4_SLOT_TIME=7.5f; + public static final int FT8_SLOT_TIME_M=150;//15秒 + public static final int FT8_5_SYMBOLS_TIME_M =8;//5个符号的时间长度0.8秒 + public static final int FT4_SLOT_TIME_M=75;//7.5秒 + public static final int FT8_TRANSMIT_DELAY=500;//默认发射延迟时长,毫秒 + public static final long DEEP_DECODE_TIMEOUT=7*1000;//深度解码的最长时间范围 + public static final int DECODE_MAX_ITERATIONS=1;//迭代次数 +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java new file mode 100644 index 0000000..fe6519a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java @@ -0,0 +1,501 @@ +package com.bg7yoz.ft8cn; +/** + * Ft8Message类是用于展现FT8信号的解析结果。 + * 包括UTC时间、信噪比、时间偏移、频率、得分、消息的文本、消息的哈希值 + * ----2022.5.6----- + * time_sec可能是时间偏移,目前还不能完全确定,待后续解决。 + * 1.为方便在列表中显示,各要素通过Get方法,返回String类型的结果。 + * -----2022.5.13--- + * 2.增加i3,n3消息类型内容 + * @author BG7YOZ + * @date 2022.5.6 + */ + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.ft8signal.FT8Package; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; +import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.google.android.gms.maps.model.LatLng; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; + +public class Ft8Message { + private static String TAG = "Ft8Message"; + public int i3 = 0; + public int n3 = 0; + public int signalFormat = FT8Common.FT8_MODE;//是不是FT8格式的消息 + public long utcTime;//UTC时间 + public boolean isValid;//是否是有效信息 + public int snr = 0;//信噪比 + public float time_sec = 0;//时间偏移(秒) + public float freq_hz = 0;//频率 + public int score = 0;//得分 + public int messageHash;//消息的哈希 + + public String callsignFrom = null;//发起呼叫的呼号 + public String callsignTo = null;//接收呼叫的呼号 + + public String modifier = null;//目标呼号的修饰符 如CQ POTA BG7YOZ OL50中的POTA + + public String extraInfo = null; + public String maidenGrid = null; + public int report = -100;//当-100时,意味着没有信号报告 + public long callFromHash10 = 0;//12位长度的哈希码 + public long callFromHash12 = 0;//12位长度的哈希码 + public long callFromHash22 = 0;//12位长度的哈希码 + public long callToHash10 = 0;//12位长度的哈希码 + public long callToHash12 = 0;//12位长度的哈希码 + public long callToHash22 = 0;//12位长度的哈希码 + //private boolean isCallMe = false;//是不是CALL我的消息 + public long band;//载波频率 + + public String fromWhere = null;//用于显示地址 + public String toWhere = null;//用于显示地址 + + public boolean isQSL_Callsign = false;//是不是通联过的呼号 + + public static MessageHashMap hashList = new MessageHashMap(); + + + public boolean fromDxcc = false; + public boolean fromItu = false; + public boolean fromCq = false; + public boolean toDxcc = false; + public boolean toItu = false; + public boolean toCq = false; + + public LatLng fromLatLng = null; + public LatLng toLatLng = null; + + public boolean isWeakSignal=false; + + + + + @NonNull + @SuppressLint({"SimpleDateFormat", "DefaultLocale"}) + @Override + public String toString() { + return String.format("%s %d %+4.2f %4.0f ~ %s Hash : %#06X", + new SimpleDateFormat("HHmmss").format(utcTime), + snr, time_sec, freq_hz, getMessageText(), messageHash); + } + + /** + * 创建一个解码消息对象,要确定信号的格式。 + * + * @param signalFormat + */ + public Ft8Message(int signalFormat) { + this.signalFormat = signalFormat; + } + + public Ft8Message(String callTo, String callFrom, String extraInfo) { + //如果是自由文本,callTo=CQ,callFrom=MyCall,extraInfo=freeText + this.callsignTo = callTo.toUpperCase(); + this.callsignFrom = callFrom.toUpperCase(); + this.extraInfo = extraInfo.toUpperCase(); + } + + public Ft8Message(int i3, int n3, String callTo, String callFrom, String extraInfo) { + this.callsignTo = callTo; + this.callsignFrom = callFrom; + this.extraInfo = extraInfo; + this.i3 = i3; + this.n3 = n3; + this.utcTime = UtcTimer.getSystemTime();//用于显示TX + } + + /** + * 创建一个解码消息对象 + * + * @param message 如果message不为null,则创建一个与message内容一样的解码消息对象 + */ + public Ft8Message(Ft8Message message) { + if (message != null) { + + signalFormat = message.signalFormat; + utcTime = message.utcTime; + isValid = message.isValid; + snr = message.snr; + time_sec = message.time_sec; + freq_hz = message.freq_hz; + score = message.score; + band = message.band; + + messageHash = message.messageHash; + + if (message.callsignFrom.equals("<...>")) {//到哈希列表中查一下 + callsignFrom = hashList.getCallsign(new long[]{message.callFromHash10, message.callFromHash12, message.callFromHash22}); + } else { + callsignFrom = message.callsignFrom; + } + + if (message.callsignTo.equals("<...>")) {//到哈希列表中查一下 + callsignTo = hashList.getCallsign(new long[]{message.callToHash10, message.callToHash12, message.callToHash22}); + } else { + callsignTo = message.callsignTo; + } + if (message.i3 == 4) { + hashList.addHash(FT8Package.getHash22(message.callsignFrom), message.callsignFrom); + hashList.addHash(FT8Package.getHash12(message.callsignFrom), message.callsignFrom); + hashList.addHash(FT8Package.getHash10(message.callsignFrom), message.callsignFrom); + } + + extraInfo = message.extraInfo; + maidenGrid = message.maidenGrid; + report = message.report; + callToHash10 = message.callToHash10; + callToHash12 = message.callToHash12; + callToHash22 = message.callToHash22; + callFromHash10 = message.callFromHash10; + callFromHash12 = message.callFromHash12; + callFromHash22 = message.callFromHash22; + + + i3 = message.i3; + n3 = message.n3; + + //把哈希和呼号对应关系保存到列表里 + hashList.addHash(callToHash10, callsignTo); + hashList.addHash(callToHash12, callsignTo); + hashList.addHash(callToHash22, callsignTo); + hashList.addHash(callFromHash10, callsignFrom); + hashList.addHash(callFromHash12, callsignFrom); + hashList.addHash(callFromHash22, callsignFrom); + + + //Log.d(TAG, String.format("i3:%d,n3:%d,From:%s,To:%s", i3, n3, getCallsignFrom(), getCallsignTo())); + } + } + + /** + * 返回解码消息的所使用的频率 + * + * @return String 为方便显示,返回值是字符串 + */ + @SuppressLint("DefaultLocale") + public String getFreq_hz() { + return String.format("%04.0f", freq_hz); + } + + public String getMessageText(boolean showWeekSignal){ + if (isWeakSignal && showWeekSignal){ + return "*"+getMessageText(); + }else { + return getMessageText(); + } + } + + /** + * 返回解码消息的文本内容 + * + * @return String + */ + public String getMessageText() { + + if (i3 == 0 && n3 == 0) {//说明是自由文本 + if (extraInfo.length() < 13) { + return String.format("%-13s", extraInfo.toUpperCase()); + } else { + return extraInfo.toUpperCase().substring(0, 13); + } + } + if (modifier != null && checkIsCQ()) {//修饰符 + if (modifier.matches("[0-9]{3}|[A-Z]{1,4}")) { + return String.format("%s %s %s %s", callsignTo, modifier, callsignFrom, extraInfo).trim(); + } + } + return String.format("%s %s %s", callsignTo, callsignFrom, extraInfo).trim(); + } + + /** + * 返回解码消息带信噪比的内容 + * + * @return 内容 + */ + @SuppressLint("DefaultLocale") + public String getMessageTextWithDb() { + return String.format("%d %s %s %s", snr, callsignTo, callsignFrom, extraInfo).trim(); + } + + /** + * 返回消息的延迟时间。可能不一定对,待研究清楚解码算法后在确定 + * + * @return String 为方便显示,返回值是字符串。 + */ + @SuppressLint("DefaultLocale") + public String getDt() { + return String.format("%.1f", time_sec); + } + + /** + * 返回解码消息的信噪比dB值,该计算方法还为搞定,暂时用000代替 + * + * @return String 为方便显示,返回值是字符串 + */ + public String getdB() { + return String.valueOf(snr); + } + + /** + * 检查消息处于奇数还是偶数序列。 + * + * @return boolean 处于偶数序列true,第0,30秒为true + */ + public boolean isEvenSequence() { + if (signalFormat == FT8Common.FT8_MODE) { + return (utcTime / 1000) % 15 == 0; + } else { + return (utcTime / 100) % 75 == 0; + } + } + + /** + * 显示当前消息处于哪一个时间序列的。 + * + * @return String 以时间周期取模为结果。 + */ + @SuppressLint("DefaultLocale") + public int getSequence() { + if (signalFormat == FT8Common.FT8_MODE) { + return (int) ((((utcTime + 750) / 1000) / 15) % 2); + } else { + return (int) (((utcTime + 370) / 100) / 75) % 2; + } + } + + @SuppressLint("DefaultLocale") + public int getSequence4() { + if (signalFormat == FT8Common.FT8_MODE) { + return (int) ((((utcTime + 750) / 1000) / 15) % 4); + } else { + return (int) (((utcTime + 370) / 100) / 75) % 4; + } + } + + /** + * 消息中含有mycall呼号的 + * + * @return boolean + */ + public boolean inMyCall() { + if (GeneralVariables.myCallsign.length() == 0) return false; + return this.callsignFrom.contains(GeneralVariables.myCallsign) + || this.callsignTo.contains(GeneralVariables.myCallsign); + //return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals("")); + } +/* +i3.n3类型 基本目的 消息范例 位字段标签 +0.0 自由文本(Free Text) TNX BOB 73 GL f71 +0.1 远征(DXpedition) K1ABC RR73; W9XYZ -08 c28 c28 h10 r5 +0.3 野外日(Field Day) K1ABC W9XYZ 6A WI c28 c28 R1 n4 k3 S7 +0.4 野外日(Field Day) W9XYZ K1ABC R 17B EMA c28 c28 R1 n4 k3 S7 +0.5 遥测(Telemetry) 123456789ABCDEF012 t71 +1. 标准消息(Std Msg) K1ABC/R W9XYZ/R R EN37 c28 r1 c28 r1 R1 g15 +2. 欧盟甚高频(EU VHF) G4ABC/P PA9XYZ JO22 c28 p1 c28 p1 R1 g15 +3. 电传(RTTY RU) K1ABC W9XYZ 579 WI t1 c28 c28 R1 r3 s13 +4. 非标准呼叫(NonStd Call) PJ4/K1ABC RRR h12 c58 h1 r2 c1 +5. 欧盟甚高频(EU VHF) R 570007 JO22DB h12 h22 R1 r3 s11 g25 +*/ +/* +标签 传达的信息 +c1 第一个呼号是CQ;h12被忽略 +c28 标准呼号、CQ、DE、QRZ或22位哈希 +c58 非标准呼号,最多11个字符 +f71 自由文本,最多13个字符 +g15 4字符网格、报告、RRR、RR73、73或空白 +g25 6字符网格 +h1 哈希呼号是第二个呼号 +h10 哈希呼号,10位 +h12 哈希呼号,12位 +h22 哈希呼号,22位 +k3 野外日级别(Class):A、B、…F +n4 发射器数量:1-16、17-32 +p1 呼号后缀 /P +r1 呼号后缀/R +r2 RRR、RR73、73、或空白 +r3 报告:2-9,显示为529-599或52-59 +R1 R +r5 报告:-30到+30,仅偶数 +s11 序列号(0-2047) +s13 序列号(0-7999)或州/省 +S7 ARRL/RAC部分 +t1 TU; +t71 遥感数据,最多18位十六进制数字 + +*/ + + + /** + * 获取发送者的呼号,fromTo的最终解决办法要在decode.c中解决---TO DO---- + * 可获取发送者呼号的消息类型为i1,i2,i3,i4,i5,i0.1,i0.3,i0.4 + * + * @return String 返回呼号 + */ + public String getCallsignFrom() { + if (callsignFrom == null) { + return ""; + } + return callsignFrom.replace("<", "").replace(">", ""); + } + + /** + * 获取通联信息中的接收呼号 + * + * @return + */ + public String getCallsignTo() { + if (callsignTo == null) { + return ""; + } + if (callsignTo.length() < 2) { + return ""; + } + if (callsignTo.substring(0, 2).equals("CQ") || callsignTo.substring(0, 2).equals("DE") + || callsignTo.substring(0, 3).equals("QRZ")) { + return ""; + } + return callsignTo.replace("<", "").replace(">", ""); + } + + /** + * 从消息中获取梅登海德网格信息 + * + * @return String,梅登海德网格,如果没有返回""。 + */ + public String getMaidenheadGrid(DatabaseOpr db) { + if (i3 != 1 && i3 != 2) {//一般只有i3=1或i3=2,标准消息,甚高频消息才有网格 + return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 + } else { + String[] msg = getMessageText().split(" "); + if (msg.length < 1) { + return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 + } + String s = msg[msg.length - 1]; + if (MaidenheadGrid.checkMaidenhead(s)) { + return s; + } else {//不是网格信息,就可能是信号报告 + return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格 + } + } + } + + public String getToMaidenheadGrid(DatabaseOpr db) { + if (checkIsCQ()) return ""; + return GeneralVariables.getGridByCallsign(callsignTo, db); + } + + /** + * 查看消息是不是CQ + * + * @return boolean 是CQ返回true + */ + public boolean checkIsCQ() { + String s = callsignTo.trim().split(" ")[0]; + if (s == null) { + return false; + } else { + return (s.equals("CQ") || s.equals("DE") || s.equals("QRZ")); + } + } + + /** + * 查消息的类型。i3.n3。 + * + * @return 消息类型 + */ + + public String getCommandInfo() { + return getCommandInfoByI3N3(i3, n3); + } + + /** + * 查消息的类型。i3.n3。 + * + * @param i i3 + * @param n n3 + * @return 消息类型 + */ + @SuppressLint("DefaultLocale") + public static String getCommandInfoByI3N3(int i, int n) { + String format = "%d.%d:%s"; + switch (i) { + case 1: + case 2: + return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.std_msg)); + case 5: + case 3: + case 4: + return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg)); + case 0: + switch (n) { + case 0: + return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.free_text)); + case 1: + return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.dXpedition)); + case 3: + case 4: + return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.field_day)); + case 5: + return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.telemetry)); + } + } + return ""; + } + + //获取发送者的传输对象 + public TransmitCallsign getFromCallTransmitCallsign() { + return new TransmitCallsign(this.i3, this.n3, this.callsignFrom, freq_hz + , this.getSequence() + , snr); + } + + //获取发送者的传输对象,注意!!!与发送者的时序是相反的!!! + public TransmitCallsign getToCallTransmitCallsign() { + if (report == -100) {//如果消息中没有信号报告,就用发送方的SNR代替 + return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, snr); + } else { + return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, report); + } + } + + @SuppressLint("DefaultLocale") + public String toHtml() { + StringBuilder result = new StringBuilder(); + + result.append(""); + result.append(UtcTimer.getDatetimeStr(utcTime)); + result.append("\n"); + + result.append(""); + result.append(getdB()); + result.append("\n"); + + result.append(""); + result.append(String.format("%.1f", time_sec)); + result.append("\n"); + + result.append(""); + result.append(String.format("%.0f", freq_hz)); + result.append("\n"); + + result.append(""); + result.append(getMessageText()); + result.append("\n"); + + result.append(""); + result.append(BaseRigOperation.getFrequencyStr(band)); + result.append("\n"); + + return result.toString(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java new file mode 100644 index 0000000..d1fd408 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java @@ -0,0 +1,587 @@ +package com.bg7yoz.ft8cn; +/** + * 常用变量。关于mainContext有内存泄漏的风险,以后解决。 + * mainContext + */ + +import android.annotation.SuppressLint; +import android.content.Context; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.callsign.CallsignDatabase; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.ft8transmit.QslRecordList; +import com.bg7yoz.ft8cn.html.HtmlContext; +import com.bg7yoz.ft8cn.icom.IcomAudioUdp; +import com.bg7yoz.ft8cn.log.QSLRecord; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public class GeneralVariables { + private static final String TAG = "GeneralVariables"; + public static String VERSION = BuildConfig.VERSION_NAME;//版本号"0.62(Beta 4)"; + public static String BUILD_DATE = BuildConfig.apkBuildTime;//编译的时间 + public static int MESSAGE_COUNT = 3000;//消息的最大缓存数量 + public static boolean saveSWLMessage=false;//保存解码消息开关 + public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关 + + public static boolean deepDecodeMode=false;//是否开启深度解码 + + public static boolean audioOutput32Bit =true;//音频输出类型true=float,false=int16 + public static int audioSampleRate=12000;//发射音频的采样率 + + public static MutableLiveData mutableVolumePercent = new MutableLiveData<>(); + public static float volumePercent = 0.5f;//播放音频的音量,是百分比 + + public static int flexMaxRfPower=10;//flex电台的最大发射功率 + public static int flexMaxTunePower=10;//flex电台的最大调谐功率 + + private Context mainContext; + public static CallsignDatabase callsignDatabase = null; + + public void setMainContext(Context context) { + mainContext = context; + } + + public static boolean isChina = true;//语言是不是中国 + public static boolean isTraditionalChinese = true;//语言是不是繁体中文 + //public static double maxDist = 0;//最远距离 + + //各已经通联的分区列表 + public static final Map dxccMap = new HashMap<>(); + public static final Map cqMap = new HashMap<>(); + public static final Map ituMap = new HashMap<>(); + + private static final Map excludedCallsigns = new HashMap<>(); + + /** + * 添加排除的字头 + * + * @param callsigns 呼号 + */ + public static synchronized void addExcludedCallsigns(String callsigns) { + excludedCallsigns.clear(); + String[] s = callsigns.toUpperCase().replace(" ", ",") + .replace("|", ",") + .replace(",", ",").split(","); + for (int i = 0; i < s.length; i++) { + if (s[i].length() > 0) { + excludedCallsigns.put(s[i], 0); + } + } + } + + /** + * 查找是否含有排除的字头 + * + * @param callsign 呼号 + * @return 是否 + */ + public static synchronized boolean checkIsExcludeCallsign(String callsign) { + Iterator iterator = excludedCallsigns.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (callsign.toUpperCase().indexOf(key) == 0) { + return true; + } + } + return false; + } + + /** + * 获取排除呼号前缀的列表 + * + * @return 列表 + */ + public static synchronized String getExcludeCallsigns() { + StringBuilder calls = new StringBuilder(); + Iterator iterator = excludedCallsigns.keySet().iterator(); + int i = 0; + while (iterator.hasNext()) { + String key = iterator.next(); + if (i == 0) { + calls.append(key); + } else { + calls.append(",").append(key); + } + i++; + } + return calls.toString(); + } + + + //通联记录列表,包括成功与不成功的 + public static QslRecordList qslRecordList = new QslRecordList(); + + //此处有内存泄露警告,但Application Context不应该会内存泄露,所以注释掉 + @SuppressLint("StaticFieldLeak") + private static GeneralVariables generalVariables = null; + + public static GeneralVariables getInstance() { + if (generalVariables == null) { + generalVariables = new GeneralVariables(); + } + return generalVariables; + } + + public static Context getMainContext() { + return GeneralVariables.getInstance().mainContext; + } + + + public static MutableLiveData mutableDebugMessage = new MutableLiveData<>(); + public static int QUERY_FREQ_TIMEOUT = 2000;//轮询频率变化的时间间隔。2秒 + public static int START_QUERY_FREQ_DELAY = 2000;//开始轮询频率的时间延迟 + + public static final int DEFAULT_LAUNCH_SUPERVISION = 10 * 60 * 1000;//发射监管默认值,10分钟 + private static String myMaidenheadGrid = ""; + public static MutableLiveData mutableMyMaidenheadGrid = new MutableLiveData<>(); + + public static int connectMode = ConnectMode.USB_CABLE;//连接方式USB==0,BLUE_TOOTH==1 + + //public static String bluetoothDeviceAddress=null;//可以用于连接的蓝牙设备地址 + + + //用于记录呼号于网格的对应关系 todo---应当把此处列表也放到后台追踪信息里 + //public static ArrayList callsignMaidenheadGrids=new ArrayList<>(); + public static final Map callsignAndGrids = new ConcurrentHashMap<>(); + //private static final Map callsignAndGrids=new HashMap<>(); + + public static String myCallsign = "";//我的呼号 + public static String toModifier = "";//呼叫的修饰符 + private static float baseFrequency = 1000;//声音频率 + public static MutableLiveData mutableBaseFrequency = new MutableLiveData<>(); + + public static boolean synFrequency = false;//同频发射 + public static int transmitDelay = 500;//发射延迟时间,这个时间也是给上一个周期的解码时间 + public static int pttDelay = 100;//PTT的响应时间,在给电台PTT指令后,一般电台会有一个响应时间,此处默认是100毫秒 + public static int civAddress = 0xa4;//civ地址 + public static int baudRate = 19200;//波特率 + public static long band = 14074000;//载波频段 + public static int instructionSet = 0;//指令集,0:icom,1:yaesu 2 代,2:yaesu 3代。 + public static int bandListIndex = -1;//电台波段的索引值 + public static MutableLiveData mutableBandChange = new MutableLiveData<>();//波段索引值变化 + public static int controlMode = ControlMode.VOX; + public static int modelNo = 0; + public static int launchSupervision = DEFAULT_LAUNCH_SUPERVISION;//发射监管 + public static long launchSupervisionStart = UtcTimer.getSystemTime();//自动发射的起始时间 + public static int noReplyLimit = 0;//呼叫无回应次数0==忽略 + + public static int noReplyCount = 0;//没有回应的次数 + + //下面4个参数是ICOM网络方式连接的参数 + public static String icomIp = "255.255.255.255"; + public static int icomUdpPort = 50001; + public static String icomUserName = "ic705"; + public static String icomPassword = ""; + + + public static boolean autoFollowCQ = true;//自动关注CQ + public static boolean autoCallFollow = true;//自动呼叫关注的呼号 + public static ArrayList QSL_Callsign_list = new ArrayList<>();//QSL成功的呼号 + public static ArrayList QSL_Callsign_list_other_band = new ArrayList<>();//在其它波段QSL成功的呼号 + + + public static final ArrayList followCallsign = new ArrayList<>();//关注的呼号 + + public static ArrayList transmitMessages = new ArrayList<>();//放在呼叫界面,关注的列表 + + public static void setMyMaidenheadGrid(String grid) { + myMaidenheadGrid = grid; + mutableMyMaidenheadGrid.postValue(grid); + } + + public static String getMyMaidenheadGrid() { + return myMaidenheadGrid; + } + + public static float getBaseFrequency() { + return baseFrequency; + } + + public static void setBaseFrequency(float baseFrequency) { + mutableBaseFrequency.postValue(baseFrequency); + GeneralVariables.baseFrequency = baseFrequency; + } + + @SuppressLint("DefaultLocale") + public static String getBaseFrequencyStr() { + return String.format("%.0f", baseFrequency); + } + + public static String getCivAddressStr() { + return String.format("%2X", civAddress); + } + + public static String getTransmitDelayStr() { + return String.valueOf(transmitDelay); + } + + public static String getBandString() { + return BaseRigOperation.getFrequencyAllInfo(band); + } + + /** + * 查有没有通联成功的呼号 + * + * @param callsign 呼号 + * @return 是否存在 + */ + public static boolean checkQSLCallsign(String callsign) { + return QSL_Callsign_list.contains(callsign); + } + + /** + * 查别的波段有没有通联成功的呼号 + * + * @param callsign 呼号 + * @return 是否存在 + */ + public static boolean checkQSLCallsign_OtherBand(String callsign) { + return QSL_Callsign_list_other_band.contains(callsign); + } + + + /** + * 查该呼号是不是在关注的呼号列表中 + * + * @param callsign 呼号 + * @return 是否存在 + */ + public static boolean callsignInFollow(String callsign) { + return followCallsign.contains(callsign); + } + + /** + * 向通联成功的呼号列表添加 + * + * @param callsign 呼号 + */ + public static void addQSLCallsign(String callsign) { + if (!checkQSLCallsign(callsign)) { + QSL_Callsign_list.add(callsign); + } + } + + public static String getMyMaidenhead4Grid() { + if (myMaidenheadGrid.length() > 4) { + return myMaidenheadGrid.substring(0, 4); + } + return myMaidenheadGrid; + } + + /** + * 自动程序运行起始时间 + */ + public static void resetLaunchSupervision() { + launchSupervisionStart = UtcTimer.getSystemTime(); + } + + /** + * 或取自动程序的运行时长 + * + * @return 毫秒 + */ + public static int launchSupervisionCount() { + return (int) (UtcTimer.getSystemTime() - launchSupervisionStart); + } + + public static boolean isLaunchSupervisionTimeout() { + if (launchSupervision == 0) return false;//0是不监管 + return launchSupervisionCount() > launchSupervision; + } + + /** + * 从extraInfo中查消息顺序 + * + * @param extraInfo 消息中的扩展内容 + * @return 返回消息序号 + */ + public static int checkFunOrderByExtraInfo(String extraInfo) { + if (checkFun5(extraInfo)) return 5; + if (checkFun4(extraInfo)) return 4; + if (checkFun3(extraInfo)) return 3; + if (checkFun2(extraInfo)) return 2; + if (checkFun1(extraInfo)) return 1; + return -1; + } + + /** + * 检查消息的序号,如果解析不出来,就-1 + * + * @param message 消息 + * @return 消息序号 + */ + public static int checkFunOrder(Ft8Message message) { + if (message.checkIsCQ()) return 6; + return checkFunOrderByExtraInfo(message.extraInfo); + + } + + + //是不是网格报告 + public static boolean checkFun1(String extraInfo) { + //网格报告必须是4位,或没有网格 + return (extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]") && !extraInfo.equals("RR73")) + || (extraInfo.trim().length() == 0); + + } + + //是不是信号报告,如-10 + public static boolean checkFun2(String extraInfo) { + if (extraInfo.trim().length() < 2) { + return false; + }//信号报告必须至少2位 + try { + return Integer.parseInt(extraInfo.trim()) != 73;//如果是73,说明是消息6,不是消息2 + //return true; + } catch (Exception e) { + return false; + } + } + + //是不是带R的信号报告,如R-10 + public static boolean checkFun3(String extraInfo) { + if (extraInfo.trim().length() < 3) { + return false; + }//带R信号报告必须至少3位 + //第一位如果不是R,或者第二位是R,说明不是消息3 + if ((extraInfo.trim().charAt(0) != 'R') || (extraInfo.trim().charAt(1) == 'R')) { + return false; + } + + try { + Integer.parseInt(extraInfo.trim().substring(1)); + return true; + } catch (Exception e) { + return false; + } + } + + //是不是RRR或RR73值 + public static boolean checkFun4(String extraInfo) { + return extraInfo.trim().equals("RR73") || extraInfo.trim().equals("RRR"); + } + + //是不是73值 + public static boolean checkFun5(String extraInfo) { + return extraInfo.trim().equals("73"); + } + + + /** + * 判断是不是信号报告,如果是,把值赋给 report + * @param extraInfo 消息扩展 + * @return 信号报告值,没找到是-100 + */ + public static int checkFun2_3(String extraInfo){ + if (extraInfo.equals("73")) return -100; + if (extraInfo.matches("[R]?[+-]?[0-9]{1,2}")){ + try { + return Integer.parseInt(extraInfo.replace("R","")); + } catch (Exception e) { + return -100; + } + } + return -100; + } + + /** + * 判断是不是网格报告,如果是,把值赋给 report + * @param extraInfo 消息扩展 + * @return 信号报告 + */ + public static boolean checkFun1_6(String extraInfo){ + return extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]") + && !extraInfo.trim().equals("RR73"); + } + /** + * 检查是否是通联结束:RRR、RR73、73 + * @param extraInfo 消息后缀 + * @return 是否 + */ + public static boolean checkFun4_5(String extraInfo){ + return extraInfo.trim().equals("RR73") + || extraInfo.trim().equals("RRR") + ||extraInfo.trim().equals("73"); + } + + /** + * 从String.xml中提取字符串 + * + * @param id id + * @return 字符串 + */ + public static String getStringFromResource(int id) { + if (getMainContext() != null) { + return getMainContext().getString(id); + } else { + return ""; + } + } + + + /** + * 把已经通联的DXCC分区添加到集合中 + * + * @param dxccPrefix DXCC前缀 + */ + public static void addDxcc(String dxccPrefix) { + dxccMap.put(dxccPrefix, dxccPrefix); + } + + /** + * 查看是不是已经通联的DXCC分区 + * + * @param dxccPrefix DXCC前缀 + * @return 是否 + */ + public static boolean getDxccByPrefix(String dxccPrefix) { + return dxccMap.containsKey(dxccPrefix); + } + + /** + * 把CQ分区加到列表里 + * + * @param cqZone cq分区编号 + */ + public static void addCqZone(int cqZone) { + cqMap.put(cqZone, cqZone); + } + + /** + * 查是否存在已经通联的CQ分区 + * + * @param cq cq分区编号 + * @return 是否存在 + */ + public static boolean getCqZoneById(int cq) { + return cqMap.containsKey(cq); + } + + /** + * 把itu分区添加到已通联的ITU列表中 + * + * @param itu itu编号 + */ + public static void addItuZone(int itu) { + ituMap.put(itu, itu); + } + + /** + * 查Itu分区在不在已通联的列表中 + * + * @param itu itu编号 + * @return 是否存在 + */ + public static boolean getItuZoneById(int itu) { + return ituMap.containsKey(itu); + } + + //用于触发新的网格 + public static MutableLiveData mutableNewGrid = new MutableLiveData<>(); + + /** + * 把呼号与网格的对应关系添加到呼号--网格对应表, + * + * @param callsign 呼号 + * @param grid 网格 + */ + public static void addCallsignAndGrid(String callsign, String grid) { + if (grid.length() >= 4) { + callsignAndGrids.put(callsign, grid); + mutableNewGrid.postValue(grid); + } + } + + /** + * 呼号--网格对应表。以呼号查网格 + * 如果内存中没有,应当到数据库中查一下。 + * + * @param callsign 呼号 + * @return 是否有对应的网格 + */ + public static boolean getCallsignHasGrid(String callsign) { + return callsignAndGrids.containsKey(callsign); + } + + /** + * 呼号--网格对应表。以呼号查网格,条件是呼号和网格都对应的上。 + * 此函数的目的是,为了更新对应表的数据库 + * + * @param callsign 呼号 + * @param grid 网格 + * @return 是否有对应的网格 + */ + public static boolean getCallsignHasGrid(String callsign, String grid) { + if (!callsignAndGrids.containsKey(callsign)) return false;//说明根本没有这个呼号 + String s = callsignAndGrids.get(callsign); + if (s == null) return false; + return s.equals(grid); + } + + public static String getGridByCallsign(String callsign, DatabaseOpr db) { + String s = callsign.replace("<", "").replace(">", ""); + if (getCallsignHasGrid(s)) { + return callsignAndGrids.get(s); + } else { + db.getCallsignQTH(callsign); + return ""; + } + } + + /** + * 遍历呼号--网格对应表,生成HTML + * + * @return HTML + */ + public static String getCallsignAndGridToHTML() { + StringBuilder result = new StringBuilder(); + int order = 0; + for (String key : callsignAndGrids.keySet()) { + order++; + HtmlContext.tableKeyRow(result,order % 2 != 0,key,callsignAndGrids.get(key)); + } + return result.toString(); + } + + public static synchronized void deleteArrayListMore(ArrayList list) { + if (list.size() > GeneralVariables.MESSAGE_COUNT) { + while (list.size() > GeneralVariables.MESSAGE_COUNT) { + list.remove(0); + } + } + } + + /** + * 判断是否为整数 + * + * @param str 传入的字符串 + * @return 是整数返回true, 否则返回false + */ + + public static boolean isInteger(String str) { + if (str != null && !"".equals(str.trim())) + return str.matches("^[0-9]*$"); + else + return false; + } + + /** + * 输出音频的数据类型,网络模式不可用 + */ + public enum AudioOutputBitMode{ + Float32, + Int16 + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java new file mode 100644 index 0000000..5a0d7f8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java @@ -0,0 +1,660 @@ +package com.bg7yoz.ft8cn; +/** + * FT8CN程序的主Activity。本APP采用Fragment框架实现,每个Fragment实现不同的功能。 + * ----2022.5.6----- + * 主要完成以下功能: + * 1.生成MainViewModel实例。MainViewModel是用于整个生存周期,用于录音、解析等功能。 + * 2.录音、存储的权限申请。 + * 3.实现Fragment的导航管理。 + * 4.USB串口连接后的提示 + * @author BG7YOZ + * @date 2022.5.6 + */ + + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AnimationUtils; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.Observer; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; +import androidx.navigation.ui.NavigationUI; + +import com.bg7yoz.ft8cn.bluetooth.BluetoothStateBroadcastReceive; +import com.bg7yoz.ft8cn.callsign.CallsignDatabase; +import com.bg7yoz.ft8cn.connector.CableSerialPort; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.database.OnAfterQueryConfig; +import com.bg7yoz.ft8cn.database.OperationBand; +import com.bg7yoz.ft8cn.databinding.MainActivityBinding; +import com.bg7yoz.ft8cn.floatview.FloatView; +import com.bg7yoz.ft8cn.floatview.FloatViewButton; +import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.bg7yoz.ft8cn.ui.FreqDialog; +import com.bg7yoz.ft8cn.ui.SetVolumeDialog; +import com.bg7yoz.ft8cn.ui.ToastMessage; +import com.google.android.material.bottomnavigation.BottomNavigationView; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + + +public class MainActivity extends AppCompatActivity { + private BluetoothStateBroadcastReceive mReceive; + private static final String TAG = "MainActivity"; + private MainViewModel mainViewModel; + private NavController navController; + private static boolean animatorRunned = false; + //private boolean animationEnd = false; + + private MainActivityBinding binding; + private FloatView floatView; + + + String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO + , Manifest.permission.ACCESS_COARSE_LOCATION + , Manifest.permission.ACCESS_WIFI_STATE + , Manifest.permission.BLUETOOTH + , Manifest.permission.BLUETOOTH_ADMIN + , Manifest.permission.MODIFY_AUDIO_SETTINGS + , Manifest.permission.WAKE_LOCK + , Manifest.permission.ACCESS_FINE_LOCATION}; + List mPermissionList = new ArrayList<>(); + + private static final int PERMISSION_REQUEST = 1; + + @Override + protected void onCreate(Bundle savedInstanceState) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + permissions = new String[]{Manifest.permission.RECORD_AUDIO + , Manifest.permission.ACCESS_COARSE_LOCATION + , Manifest.permission.ACCESS_WIFI_STATE + , Manifest.permission.BLUETOOTH + , Manifest.permission.BLUETOOTH_ADMIN + , Manifest.permission.BLUETOOTH_CONNECT + , Manifest.permission.MODIFY_AUDIO_SETTINGS + , Manifest.permission.WAKE_LOCK + , Manifest.permission.ACCESS_FINE_LOCATION}; + } + + checkPermission(); + //全屏 + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN + , WindowManager.LayoutParams.FLAG_FULLSCREEN); + + //禁止休眠 + getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + , WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + super.onCreate(savedInstanceState); + GeneralVariables.getInstance().setMainContext(getApplicationContext()); + + //判断是不是简体中文 + GeneralVariables.isTraditionalChinese = + getResources().getConfiguration().locale.getDisplayCountry().equals("中國"); + + //确定是不是中国、香港、澳门、台湾 + GeneralVariables.isChina = (getResources().getConfiguration().locale + .getLanguage().toUpperCase().startsWith("ZH")); + + mainViewModel = MainViewModel.getInstance(this); + binding = MainActivityBinding.inflate(getLayoutInflater()); + binding.initDataLayout.setVisibility(View.VISIBLE);//显示LOG页面 + setContentView(binding.getRoot()); + + + ToastMessage.getInstance(); + registerBluetoothReceiver();//注册蓝牙动作改变的广播 + if (mainViewModel.isBTConnected()) { + mainViewModel.setBlueToothOn(); + } + + + //观察DEBUG信息 + GeneralVariables.mutableDebugMessage.observe(this, new Observer() { + @Override + public void onChanged(String s) { + if (s.length() > 1) { + binding.debugLayout.setVisibility(View.VISIBLE); + } else { + binding.debugLayout.setVisibility(View.GONE); + } + binding.debugMessageTextView.setText(s); + } + }); + binding.debugLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + binding.debugLayout.setVisibility(View.GONE); + } + }); + + + mainViewModel.mutableIsRecording.observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + binding.utcProgressBar.setVisibility(View.VISIBLE); + } else { + binding.utcProgressBar.setVisibility(View.GONE); + } + } + }); + //观察时钟的变化,显示进度条 + mainViewModel.timerSec.observe(this, new Observer() { + @Override + public void onChanged(Long aLong) { + if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential() + && mainViewModel.ft8TransmitSignal.isActivated()) { + binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color)); + } else { + binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color)); + } + binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15)); + } + }); + + //添加点击发射消息提示窗口点击关闭动作 + binding.transmittingLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + binding.transmittingLayout.setVisibility(View.GONE); + } + }); + //清空缓存中的文件 + //deleteFolderFile(this.getCacheDir().getPath()); + + //Log.e(TAG, this.getCacheDir().getPath()); + + //用于Fragment的导航。 + NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); + assert navHostFragment != null;//断言不为空 + navController = navHostFragment.getNavController(); + + NavigationUI.setupWithNavController(binding.navView, navController); + //此处增加回调是因为当APP主动navigation后,无法回到解码的界面 + binding.navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + //Log.e(TAG, "onNavigationItemSelected: "+item.toString() ); + navController.navigate(item.getItemId()); + //binding.navView.setLabelFor(item.getItemId()); + return true; + } + }); + + //FT8CN Ver %s\nBG7YOZ\n%s + binding.welcomTextView.setText(String.format(getString(R.string.version_info) + , GeneralVariables.VERSION, GeneralVariables.BUILD_DATE)); + + floatView = new FloatView(this, 32); + if (!animatorRunned) { + animationImage(); + animatorRunned = true; + } else { + binding.initDataLayout.setVisibility(View.GONE); + + InitFloatView(); + } + //初始化数据 + InitData(); + + + //观察是不是flex radio + + mainViewModel.mutableIsFlexRadio.observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + //if (floatView==null) return; + if (aBoolean) { + //添加flex配置按钮 + floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon + , new View.OnClickListener() { + @Override + public void onClick(View view) { + + navController.navigate(R.id.flexRadioInfoFragment); + +// if (mainViewModel.baseRig != null) { +// if (mainViewModel.baseRig.isConnected()) { +// ToastMessage.show("flex connected"); +// }else { +// ToastMessage.show("flex disconnected"); +// } +// }else { +// ToastMessage.show("rig is null"); +// } + + } + }); + } else {//删除flex配置按钮 + floatView.deleteButtonByName("flex_radio"); + } + } + }); + + + //关闭串口设备列表按钮 + binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + binding.selectSerialPortLayout.setVisibility(View.GONE); + } + }); + + //观察串口设备列表的变化 + mainViewModel.mutableSerialPorts.observe(this, new Observer>() { + @Override + public void onChanged(ArrayList serialPorts) { + setSelectUsbDevice(); + } + }); + + //列USB设备列表 + mainViewModel.getUsbDevice(); + + + //设置发射消息框的动画 + binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this + , R.anim.view_blink)); + //观察发射的状态 + mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, + new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + binding.transmittingLayout.setVisibility(View.VISIBLE); + } else { + binding.transmittingLayout.setVisibility(View.GONE); + } + } + }); + + //观察发射内容的变化 + mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this, + new Observer() { + @Override + public void onChanged(String s) { + binding.transmittingMessageTextView.setText(s); + } + }); + } + + + /** + * 添加浮动按钮 + */ + + private void InitFloatView() { + //floatView = new FloatView(this, 32); + + binding.container.addView(floatView); + floatView.setButtonMargin(0); + floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT); + + floatView.setButtonBackgroundResourceId(R.drawable.float_button_style); + //动态添加按钮,建议使用静态的ID,静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置 + floatView.addButton(R.id.float_nav, "float_nav", R.drawable.ic_baseline_fullscreen_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + FloatViewButton button = floatView.getButtonByName("float_nav"); + if (binding.navView.getVisibility() == View.VISIBLE) { + binding.navView.setVisibility(View.GONE); + if (button != null) { + button.setImageResource(R.drawable.ic_baseline_fullscreen_exit_24); + } + } else { + binding.navView.setVisibility(View.VISIBLE); + if (button != null) { + button.setImageResource(R.drawable.ic_baseline_fullscreen_24); + } + } + } + }); + floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + new FreqDialog(binding.container.getContext(), mainViewModel).show(); + } + }); + + floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + new SetVolumeDialog(binding.container.getContext(), mainViewModel).show(); + } + }); + //打开网格追踪 + floatView.addButton(R.id.grid_tracker, "grid_tracker", R.drawable.ic_baseline_grid_tracker_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getApplicationContext(), GridTrackerMainActivity.class); + startActivity(intent); + } + }); + + +// floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon +// , new View.OnClickListener() { +// @Override +// public void onClick(View view) { +// navController.navigate(R.id.flexRadioInfoFragment); +// } +// }); + + floatView.initLocation(); + } + + /** + * 初始化一些数据 + */ + private void InitData() { + if (mainViewModel.configIsLoaded) return;//如果数据已经读取一遍了,就不用再读取了。 + + //读取波段数据 + if (mainViewModel.operationBand == null) { + mainViewModel.operationBand = OperationBand.getInstance(getBaseContext()); + } + + mainViewModel.databaseOpr.getQslDxccToMap(); + + //获取所有的配置参数 + mainViewModel.databaseOpr.getAllConfigParameter(new OnAfterQueryConfig() { + @Override + public void doOnBeforeQueryConfig(String KeyName) { + + } + + @Override + public void doOnAfterQueryConfig(String KeyName, String Value) { + mainViewModel.configIsLoaded = true; + //此处梅登海德已经通过数据库得到了,但是如果GPS能获取到,还是用GPS的 + String grid = MaidenheadGrid.getMyMaidenheadGrid(getApplicationContext()); + if (!grid.equals("")) {//说明获取到了GPS数据 + GeneralVariables.setMyMaidenheadGrid(grid); + //写到数据库中 + mainViewModel.databaseOpr.writeConfig("grid", grid, null); + } + + mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay); + //如果呼号、网格为空,就进入设置界面 + if (GeneralVariables.getMyMaidenheadGrid().equals("") + || GeneralVariables.myCallsign.equals("")) { + runOnUiThread(new Runnable() { + @Override + public void run() {//导航到设置页面 + navController.navigate(R.id.menu_nav_config); + } + }); + } + } + }); + + //把历史中通联成功的呼号与网格的对应关系 + new DatabaseOpr.GetCallsignMapGrid(mainViewModel.databaseOpr.getDb()).execute(); + + mainViewModel.getFollowCallsignsFromDataBase(); + //打开呼号位置信息的数据库,目前是以内存数据库方式。 + if (GeneralVariables.callsignDatabase == null) { + GeneralVariables.callsignDatabase = CallsignDatabase.getInstance(getBaseContext(), null, 1); + } + } + + + /** + * 检查权限 + */ + private void checkPermission() { + mPermissionList.clear(); + + //判断哪些权限未授予 + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + mPermissionList.add(permission); + } + } + + //判断是否为空 + if (!mPermissionList.isEmpty()) {//请求权限方法 + String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);//将List转为数组 + ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST); + } + } + + + /** + * 响应授权 + * 这里不管用户是否拒绝,都进入首页,不再重复申请权限 + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode != PERMISSION_REQUEST) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + + /** + * 显示串口设备列表 + */ + public void setSelectUsbDevice() { + ArrayList ports = mainViewModel.mutableSerialPorts.getValue(); + binding.selectSerialPortLinearLayout.removeAllViews(); + for (int i = 0; i < ports.size(); i++) {//动态添加串口设备列表 + View layout = LayoutInflater.from(getApplicationContext()) + .inflate(R.layout.select_serial_port_list_view_item, null); + layout.setId(i); + TextView textView = layout.findViewById(R.id.selectSerialPortListViewItemTextView); + textView.setText(ports.get(i).information()); + binding.selectSerialPortLinearLayout.addView(layout); + layout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //连接电台并做电台的频率设置等操作 + mainViewModel.connectCableRig(getApplicationContext(), ports.get(view.getId())); + binding.selectSerialPortLayout.setVisibility(View.GONE); + } + }); + } + + //选择串口设备弹框 + if ((ports.size() >= 1) && (!mainViewModel.isRigConnected())) { + binding.selectSerialPortLayout.setVisibility(View.VISIBLE); + } else {//说明没有可以识别的驱动,不显示设备弹框 + binding.selectSerialPortLayout.setVisibility(View.GONE); + } + } + + /** + * 删除指定文件夹中的所有文件 + * + * @param filePath 指定的文件夹 + */ + public static void deleteFolderFile(String filePath) { + try { + File file = new File(filePath);//获取SD卡指定路径 + File[] files = file.listFiles();//获取SD卡指定路径下的文件或者文件夹 + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) {//如果是文件直接删除 + File tempFile = new File(files[i].getPath()); + tempFile.delete(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void animationImage() { + + ObjectAnimator navigationAnimator = ObjectAnimator.ofFloat(binding.navView, "translationY", 200); + navigationAnimator.setDuration(3000); + navigationAnimator.setFloatValues(200, 200, 200, 0); + + + ObjectAnimator hideLogoAnimator = ObjectAnimator.ofFloat(binding.initDataLayout, "alpha", 1f, 1f, 1f, 0); + hideLogoAnimator.setDuration(3000); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(navigationAnimator, hideLogoAnimator); + //animatorSet.playTogether(initPositionStrAnimator, logoAnimator, navigationAnimator, hideLogoAnimator); + animatorSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + + } + + @Override + public void onAnimationEnd(Animator animator) { + //animationEnd = true; + binding.initDataLayout.setVisibility(View.GONE); + binding.utcProgressBar.setVisibility(View.VISIBLE); + InitFloatView();//显示浮窗 + //binding.floatView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + + animatorSet.start(); + } + + + //此方法只有在android:launchMode="singleTask"模式下起作用 + @Override + protected void onNewIntent(Intent intent) { + if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) { + mainViewModel.getUsbDevice(); + } + super.onNewIntent(intent); + } + + + @Override + public void onBackPressed() { + if (navController.getGraph().getStartDestination() == navController.getCurrentDestination().getId()) {//说明是到最后一个页面了 + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setMessage(getString(R.string.exit_confirmation)) + .setPositiveButton(getString(R.string.exit) + , new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(false); + } + closeThisApp();//退出APP + } + }).setNegativeButton(getString(R.string.cancel) + , new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + dialogInterface.dismiss(); + } + }); + builder.create().show(); + + } else {//退出activity堆栈 + navController.navigateUp(); + //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); + } + } + + private void closeThisApp() { + mainViewModel.ft8TransmitSignal.setActivated(false); + if (mainViewModel.baseRig != null) { + if (mainViewModel.baseRig.getConnector() != null) { + mainViewModel.baseRig.getConnector().disconnect(); + } + } + + mainViewModel.ft8SignalListener.stopListen(); + mainViewModel = null; + System.exit(0); + } + + + /** + * 注册蓝牙动作广播 + */ + private void registerBluetoothReceiver() { + if (mReceive == null) { + mReceive = new BluetoothStateBroadcastReceive(getApplicationContext(), mainViewModel); + } + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE); + intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_STATE); + intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); + intentFilter.addAction(BluetoothAdapter.EXTRA_CONNECTION_STATE); + intentFilter.addAction(BluetoothAdapter.EXTRA_STATE); + intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF"); + intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON"); + registerReceiver(mReceive, intentFilter); + } + + /** + * 注销蓝牙动作广播 + */ + private void unregisterBluetoothReceiver() { + if (mReceive != null) { + unregisterReceiver(mReceive); + mReceive = null; + } + } + + @Override + protected void onDestroy() { + unregisterBluetoothReceiver(); + super.onDestroy(); + } + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java new file mode 100644 index 0000000..2872f40 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java @@ -0,0 +1,921 @@ +package com.bg7yoz.ft8cn; +/** + * -----2022.5.6----- + * MainViewModel类,用于解码FT8信号以及保存与解码有关的变量数据。生存于APP的整个生命周期。 + * 1.解码的总条数。decoded_counter和mutable_Decoded_Counter。 + * 2.解码消息的列表。消息以Ft8Message展示,列表用ArrayList泛型实现。ft8Messages,mutableFt8MessageList。 + * 3.解码和录音都需要时间同步,也就是以UTC时间的每15秒为一个周期。同步事件的触发由UtcTimer类来实现。 + * 4.当前的UTC时间。timerSec,更新频率(心跳频率)由UtcTimer确定,暂定100毫秒。 + * 5.通过类方法getInstance获取当前的MainViewModel的实例,确保有唯一的实例。 + * 6.用HamAudioRecorder类实现录音,目前只实现录音成文件,然后读取文件的数据给解码模块,后面要改成直接给数组的方式----TO DO--- + * 7.解码采用JNI接口调用原生C语言。调用接口名时ft8cn,由cpp文件夹下的CMakeLists.txt维护。各函数的调用接口在decode_ft8.cpp中。 + * -----2022.5.9----- + * 如果系统没有发射信号,触发器会在每一个周期触发录音动作,因录音开始和结束要浪费一些时间,如果不干预上一个录音的动作,将出现 + * 连续的周期内录音动作重叠,造成第二个录音动作失败。所以,第二个周期的录音开始前,要停止前一个周期的录音,造成的结果就是每一次录音 + * 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右 + *

+ * + * @author BG7YOZ + * @date 2022.5.6 + */ + +import static com.bg7yoz.ft8cn.GeneralVariables.getStringFromResource; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.media.AudioManager; +import android.os.Handler; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; + +import com.bg7yoz.ft8cn.callsign.CallsignDatabase; +import com.bg7yoz.ft8cn.callsign.CallsignInfo; +import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; +import com.bg7yoz.ft8cn.connector.BluetoothRigConnector; +import com.bg7yoz.ft8cn.connector.CableConnector; +import com.bg7yoz.ft8cn.connector.CableSerialPort; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.connector.FlexConnector; +import com.bg7yoz.ft8cn.connector.IComWifiConnector; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns; +import com.bg7yoz.ft8cn.database.OperationBand; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.ft8listener.FT8SignalListener; +import com.bg7yoz.ft8cn.ft8listener.OnFt8Listen; +import com.bg7yoz.ft8cn.ft8transmit.FT8TransmitSignal; +import com.bg7yoz.ft8cn.ft8transmit.OnDoTransmitted; +import com.bg7yoz.ft8cn.ft8transmit.OnTransmitSuccess; +import com.bg7yoz.ft8cn.html.LogHttpServer; +import com.bg7yoz.ft8cn.icom.IComWifiRig; +import com.bg7yoz.ft8cn.log.QSLCallsignRecord; +import com.bg7yoz.ft8cn.log.QSLRecord; +import com.bg7yoz.ft8cn.log.SWLQsoList; +import com.bg7yoz.ft8cn.rigs.BaseRig; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.rigs.ElecraftRig; +import com.bg7yoz.ft8cn.rigs.Flex6000Rig; +import com.bg7yoz.ft8cn.rigs.FlexNetworkRig; +import com.bg7yoz.ft8cn.rigs.GuoHeQ900Rig; +import com.bg7yoz.ft8cn.rigs.IcomRig; +import com.bg7yoz.ft8cn.rigs.InstructionSet; +import com.bg7yoz.ft8cn.rigs.KenwoodKT90Rig; +import com.bg7yoz.ft8cn.rigs.KenwoodTS2000Rig; +import com.bg7yoz.ft8cn.rigs.KenwoodTS590Rig; +import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; +import com.bg7yoz.ft8cn.rigs.Wolf_sdr_450Rig; +import com.bg7yoz.ft8cn.rigs.XieGu6100Rig; +import com.bg7yoz.ft8cn.rigs.XieGuRig; +import com.bg7yoz.ft8cn.rigs.Yaesu2Rig; +import com.bg7yoz.ft8cn.rigs.Yaesu38Rig; +import com.bg7yoz.ft8cn.rigs.Yaesu38_450Rig; +import com.bg7yoz.ft8cn.rigs.Yaesu39Rig; +import com.bg7yoz.ft8cn.rigs.YaesuDX10Rig; +import com.bg7yoz.ft8cn.spectrum.SpectrumListener; +import com.bg7yoz.ft8cn.timer.OnUtcTimer; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.bg7yoz.ft8cn.ui.ToastMessage; +import com.bg7yoz.ft8cn.wave.HamRecorder; +import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + + +public class MainViewModel extends ViewModel { + String TAG = "ft8cn MainViewModel"; + public boolean configIsLoaded = false; + + private static MainViewModel viewModel = null;//当前存在的实例。 + //public static Application application; + + + //public int decoded_counter = 0;//解码的总条数 + public final ArrayList ft8Messages = new ArrayList<>();//消息列表 + public UtcTimer utcTimer;//同步触发动作的计时器。 + + //public boolean showTrackerInfo=true; + + //public CallsignDatabase callsignDatabase = null;//呼号信息的数据库 + public DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库 + + + public MutableLiveData mutable_Decoded_Counter = new MutableLiveData<>();//解码的总条数 + public int currentDecodeCount = 0;//本次解码的条数 + public MutableLiveData> mutableFt8MessageList = new MutableLiveData<>();//消息列表 + public MutableLiveData timerSec = new MutableLiveData<>();//当前UTC时间。更新频率由UtcTimer确定,未触发时约100毫秒。 + public MutableLiveData mutableIsRecording = new MutableLiveData<>();//是否处于录音状态 + public MutableLiveData mutableHamRecordIsRunning = new MutableLiveData<>();//HamRecord是否运转 + public MutableLiveData mutableTimerOffset = new MutableLiveData<>();//本周期的时间延迟 + public MutableLiveData mutableIsDecoding = new MutableLiveData<>();//会触发频谱图中的标记动作 + public ArrayList currentMessages = null;//本周期解码的消息(用于画到频谱上) + + public MutableLiveData mutableIsFlexRadio = new MutableLiveData<>();//是不是flex电台 + + private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool(); + private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool(); + private final GetQTHRunnable getQTHRunnable = new GetQTHRunnable(this); + private final SendWaveDataRunnable sendWaveDataRunnable = new SendWaveDataRunnable(); + + + public HamRecorder hamRecorder;//用于录音的对象 + public FT8SignalListener ft8SignalListener;//用于监听FT8信号并解码的对象 + public FT8TransmitSignal ft8TransmitSignal;//用于发射信号用的对象 + public SpectrumListener spectrumListener;//用于画频谱的对象 + public boolean markMessage = true;//是否标记消息开关 + + //控制电台的方式 + public OperationBand operationBand = null; + + private SWLQsoList swlQsoList = new SWLQsoList();//用于记录SWL的QSO对象,对SWL QSO做判断,防止重复。 + + + public MutableLiveData> mutableSerialPorts = new MutableLiveData<>(); + private ArrayList serialPorts;//串口列表 + public BaseRig baseRig;//电台 + private final OnRigStateChanged onRigStateChanged = new OnRigStateChanged() { + @Override + public void onDisconnected() { + //与电台连接中断 + ToastMessage.show(getStringFromResource(R.string.disconnect_rig)); + } + + @Override + public void onConnected() { + //与电台建立连接 + ToastMessage.show(getStringFromResource(R.string.connected_rig)); + } + + @Override + public void onPttChanged(boolean isOn) { + + } + + @Override + public void onFreqChanged(long freq) { + //当前频率:%s + ToastMessage.show(String.format(getStringFromResource(R.string.current_frequency) + , BaseRigOperation.getFrequencyAllInfo(freq))); + //把频率的变化写回到全局变量中 + GeneralVariables.band = freq; + GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(freq); + GeneralVariables.mutableBandChange.postValue(GeneralVariables.bandListIndex); + + databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 + + } + + @Override + public void onRunError(String message) { + //与电台通讯出现错误, + ToastMessage.show(String.format(getStringFromResource(R.string.radio_communication_error) + , message)); + } + }; + + //发射信号用的消息列表 + //public ArrayList transmitMessages = new ArrayList<>(); + //public MutableLiveData> mutableTransmitMessages = new MutableLiveData<>(); + public MutableLiveData mutableTransmitMessagesCount = new MutableLiveData<>(); + + + public boolean deNoise = false;//在频谱中抑制噪声 + + //*********日志查询需要的变量******************** + public boolean logListShowCallsign = false;//在日志查询列表的表现形式 + public String queryKey = "";//查询的关键字 + public int queryFilter = 0;//过滤,0全部,1,确认,2,未确认 + public MutableLiveData mutableQueryFilter = new MutableLiveData<>(); + public ArrayList callsignRecords = new ArrayList<>(); + //public ArrayList qslRecords=new ArrayList<>(); + //******************************************** + //关注呼号的列表 + //public ArrayList followCallsign = new ArrayList<>(); + + + //日志管理HTTP SERVER + private final LogHttpServer httpServer; + + /** + * 获取MainViewModel的实例,确保存在唯一的MainViewModel实例,该实例在APP的全部生存周期中。 + * + * @param owner ViewModelStoreOwner 所有者,一般为Activity或Fragment。 + * @return MainViewModel 返回一个MainViewModel实例 + */ + public static MainViewModel getInstance(ViewModelStoreOwner owner) { + if (viewModel == null) { + viewModel = new ViewModelProvider(owner).get(MainViewModel.class); + } + return viewModel; + } + + /** + * 获取消息列表中指定的消息 + * + * @param position 在Mutable类型的列表中的位置 + * @return 返回一个Ft8Message类型的解码后的信息 + */ + public Ft8Message getFt8Message(int position) { + return Objects.requireNonNull(ft8Messages.get(position)); + } + + /** + * MainViewModel的构造函数主要完成一下事情: + * 1.创建与UTC同步的时钟,时钟是UtcTimer类,内核是用Timer和TimerTask实现的。回调函数是多线程的,要考虑线程安全的问题。 + * 2.创建Mutable型的解码消息列表。 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + public MainViewModel() { + + //获取配置信息。 + databaseOpr = DatabaseOpr.getInstance(GeneralVariables.getMainContext() + , "data.db"); + mutableIsDecoding.postValue(false);//解码状态 + //创录音对象 + hamRecorder = new HamRecorder(null); + hamRecorder.startRecord(); + + mutableIsFlexRadio.setValue(false); + + //创建用于显示时间的计时器 + utcTimer = new UtcTimer(10, false, new OnUtcTimer() { + @Override + public void doHeartBeatTimer(long utc) {//不触发时的时钟信息 + + } + + @Override + public void doOnSecTimer(long utc) {//当指定间隔时触发时 + timerSec.postValue(utc);//发送当前UTC时间 + mutableIsRecording.postValue(hamRecorder.isRunning()); + mutableHamRecordIsRunning.postValue(hamRecorder.isRunning());//发送当前计时器状态 + } + }); + utcTimer.start();//启动计时器 + + //同步一下时间。microsoft的NTP服务器 + UtcTimer.syncTime(null); + + mutableFt8MessageList.setValue(ft8Messages); + + //创建监听对象,回调中的动作用于处理解码、发射、关注的呼号列表添加等操作 + ft8SignalListener = new FT8SignalListener(databaseOpr, new OnFt8Listen() { + @Override + public void beforeListen(long utc) { + mutableIsDecoding.postValue(true); + } + + @Override + public void afterDecode(long utc, float time_sec, int sequential + , ArrayList messages, boolean isDeep) { + if (messages.size() == 0) return;//没有解码出消息,不触发动作 + + synchronized (ft8Messages) { + ft8Messages.addAll(messages);//添加消息到列表 + } + GeneralVariables.deleteArrayListMore(ft8Messages);//删除多余的消息,FT8CN限定的可展示消息的总数量 + + mutableFt8MessageList.postValue(ft8Messages);//触发添加消息的动作,让界面能观察到 + mutableTimerOffset.postValue(time_sec);//本次时间偏移量 + + + findIncludedCallsigns(messages);//查找符合条件的消息,放到呼叫列表中 + + //检查发射程序。从消息列表中解析发射的程序 + //超出周期2秒钟,就不应该解析了 + if (!ft8TransmitSignal.isTransmitting() + && (ft8SignalListener.timeSec + + GeneralVariables.pttDelay + + GeneralVariables.transmitDelay <= 2000)) {//考虑网络模式,发射时长是13秒 + ft8TransmitSignal.parseMessageToFunction(messages);//解析消息,并处理 + } + + currentMessages = messages; + + if (isDeep) { + currentDecodeCount += messages.size(); + } else { + currentDecodeCount = messages.size(); + } + + mutableIsDecoding.postValue(false);//解码的状态,会触发频谱图中的标记动作 + + + getQTHRunnable.messages = messages; + getQTHThreadPool.execute(getQTHRunnable);//用线程池的方式查询归属地 + + //此变量也是告诉消息列表变化的 + mutable_Decoded_Counter.postValue( + currentDecodeCount);//告知界面消息的总数量 + + if (GeneralVariables.saveSWLMessage) { + databaseOpr.writeMessage(messages);//把SWL消息写到数据库 + } + //检查QSO of SWL,并保存到SWLQSOTable中的通联列表qsoList中 + if (GeneralVariables.saveSWL_QSO) { + swlQsoList.findSwlQso(messages, ft8Messages, new SWLQsoList.OnFoundSwlQso() { + @Override + public void doFound(QSLRecord record) { + databaseOpr.addSWL_QSO(record);//把SWL QSO保存到数据库 + ToastMessage.show(record.swlQSOInfo()); + } + }); + } + //从列表中查找呼号和网格对应关系,并添加到表中 + getCallsignAndGrid(messages); + } + }); + + ft8SignalListener.setOnWaveDataListener(new FT8SignalListener.OnWaveDataListener() { + @Override + public void getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone) { + hamRecorder.getVoiceData(duration, afterDoneRemove, getVoiceDataDone); + } + }); + + + ft8SignalListener.startListen(); + + //频谱监听对象 + spectrumListener = new SpectrumListener(hamRecorder); + + + //创建发射对象,回调:发射前,发射后、QSL成功后。 + ft8TransmitSignal = new FT8TransmitSignal(databaseOpr, new OnDoTransmitted() { + @Override + public void onBeforeTransmit(Ft8Message message, int functionOder) { + if (GeneralVariables.controlMode == ControlMode.CAT + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + if (baseRig != null) { + if (GeneralVariables.connectMode != ConnectMode.NETWORK) stopSco(); + baseRig.setPTT(true); + } + } + if (ft8TransmitSignal.isActivated()) { + GeneralVariables.transmitMessages.add(message); + //mutableTransmitMessages.postValue(GeneralVariables.transmitMessages); + mutableTransmitMessagesCount.postValue(1); + } + } + + @Override + public void onAfterTransmit(Ft8Message message, int functionOder) { + if (GeneralVariables.controlMode == ControlMode.CAT + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + if (baseRig != null) { + baseRig.setPTT(false); + if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco(); + } + } + } + + @Override + public void onTransmitByWifi(Ft8Message msg) { + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + if (baseRig != null) { + if (baseRig.isConnected()) { + sendWaveDataRunnable.baseRig = baseRig; + sendWaveDataRunnable.message = msg; + //以线程池的方式执行网络数据包发送 + sendWaveDataThreadPool.execute(sendWaveDataRunnable); + } + } + } + } + }, new OnTransmitSuccess() {//当通联成功时 + @Override + public void doAfterTransmit(QSLRecord qslRecord) { + databaseOpr.addQSL_Callsign(qslRecord);//两个操作,把呼号和QSL记录下来 + if (qslRecord.getToCallsign() != null) {//把通联成功的分区加入到分区列表 + GeneralVariables.callsignDatabase.getCallsignInformation(qslRecord.getToCallsign() + , new OnAfterQueryCallsignLocation() { + @Override + public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { + GeneralVariables.addDxcc(callsignInfo.DXCC); + GeneralVariables.addItuZone(callsignInfo.ITUZone); + GeneralVariables.addCqZone(callsignInfo.CQZone); + } + }); + } + } + }); + + + //打开HTTP SERVER + httpServer = new LogHttpServer(this, LogHttpServer.DEFAULT_PORT); + try { + httpServer.start(); + } catch (IOException e) { + Log.e(TAG, "http server error:" + e.getMessage()); + } + } + + public void setTransmitIsFreeText(boolean isFreeText) { + if (ft8TransmitSignal != null) { + ft8TransmitSignal.setTransmitFreeText(isFreeText); + } + } + + public boolean getTransitIsFreeText() { + if (ft8TransmitSignal != null) { + return ft8TransmitSignal.isTransmitFreeText(); + } + return false; + } + + + /** + * 查找符合条件的消息,放到呼叫列表中 + * + * @param messages 消息 + */ + private synchronized void findIncludedCallsigns(ArrayList messages) { + Log.d(TAG, "findIncludedCallsigns: 查找关注的呼号"); + if (ft8TransmitSignal.isActivated() && ft8TransmitSignal.sequential != UtcTimer.getNowSequential()) { + return; + } + int count = 0; + for (Ft8Message msg : messages) { + //与我的呼号有关,与关注的呼号有关 + if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign) + || msg.getCallsignTo().equals(GeneralVariables.myCallsign) + || GeneralVariables.callsignInFollow(msg.getCallsignFrom()) + || (GeneralVariables.callsignInFollow(msg.getCallsignTo()) && (msg.getCallsignTo() != null)) + || (GeneralVariables.autoFollowCQ && msg.checkIsCQ())) {//是CQ,并且允许关注CQ + //看不是通联成功的呼号的消息 + msg.isQSL_Callsign = GeneralVariables.checkQSLCallsign(msg.getCallsignFrom()); + if (!GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//不在排除呼号前缀的,才加入列表 + count++; + GeneralVariables.transmitMessages.add(msg); + } + } + } + GeneralVariables.deleteArrayListMore(GeneralVariables.transmitMessages);//删除多余的消息 + //mutableTransmitMessages.postValue(GeneralVariables.transmitMessages); + mutableTransmitMessagesCount.postValue(count); + } + + /** + * 清除传输消息列表 + */ + public void clearTransmittingMessage() { + GeneralVariables.transmitMessages.clear(); + mutableTransmitMessagesCount.postValue(0); + } + + + /** + * 从消息列表中查找呼号和网格的对应关系 + * + * @param messages 消息列表 + */ + private void getCallsignAndGrid(ArrayList messages) { + for (Ft8Message msg : messages) { + if (GeneralVariables.checkFun1(msg.extraInfo)) {//检查是不是网格 + //如果内存表中没有,或不一致,就写入数据库中 + if (!GeneralVariables.getCallsignHasGrid(msg.getCallsignFrom(), msg.maidenGrid)) { + databaseOpr.addCallsignQTH(msg.getCallsignFrom(), msg.maidenGrid);//写数据库 + } + GeneralVariables.addCallsignAndGrid(msg.getCallsignFrom(), msg.maidenGrid); + } + } + } + + /** + * 清除消息列表 + */ + public void clearFt8MessageList() { + ft8Messages.clear(); + mutable_Decoded_Counter.postValue(ft8Messages.size()); + mutableFt8MessageList.postValue(ft8Messages); + } + + + /** + * 删除单个文件 + * + * @param fileName 要删除的文件的文件名 + */ + public static void deleteFile(String fileName) { + File file = new File(fileName); + // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除 + if (file.exists() && file.isFile()) { + file.delete(); + } + } + + /** + * 向关注的呼号列表添加呼号 + * + * @param callsign 呼号 + */ + public void addFollowCallsign(String callsign) { + if (!GeneralVariables.followCallsign.contains(callsign)) { + GeneralVariables.followCallsign.add(callsign); + databaseOpr.addFollowCallsign(callsign); + } + } + + + /** + * 从数据库中获取关注的呼号列表 + */ + public void getFollowCallsignsFromDataBase() { + databaseOpr.getFollowCallsigns(new OnAfterQueryFollowCallsigns() { + @Override + public void doOnAfterQueryFollowCallsigns(ArrayList callsigns) { + for (String s : callsigns) { + if (!GeneralVariables.followCallsign.contains(s)) { + GeneralVariables.followCallsign.add(s); + } + } + } + }); + } + + + /** + * 设置操作载波频率。如果电台没有连接,就有操作 + */ + public void setOperationBand() { + if (!isRigConnected()) { + return; + } + + //先设置上边带,再设置频率 + baseRig.setUsbModeToRig();//设置上边带 + + //此处延迟1秒发送第二个指令,是防止协谷X6100断开连接的问题 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + baseRig.setFreq(GeneralVariables.band);//设置频率 + baseRig.setFreqToRig(); + } + }, 800); + } + + public void setCivAddress() { + if (baseRig != null) { + baseRig.setCivAddress(GeneralVariables.civAddress); + } + } + + public void setControlMode() { + if (baseRig != null) { + baseRig.setControlMode(GeneralVariables.controlMode); + } + } + + + /** + * 通过USB连接电台 + * + * @param context context + * @param port 串口 + */ + public void connectCableRig(Context context, CableSerialPort.SerialPort port) { + if (GeneralVariables.controlMode == ControlMode.VOX) {//如果当前是VOX,就改成CAT模式 + GeneralVariables.controlMode = ControlMode.CAT; + } + connectRig(); + + if (baseRig == null) { + return; + } + baseRig.setControlMode(GeneralVariables.controlMode); + CableConnector connector = new CableConnector(context, port, GeneralVariables.baudRate + , GeneralVariables.controlMode); + baseRig.setOnRigStateChanged(onRigStateChanged); + baseRig.setConnector(connector); + connector.connect(); + + //晚1秒钟设置模式,防止有的电台反应不过来 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setOperationBand();//设置载波频率 + } + }, 1000); + + } + + public void connectBluetoothRig(Context context, BluetoothDevice device) { + GeneralVariables.controlMode = ControlMode.CAT;//蓝牙控制模式,只能是CAT控制 + connectRig(); + if (baseRig == null) { + return; + } + baseRig.setControlMode(GeneralVariables.controlMode); + BluetoothRigConnector connector = BluetoothRigConnector.getInstance(context, device.getAddress() + , GeneralVariables.controlMode); + baseRig.setOnRigStateChanged(onRigStateChanged); + baseRig.setConnector(connector); + + new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的,等2秒再设置频率 + @Override + public void run() { + setOperationBand();//设置载波频率 + } + }, 5000); + } + + public void connectIComWifiRig(Context context, IComWifiRig iComWifiRig) { + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + if (baseRig != null) { + if (baseRig.getConnector() != null) { + baseRig.getConnector().disconnect(); + } + } + } + + GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式 + IComWifiConnector iComWifiConnector = new IComWifiConnector(GeneralVariables.controlMode + , iComWifiRig); + iComWifiConnector.setOnWifiDataReceived(new IComWifiConnector.OnWifiDataReceived() { + @Override + public void OnWaveReceived(int bufferLen, float[] buffer) { + hamRecorder.doOnWaveDataReceived(bufferLen, buffer); + } + + @Override + public void OnCivReceived(byte[] data) { + + } + }); + iComWifiConnector.connect(); + connectRig(); + + baseRig.setControlMode(GeneralVariables.controlMode); + baseRig.setOnRigStateChanged(onRigStateChanged); + baseRig.setConnector(iComWifiConnector); +// + new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的,等2秒再设置频率 + @Override + public void run() { + setOperationBand();//设置载波频率 + } + }, 1000); + } + + /** + * 连接到flexRadio + * + * @param context context + * @param flexRadio flexRadio对象 + */ + public void connectFlexRadioRig(Context context, FlexRadio flexRadio) { + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + if (baseRig != null) { + if (baseRig.getConnector() != null) { + baseRig.getConnector().disconnect(); + } + } + } + GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式 + FlexConnector flexConnector = new FlexConnector(context, flexRadio, GeneralVariables.controlMode); + flexConnector.setOnWaveDataReceived(new FlexConnector.OnWaveDataReceived() { + @Override + public void OnDataReceived(int bufferLen, float[] buffer) { + hamRecorder.doOnWaveDataReceived(bufferLen, buffer); + } + }); + flexConnector.connect(); + connectRig(); + + baseRig.setOnRigStateChanged(onRigStateChanged); + baseRig.setConnector(flexConnector); +// + new Handler().postDelayed(new Runnable() {//连接是需要时间的,等2秒再设置频率 + @Override + public void run() { + setOperationBand();//设置载波频率 + } + }, 3000); + } + + + /** + * 根据指令集创建不同型号的电台 + */ + private void connectRig() { + if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK) + || (GeneralVariables.instructionSet == InstructionSet.ICOM + && GeneralVariables.connectMode == ConnectMode.NETWORK)) { + hamRecorder.setDataFromLan(); + } else { + hamRecorder.setDataFromMic(); + } + baseRig = null; + //此处判断是用什么类型的电台,ICOM,YAESU 2,YAESU 3 + switch (GeneralVariables.instructionSet) { + case InstructionSet.ICOM: + baseRig = new IcomRig(GeneralVariables.civAddress); + break; + case InstructionSet.YAESU_2: + baseRig = new Yaesu2Rig(); + break; + case InstructionSet.YAESU_3_9: + baseRig = new Yaesu39Rig();//yaesu3代指令,9位频率 + break; + case InstructionSet.YAESU_3_8: + baseRig = new Yaesu38Rig();//yaesu3代指令,8位频率 + break; + case InstructionSet.YAESU_3_450: + baseRig = new Yaesu38_450Rig();//yaesu3代指令,8位频率 + break; + case InstructionSet.KENWOOD_TK90: + baseRig = new KenwoodKT90Rig();//建伍TK90 + break; + case InstructionSet.YAESU_DX10: + baseRig = new YaesuDX10Rig();//YAESU DX10 DX101 + break; + case InstructionSet.KENWOOD_TS590: + baseRig = new KenwoodTS590Rig();//KENWOOD TS590 + break; + case InstructionSet.GUOHE_Q900: + baseRig = new GuoHeQ900Rig();//国赫Q900 + break; + case InstructionSet.XIEGUG90S://协谷,USB模式 + baseRig = new XieGuRig(GeneralVariables.civAddress);//协谷G90S + break; + case InstructionSet.ELECRAFT: + baseRig = new ElecraftRig();//ELECRAFT + break; + case InstructionSet.FLEX_CABLE: + baseRig = new Flex6000Rig();//FLEX6000 + break; + case InstructionSet.FLEX_NETWORK: + baseRig = new FlexNetworkRig(); + break; + case InstructionSet.XIEGU_6100: + baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100 + break; + case InstructionSet.KENWOOD_TS2000: + baseRig = new KenwoodTS2000Rig();//建伍TS2000 + break; + case InstructionSet.WOLF_SDR_DIGU: + baseRig = new Wolf_sdr_450Rig(false); + break; + case InstructionSet.WOLF_SDR_USB: + baseRig = new Wolf_sdr_450Rig(true); + break; + } + + mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK); + + } + + + /** + * 检察电台是否处于连接状态,两种情况:rigBaseClass没建立,串口没连接成功 + * + * @return 是否连接 + */ + public boolean isRigConnected() { + if (baseRig == null) { + return false; + } else { + return baseRig.isConnected(); + } + } + + /** + * 获取串口设备列表 + */ + public void getUsbDevice() { + serialPorts = + CableSerialPort.listSerialPorts(GeneralVariables.getMainContext()); + mutableSerialPorts.postValue(serialPorts); + } + + + public void startSco() { + AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() + .getSystemService(Context.AUDIO_SERVICE); + if (audioManager == null) return; + if (!audioManager.isBluetoothScoAvailableOffCall()) { + //蓝牙设备不支持录音 + ToastMessage.show(getStringFromResource(R.string.does_not_support_recording)); + return; + } + audioManager.setBluetoothScoOn(true); + audioManager.startBluetoothSco();//71毫秒 + audioManager.setSpeakerphoneOn(false);//进入耳机模式 + } + + public void stopSco() { + AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() + .getSystemService(Context.AUDIO_SERVICE); + if (audioManager == null) return; + if (audioManager.isBluetoothScoOn()) { + audioManager.setBluetoothScoOn(false); + audioManager.stopBluetoothSco(); + audioManager.setSpeakerphoneOn(true);//退出耳机模式 + } + + } + + + public void setBlueToothOn() { + AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() + .getSystemService(Context.AUDIO_SERVICE); + if (audioManager == null) return; + if (!audioManager.isBluetoothScoAvailableOffCall()) { + //蓝牙设备不支持录音 + ToastMessage.show(getStringFromResource(R.string.does_not_support_recording)); + } + + /* + 播放音乐的对应的就是MODE_NORMAL, 如果使用外放播则调用audioManager.setSpeakerphoneOn(true)即可. + 若使用耳机和听筒,则需要先设置模式为MODE_IN_CALL(3.0以前)或MODE_IN_COMMUNICATION(3.0以后). + */ + audioManager.setMode(AudioManager.MODE_NORMAL);//178毫秒 + audioManager.setBluetoothScoOn(true); + audioManager.stopBluetoothSco(); + audioManager.startBluetoothSco();//71毫秒 + audioManager.setSpeakerphoneOn(false);//进入耳机模式 + + //进入到蓝牙耳机模式 + ToastMessage.show(getStringFromResource(R.string.bluetooth_headset_mode)); + + } + + public void setBlueToothOff() { + + AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext() + .getSystemService(Context.AUDIO_SERVICE); + if (audioManager == null) return; + if (audioManager.isBluetoothScoOn()) { + audioManager.setMode(AudioManager.MODE_NORMAL); + audioManager.setBluetoothScoOn(false); + audioManager.stopBluetoothSco(); + audioManager.setSpeakerphoneOn(true);//退出耳机模式 + } + //离开蓝牙耳机模式 + ToastMessage.show(getStringFromResource(R.string.bluetooth_Headset_mode_cancelled)); + + } + + + /** + * 查询蓝牙是否连接 + * + * @return 是否 + */ + @SuppressLint("MissingPermission") + public boolean isBTConnected() { + BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter(); + if (blueAdapter == null) return false; + + //蓝牙头戴式耳机,支持语音输入输出 + int headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); + int a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP); + return headset == BluetoothAdapter.STATE_CONNECTED || a2dp == BluetoothAdapter.STATE_CONNECTED; + } + + private static class GetQTHRunnable implements Runnable { + MainViewModel mainViewModel; + ArrayList messages; + + public GetQTHRunnable(MainViewModel mainViewModel) { + this.mainViewModel = mainViewModel; + } + + + @Override + public void run() { + CallsignDatabase.getMessagesLocation( + GeneralVariables.callsignDatabase.getDb(), messages); + mainViewModel.mutableFt8MessageList.postValue(mainViewModel.ft8Messages); + } + } + + private static class SendWaveDataRunnable implements Runnable { + BaseRig baseRig; + //float[] data; + Ft8Message message; + + @Override + public void run() { + if (baseRig != null && message != null) { + baseRig.sendWaveData(message);//实际生成的数据是12.64+0.04,0.04是生成的0数据 + } + } + } + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java new file mode 100644 index 0000000..e556f62 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java @@ -0,0 +1,55 @@ +package com.bg7yoz.ft8cn; +/** + * 呼号的哈希码列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import java.util.HashMap; + +public class MessageHashMap extends HashMap { + private static final String TAG = "MessageHashMap"; + + /** + * 添加呼号和哈希码到列表 + * + * @param hashCode 哈希码 + * @param callsign 呼号 + * @return false说明已经存在了 + */ + public synchronized void addHash(long hashCode, String callsign) { + //if (callsign.length()<2){return;} + //if (){return;} + if (callsign.equals("CQ")||callsign.equals("QRZ")||callsign.equals("DE")){ + return; + } + if (hashCode == 0 || checkHash(hashCode)|| callsign.charAt(0) == '<') { + return; + } + Log.d(TAG, String.format("addHash: callsign:%s ,hash:%x",callsign,hashCode )); + put(hashCode,callsign); + } + + //检查是否存在这个hash码 + public boolean checkHash(long hashCode) { + return get(hashCode)!=null; +// for (HashStruct hash : this) { +// if (hash.hashCode == hashCode) { +// return true; +// } +// } +// return false; + } + + //通过哈希码查呼号 + public synchronized String getCallsign(long[] hashCode) { + for (long l : hashCode) { + if (checkHash(l)) { + return String.format("<%s>", get(l)); + } + } + return "<...>"; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java new file mode 100644 index 0000000..bfc10bd --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java @@ -0,0 +1,63 @@ +package com.bg7yoz.ft8cn.bluetooth; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.ParcelUuid; + +import com.bg7yoz.ft8cn.BuildConfig; + +/** + * 与蓝牙有关的常量 + */ + +public class BluetoothConstants { + + // values have to be globally unique + static final String INTENT_ACTION_DISCONNECT = BuildConfig.APPLICATION_ID + ".Disconnect"; + static final String NOTIFICATION_CHANNEL = BuildConfig.APPLICATION_ID + ".Channel"; + static final String INTENT_CLASS_MAIN_ACTIVITY = BuildConfig.APPLICATION_ID + ".MainActivity"; + + // values have to be unique within each app + static final int NOTIFY_MANAGER_START_FOREGROUND_SERVICE = 1001; + + + public static boolean checkBluetoothIsOpen(){ + BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter(); + if (adapter==null){ + return false; + }else { + return adapter.isEnabled(); + } + } + + public static boolean checkIsSpp(BluetoothDevice device) { + @SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids(); + + if (parcelUuids != null) { + for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的 + if (parcelUuids[i].getUuid().toString().toUpperCase().equals("00001101-0000-1000-8000-00805F9B34FB")) { + return true; + } + } + } + return false; + } + + public static boolean checkIsHeadSet(BluetoothDevice device){ + @SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids(); + boolean audioSinkService=false; + boolean handsFreeService=false; + if (parcelUuids != null) { + for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的 + if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000111e-0000-1000-8000-00805f9b34fb")) { + handsFreeService=true; + } + if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000110b-0000-1000-8000-00805f9b34fb")) { + audioSinkService=true; + } + } + } + return audioSinkService&&handsFreeService; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java new file mode 100644 index 0000000..55ad4c2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java @@ -0,0 +1,13 @@ +package com.bg7yoz.ft8cn.bluetooth; + +/** + * 蓝牙串口的回调接口 + * BG7YOZ + * 2023-03 + */ +public interface BluetoothSerialListener { + void onSerialConnect (); + void onSerialConnectError (Exception e); + void onSerialRead (byte[] data); + void onSerialIoError (Exception e); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java new file mode 100644 index 0000000..6d70f42 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java @@ -0,0 +1,213 @@ +package com.bg7yoz.ft8cn.bluetooth; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; + +import androidx.annotation.Nullable; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.Queue; + +/** + * 蓝牙串口有关的服务 + * BG7YOZ + * 2023-03 + */ +public class BluetoothSerialService extends Service implements BluetoothSerialListener { + + public class SerialBinder extends Binder { + public BluetoothSerialService getService() { return BluetoothSerialService.this; } + } + + private enum QueueType {Connect, ConnectError, Read, IoError} + + private static class QueueItem { + QueueType type; + byte[] data; + Exception e; + + QueueItem(QueueType type, byte[] data, Exception e) { this.type=type; this.data=data; this.e=e; } + } + + private final Handler mainLooper; + private final IBinder binder; + private final Queue queue1, queue2; + + private BluetoothSerialSocket socket; + private BluetoothSerialListener listener; + private boolean connected; + + /** + * Lifecylce + */ + public BluetoothSerialService() { + mainLooper = new Handler(Looper.getMainLooper()); + binder = new SerialBinder(); + queue1 = new LinkedList<>(); + queue2 = new LinkedList<>(); + } + + @Override + public void onDestroy() { + //cancelNotification(); + disconnect(); + super.onDestroy(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + /** + * Api + */ + public void connect(BluetoothSerialSocket socket) throws IOException { + socket.connect(this); + this.socket = socket; + connected = true; + } + + public void disconnect() { + connected = false; // ignore data,errors while disconnecting + //cancelNotification(); + if(socket != null) { + socket.disconnect(); + socket = null; + } + } + + public void write(byte[] data) throws IOException { + if(!connected) + throw new IOException("not connected"); + socket.write(data); + } + + public void attach(BluetoothSerialListener listener) { + if(Looper.getMainLooper().getThread() != Thread.currentThread()) + throw new IllegalArgumentException("not in main thread"); + //cancelNotification(); + // use synchronized() to prevent new items in queue2 + // new items will not be added to queue1 because mainLooper.post and attach() run in main thread + synchronized (this) { + this.listener = listener; + } + for(QueueItem item : queue1) { + switch(item.type) { + case Connect: listener.onSerialConnect (); break; + case ConnectError: listener.onSerialConnectError (item.e); break; + case Read: listener.onSerialRead (item.data); break; + case IoError: listener.onSerialIoError (item.e); break; + } + } + for(QueueItem item : queue2) { + switch(item.type) { + case Connect: listener.onSerialConnect (); break; + case ConnectError: listener.onSerialConnectError (item.e); break; + case Read: listener.onSerialRead (item.data); break; + case IoError: listener.onSerialIoError (item.e); break; + } + } + queue1.clear(); + queue2.clear(); + } + + public void detach() { + if (connected){ + disconnect(); + } + listener = null; + } + + + + /** + * SerialListener + */ + public void onSerialConnect() { + if(connected) { + synchronized (this) { + if (listener != null) { + mainLooper.post(() -> { + if (listener != null) { + listener.onSerialConnect(); + } else { + queue1.add(new QueueItem(QueueType.Connect, null, null)); + } + }); + } else { + queue2.add(new QueueItem(QueueType.Connect, null, null)); + } + } + } + } + + public void onSerialConnectError(Exception e) { + if(connected) { + synchronized (this) { + if (listener != null) { + mainLooper.post(() -> { + if (listener != null) { + listener.onSerialConnectError(e); + } else { + queue1.add(new QueueItem(QueueType.ConnectError, null, e)); + //cancelNotification(); + disconnect(); + } + }); + } else { + queue2.add(new QueueItem(QueueType.ConnectError, null, e)); + //cancelNotification(); + disconnect(); + } + } + } + } + + public void onSerialRead(byte[] data) { + if(connected) { + synchronized (this) { + if (listener != null) { + mainLooper.post(() -> { + if (listener != null) { + listener.onSerialRead(data); + } else { + queue1.add(new QueueItem(QueueType.Read, data, null)); + } + }); + } else { + queue2.add(new QueueItem(QueueType.Read, data, null)); + } + } + } + } + + public void onSerialIoError(Exception e) { + if(connected) { + synchronized (this) { + if (listener != null) { + mainLooper.post(() -> { + if (listener != null) { + listener.onSerialIoError(e); + } else { + queue1.add(new QueueItem(QueueType.IoError, null, e)); + //cancelNotification(); + disconnect(); + } + }); + } else { + queue2.add(new QueueItem(QueueType.IoError, null, e)); + //cancelNotification(); + disconnect(); + } + } + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java new file mode 100644 index 0000000..626db39 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java @@ -0,0 +1,127 @@ +package com.bg7yoz.ft8cn.bluetooth; +/** + * 蓝牙串口的SOCKET + * BG7YOZ + * 2023-03 + */ + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import java.io.IOException; +import java.security.InvalidParameterException; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.Executors; + +public class BluetoothSerialSocket implements Runnable { + + private static final UUID BLUETOOTH_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + + private final BroadcastReceiver disconnectBroadcastReceiver; + + private final Context context; + private BluetoothSerialListener listener; + private final BluetoothDevice device; + private BluetoothSocket socket; + private boolean connected; + + public BluetoothSerialSocket(Context context, BluetoothDevice device) { + if(context instanceof Activity) + throw new InvalidParameterException("expected non UI context"); + this.context = context; + this.device = device; + disconnectBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if(listener != null) + listener.onSerialIoError(new IOException("background disconnect")); + disconnect(); // disconnect now, else would be queued until UI re-attached + } + }; + } + + @SuppressLint("MissingPermission") + String getName() { + return device.getName() != null ? device.getName() : device.getAddress(); + } + + /** + * connect-success and most connect-errors are returned asynchronously to listener + */ + void connect(BluetoothSerialListener listener) throws IOException { + this.listener = listener; + context.registerReceiver(disconnectBroadcastReceiver, new IntentFilter(BluetoothConstants.INTENT_ACTION_DISCONNECT)); + Executors.newCachedThreadPool().submit(this); + } + + void disconnect() { + listener = null; // ignore remaining data and errors + // connected = false; // run loop will reset connected + if(socket != null) { + try { + socket.close(); + } catch (Exception ignored) { + } + socket = null; + } + try { + context.unregisterReceiver(disconnectBroadcastReceiver); + } catch (Exception ignored) { + } + } + + void write(byte[] data) throws IOException { + if (!connected) + throw new IOException("not connected"); + socket.getOutputStream().write(data); + } + + @SuppressLint("MissingPermission") + @Override + public void run() { // connect & read + try { + socket = device.createRfcommSocketToServiceRecord(BLUETOOTH_SPP); + socket.connect(); + if(listener != null) + listener.onSerialConnect(); + } catch (Exception e) { + if(listener != null) + listener.onSerialConnectError(e); + try { + socket.close(); + } catch (Exception ignored) { + } + socket = null; + return; + } + connected = true; + try { + byte[] buffer = new byte[1024]; + int len; + //noinspection InfiniteLoopStatement + while (true) { + len = socket.getInputStream().read(buffer); + byte[] data = Arrays.copyOf(buffer, len); + if(listener != null) + listener.onSerialRead(data); + } + } catch (Exception e) { + connected = false; + if (listener != null) + listener.onSerialIoError(e); + try { + socket.close(); + } catch (Exception ignored) { + } + socket = null; + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java new file mode 100644 index 0000000..9618712 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java @@ -0,0 +1,109 @@ +package com.bg7yoz.ft8cn.bluetooth; +/** + * 蓝牙状态广播类。连接、断开、变化 + * @writer bg7yoz + * @date 2022-07-22 + */ + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +public class BluetoothStateBroadcastReceive extends BroadcastReceiver { + private static final String TAG="BluetoothStateBroadcastReceive"; + private Context context; + private MainViewModel mainViewModel; + + public BluetoothStateBroadcastReceive(Context context, MainViewModel mainViewModel) { + this.context = context; + this.mainViewModel = mainViewModel; + } + + @SuppressLint("MissingPermission") + @Override + public void onReceive(Context context, Intent intent) { + this.context=context; + String action = intent.getAction(); + + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter(); + int headset=-1; + int a2dp=-1; + if (blueAdapter!=null) { + headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); + a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP); + } + switch (action) { + case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED: + case BluetoothAdapter.EXTRA_CONNECTION_STATE: + case BluetoothAdapter.EXTRA_STATE: + if(headset == BluetoothProfile.STATE_CONNECTED ||a2dp==BluetoothProfile.STATE_CONNECTED){ + //if(headset == BluetoothProfile.STATE_CONNECTED){ + //if(a2dp==BluetoothProfile.STATE_CONNECTED){ + mainViewModel.setBlueToothOn(); + }else { + mainViewModel.setBlueToothOff(); + } + break; + + case BluetoothDevice.ACTION_ACL_CONNECTED: + if (device!=null) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.bluetooth_is_connected) + ,device.getName())); + } + break; + + case BluetoothDevice.ACTION_ACL_DISCONNECTED: + if (device!=null) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.bluetooth_is_diconnected) + ,device.getName())); + } + break; + + case AudioManager.ACTION_AUDIO_BECOMING_NOISY: + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.sound_source_switched)); + break; + + + case BluetoothAdapter.ACTION_STATE_CHANGED: + int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); + switch (blueState) { + case BluetoothAdapter.STATE_OFF: + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_off)); + break; + case BluetoothAdapter.STATE_ON: + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_on)); + break; + } + break; + + } + } + +// static final int PROFILE_HEADSET = 0; +// static final int PROFILE_A2DP = 1; +// static final int PROFILE_OPP = 2; +// static final int PROFILE_HID = 3; +// static final int PROFILE_PANU = 4; +// static final int PROFILE_NAP = 5; +// static final int PROFILE_A2DP_SINK = 6; +// +// private boolean checkBluetoothClass(BluetoothClass bluetoothClass,int proFile){ +// if (proFile==PROFILE_A2DP){ +// bluetoothClass.hasService(BluetoothClass.Service.RENDER); +// return true; +// } +// } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java new file mode 100644 index 0000000..dde5f67 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java @@ -0,0 +1,277 @@ +package com.bg7yoz.ft8cn.callsign; +/** + * 用于呼号归属地查询的数据库操作,数据库采用内存方式。来源是CTY.DAT + * @author BG7YOZ + * 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.AsyncTask; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.google.android.gms.maps.model.LatLng; + +import java.util.ArrayList; +import java.util.Set; + +public class CallsignDatabase extends SQLiteOpenHelper { + private static final String TAG = "CallsignDatabase"; + @SuppressLint("StaticFieldLeak") + private static CallsignDatabase instance; + private final Context context; + private SQLiteDatabase db; + + public static CallsignDatabase getInstance(@Nullable Context context, @Nullable String databaseName, int version) { + if (instance == null) { + instance = new CallsignDatabase(context, databaseName, null, version); + } + return instance; + } + + + public CallsignDatabase(@Nullable Context context, @Nullable String name + , @Nullable SQLiteDatabase.CursorFactory factory, int version) { + super(context, name, factory, version); + this.context = context; + + //链接数据库,如果实体库不存在,就会调用onCreate方法,在onCreate方法中初始化数据库 + db = this.getWritableDatabase(); + } + + public SQLiteDatabase getDb() { + return db; + } + + /** + * 当实体数据库不存在时,会调用该方法。可在这个地方创建数据,并添加文件 + * + * @param sqLiteDatabase 需要连接的数据库 + */ + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + Log.d(TAG, "Create database."); + db = sqLiteDatabase;//把数据库链接保存下来 + createTables();//创建数据表 + new InitDatabase(context, db).execute();//导入数据 + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + + } + + + private void createTables() { + try { + db.execSQL("CREATE TABLE countries (\n" + + "id INTEGER NOT NULL PRIMARY KEY,\n" + + "CountryNameEn TEXT,\n" + + "CountryNameCN TEXT,\n" + + "CQZone INTEGER,\n" + + "ITUZone INTEGER,\n" + + "Continent TEXT,\n" + + "Latitude REAL,\n" + + "Longitude REAL,\n" + + "GMT_offset REAL,\n" + + "DXCC TEXT)"); + db.execSQL("CREATE INDEX countries_id_IDX ON countries (id)"); + db.execSQL("CREATE TABLE callsigns (countryId INTEGER NOT NULL,callsign TEXT)"); + db.execSQL("CREATE INDEX callsigns_callsign_IDX ON callsigns (callsign)"); + + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + + } + } + + //查呼号的归属地 + public void getCallsignInformation(String callsign, OnAfterQueryCallsignLocation afterQueryCallsignLocation) { + new QueryCallsignInformation(db, callsign, afterQueryCallsignLocation).execute(); + } + + public CallsignInfo getCallInfo(String callsign) { + return getCallsignInfo(db, callsign); + } + + /** + * 更新消息中的位置及经纬度信息 + * + * @param ft8Messages 消息列表 + */ + public static synchronized void getMessagesLocation(SQLiteDatabase db, ArrayList ft8Messages ) { + if (ft8Messages==null) return; + ArrayList messages = new ArrayList<>(ft8Messages);//防止线程访问冲突 + + for (Ft8Message msg : messages) { + if (msg.i3==0&&msg.n3==0) continue;//如果是自由文本,就不查了 + CallsignInfo fromCallsignInfo = getCallsignInfo(db, + msg.callsignFrom.replace("<","").replace(">","")); + if (fromCallsignInfo != null) { + msg.fromDxcc = !GeneralVariables.getDxccByPrefix(fromCallsignInfo.DXCC); + msg.fromItu = !GeneralVariables.getItuZoneById(fromCallsignInfo.ITUZone); + msg.fromCq = !GeneralVariables.getCqZoneById(fromCallsignInfo.CQZone); + if (GeneralVariables.isChina) { + msg.fromWhere = fromCallsignInfo.CountryNameCN; + } else { + msg.fromWhere = fromCallsignInfo.CountryNameEn; + } + msg.fromLatLng = new LatLng(fromCallsignInfo.Latitude, fromCallsignInfo.Longitude * -1); + } + + if (msg.checkIsCQ() || msg.getCallsignTo().contains("...")) {//CQ就不查了 + continue; + } + + CallsignInfo toCallsignInfo = getCallsignInfo(db, + msg.callsignTo.replace("<","").replace(">","")); + if (toCallsignInfo != null) { + msg.toDxcc = !GeneralVariables.getDxccByPrefix(toCallsignInfo.DXCC); + msg.toItu = !GeneralVariables.getItuZoneById(toCallsignInfo.ITUZone); + msg.toCq = !GeneralVariables.getCqZoneById(toCallsignInfo.CQZone); + + if (GeneralVariables.isChina) { + msg.toWhere = toCallsignInfo.CountryNameCN; + } else { + msg.toWhere = toCallsignInfo.CountryNameEn; + } + msg.toLatLng = new LatLng(toCallsignInfo.Latitude, toCallsignInfo.Longitude*-1); + } + } + } + + @SuppressLint("Range") + private static CallsignInfo getCallsignInfo(SQLiteDatabase db, String callsign) { + CallsignInfo callsignInfo = null; + + String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" + + "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" + + "order by LENGTH(callsign) desc\n" + + "LIMIT 1"; + + Cursor cursor = db.rawQuery(querySQL, new String[]{callsign.toUpperCase(), callsign.toUpperCase()}); + if (cursor.moveToFirst()) { + callsignInfo = new CallsignInfo(callsign.toUpperCase() + , cursor.getString(cursor.getColumnIndex("CountryNameEn")) + , cursor.getString(cursor.getColumnIndex("CountryNameCN")) + , cursor.getInt(cursor.getColumnIndex("CQZone")) + , cursor.getInt(cursor.getColumnIndex("ITUZone")) + , cursor.getString(cursor.getColumnIndex("Continent")) + , cursor.getFloat(cursor.getColumnIndex("Latitude")) + , cursor.getFloat(cursor.getColumnIndex("Longitude")) + , cursor.getFloat(cursor.getColumnIndex("GMT_offset")) + , cursor.getString(cursor.getColumnIndex("DXCC"))); + } + cursor.close(); + return callsignInfo; + } + + + static class QueryCallsignInformation extends AsyncTask { + private final SQLiteDatabase db; + private final String sqlParameter; + private final OnAfterQueryCallsignLocation afterQueryCallsignLocation; + + public QueryCallsignInformation(SQLiteDatabase db, String sqlParameter, OnAfterQueryCallsignLocation afterQueryCallsignLocation) { + this.db = db; + this.sqlParameter = sqlParameter; + this.afterQueryCallsignLocation = afterQueryCallsignLocation; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { +// String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" + +// "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" + +// "order by LENGTH(callsign) desc\n" + +// "LIMIT 1"; +// +// Cursor cursor = db.rawQuery(querySQL, new String[]{sqlParameter.toUpperCase(), sqlParameter.toUpperCase()}); +// if (cursor.moveToFirst()) { +// CallsignInfo callsignInfo = new CallsignInfo(); +// callsignInfo.CallSign = sqlParameter.toUpperCase(); +// callsignInfo.CountryNameEn = cursor.getString(cursor.getColumnIndex("CountryNameEn")); +// callsignInfo.CountryNameCN = cursor.getString(cursor.getColumnIndex("CountryNameCN")); +// callsignInfo.CQZone = cursor.getInt(cursor.getColumnIndex("CQZone")); +// callsignInfo.ITUZone = cursor.getInt(cursor.getColumnIndex("ITUZone")); +// callsignInfo.Continent = cursor.getString(cursor.getColumnIndex("Continent")); +// callsignInfo.Latitude = cursor.getFloat(cursor.getColumnIndex("Latitude")); +// callsignInfo.Longitude = cursor.getFloat(cursor.getColumnIndex("Longitude")); +// callsignInfo.GMT_offset = cursor.getFloat(cursor.getColumnIndex("GMT_offset")); +// callsignInfo.DXCC = cursor.getString(cursor.getColumnIndex("DXCC")); +// if (afterQueryCallsignLocation!=null){ +// afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo); +// } +// } +// cursor.close(); + CallsignInfo callsignInfo = getCallsignInfo(db, sqlParameter); + if (callsignInfo != null && afterQueryCallsignLocation != null) { + afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo); + } + return null; + } + } + + + static class InitDatabase extends AsyncTask { + @SuppressLint("StaticFieldLeak") + private final Context context; + private final SQLiteDatabase db; + + public InitDatabase(Context context, SQLiteDatabase db) { + this.context = context; + this.db = db; + } + + @Override + protected Void doInBackground(Void... voids) { + Log.d(TAG, "开始导入呼号位置数据..."); + String insertCountriesSQL = "INSERT INTO countries (id,CountryNameEn,CountryNameCN,CQZone" + + ",ITUZone,Continent,Latitude,Longitude,GMT_offset,DXCC)\n" + + "VALUES(?,?,?,?,?,?,?,?,?,?)"; + + ArrayList callsignInfos = + CallsignFileOperation.getCallSingInfoFromFile(context); + ContentValues values = new ContentValues(); + for (int i = 0; i < callsignInfos.size(); i++) { + try { + //把国家和地区数据写进表中,id用于关联呼号 + db.execSQL(insertCountriesSQL, new Object[]{ + i,//id号 + callsignInfos.get(i).CountryNameEn, + callsignInfos.get(i).CountryNameCN, + callsignInfos.get(i).CQZone, + callsignInfos.get(i).ITUZone, + callsignInfos.get(i).Continent, + callsignInfos.get(i).Latitude, + callsignInfos.get(i).Longitude, + callsignInfos.get(i).GMT_offset, + callsignInfos.get(i).DXCC}); + Set calls = CallsignFileOperation.getCallsigns(callsignInfos.get(i).CallSign); + + for (String s : calls + ) { + values.put("countryId", i); + values.put("callsign", s); + db.insert("callsigns", null, values); + values.clear(); + } + + } catch (Exception e) { + Log.e(TAG, "错误:" + e.getMessage()); + } + } + Log.d(TAG, "呼号位置数据导入完毕!"); + return null; + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java new file mode 100644 index 0000000..461f219 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java @@ -0,0 +1,140 @@ +package com.bg7yoz.ft8cn.callsign; +/** + * 预处理呼号数据库的文件操作,呼号的来源是CTY.DAT + * @author BG7YOZ + * @date 2023-03-20 + */ + +import android.content.Context; +import android.content.res.AssetManager; + +import com.bg7yoz.ft8cn.GeneralVariables; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class CallsignFileOperation { + public static String TAG="CallsignFileOperation"; + public static String[][] countries; + + /** + * 从assets目录中的cty.dat中读出呼号分配国家和地区的列表。呼号字符串中包括多个字符串,以逗号分割, + * @param context 用于调用getAssets()方法。 + * @return ArrayList 返回CallsignInfo数组列表 + */ + public static ArrayList getCallSingInfoFromFile(Context context){ + ArrayList callsignInfos=new ArrayList<>(); + + //读出国家和地区的中英文对应翻译表。保存到countries二维数组中。 + countries=getCountryNameToCN(context); + + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream= assetManager.open("cty.dat"); + String[] st=getLinesFromInputStream(inputStream,";"); + for (int i = 0; i getCallsigns(String s){ + String[] ls=s.replace("\n","").split(","); + Set callsigns=new HashSet<>(); + for (int i = 0; i < ls.length ; i++) { + if (ls[i].contains(")")) { + //Log.d(TAG,ls[i]); + ls[i] = ls[i].substring(0, ls[i].indexOf("(")); + //Log.d(TAG,ls[i]+" ((("); + } + if (ls[i].contains("[")) { + //Log.d(TAG,ls[i]); + ls[i] = ls[i].substring(0, ls[i].indexOf("[")); + //Log.d(TAG,ls[i]+" 【【【"); + } + callsigns.add(ls[i].trim()); + } + + return callsigns; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java new file mode 100644 index 0000000..bd5dce8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java @@ -0,0 +1,79 @@ +package com.bg7yoz.ft8cn.callsign; +/** + * 呼号信息类,用于归属地查询 + * + * @author BG7YOZ + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +public class CallsignInfo { + public static String TAG="CallsignInfo"; + public String CallSign;//呼号 + public String CountryNameEn;//国家 + public String CountryNameCN;//国家中文名 + public int CQZone;//CQ分区 + public int ITUZone;//ITU分区 + public String Continent;//大陆缩写 + public float Latitude;//以度为单位的纬度,+ 表示北 + public float Longitude;//以度为单位的经度,+ 表示西 + public float GMT_offset;//与 GMT 的本地时间偏移 + public String DXCC;//DXCC前缀 + + @SuppressLint("DefaultLocale") + @NonNull + @Override + public String toString() { + String country; + if (GeneralVariables.isChina) { + country=CountryNameCN; + }else { + country=CountryNameEn; + } + //return String.format("呼号:%s\n位置:%s\nCQ分区:%d\nITU分区:%d\n大陆:%s\n经纬度:%.2f,%.2f\n时区:%.0f\nDXCC前缀:%s" + return String.format(GeneralVariables.getStringFromResource(R.string.callsign_info) + , CallSign, country, CQZone, ITUZone, Continent, Longitude, Latitude, GMT_offset, DXCC); + } + + + public CallsignInfo(String callSign, String countryNameEn, + String countryNameCN, int CQZone, int ITUZone, + String continent, float latitude, float longitude, + float GMT_offset, String DXCC) { + CallSign = callSign; + CountryNameEn = countryNameEn; + CountryNameCN = countryNameCN; + this.CQZone = CQZone; + this.ITUZone = ITUZone; + Continent = continent; + Latitude = latitude; + Longitude = longitude; + this.GMT_offset = GMT_offset; + this.DXCC = DXCC; + } + + public CallsignInfo(String s) { + String[] info = s.split(":"); + if (info.length<9){ + Log.e(TAG,"呼号数据格式错误!"+s); + return; + } + CountryNameEn = info[0].replace("\n", "").trim(); + CQZone = Integer.parseInt(info[1].replace("\n", "").replace(" ", "")); + ITUZone = Integer.parseInt(info[2].replace("\n", "").replace(" ", "")); + Continent = info[3].replace("\n", "").replace(" ", ""); + Latitude = Float.parseFloat(info[4].replace("\n", "").replace(" ", "")); + Longitude = Float.parseFloat(info[5].replace("\n", "").replace(" ", "")); + GMT_offset = Float.parseFloat(info[6].replace("\n", "").replace(" ", "")); + DXCC = info[7].replace("\n", "").replace(" ", ""); + CallSign= info[8].replace("\n", "").replace(" ", ""); + } +} + diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java new file mode 100644 index 0000000..1693ff4 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java @@ -0,0 +1,12 @@ +package com.bg7yoz.ft8cn.callsign; + +/** + * 用于查询呼号归属地的回调接口,因为数据库操作采用异步方式 + * + * @author BG7YOZ + * @date 2023-03-20 + * + */ +public interface OnAfterQueryCallsignLocation { + void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java new file mode 100644 index 0000000..84a0422 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java @@ -0,0 +1,103 @@ +package com.bg7yoz.ft8cn.connector; +/** + * 用于连接电台的基础类,蓝牙、USB线、FLEX网络、ICOM网络都是继承于此 + * + * @author BG7YOZ + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.rigs.OnConnectReceiveData; +import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; + + +public class BaseRigConnector { + private boolean connected;//是否处于连接状态 + private OnConnectReceiveData onConnectReceiveData;//当接收到数据后的动作 + private int controlMode;//控制模式 + private OnRigStateChanged onRigStateChanged; + private OnConnectorStateChanged onConnectorStateChanged=new OnConnectorStateChanged() { + @Override + public void onDisconnected() { + if (onRigStateChanged!=null){ + onRigStateChanged.onDisconnected(); + } + connected=false; + } + + @Override + public void onConnected() { + if (onRigStateChanged!=null){ + onRigStateChanged.onConnected(); + } + connected=true; + } + + @Override + public void onRunError(String message) { + if (onRigStateChanged!=null){ + onRigStateChanged.onRunError(message); + } + connected=false; + } + }; + public BaseRigConnector(int controlMode) { + this.controlMode=controlMode; + } + + /** + * 发送数据 + * @param data 数据 + */ + public synchronized void sendData(byte[] data){}; + + /** + * 设置PTT状态,ON OFF,如果是RTS和DTR,这个是在有线方式才有的,在CableConnector中会重载此方法 + * @param on 是否ON + */ + public void setPttOn(boolean on){}; + + /** + * 使用发送数据的方式设置PTT状态 + * @param command 指令数据 + */ + public void setPttOn(byte[] command){}; + + public void setControlMode(int mode){ + controlMode=mode; + } + + public int getControlMode() { + return controlMode; + } + + public void setOnConnectReceiveData(OnConnectReceiveData receiveData){ + onConnectReceiveData=receiveData; + } + + public void sendWaveData(float[] data){ + //留给网络方式发送音频流 + } + + public OnConnectReceiveData getOnConnectReceiveData() { + return onConnectReceiveData; + } + public void connect(){ + } + public void disconnect(){ + } + + public OnRigStateChanged getOnRigStateChanged() { + return onRigStateChanged; + } + + public void setOnRigStateChanged(OnRigStateChanged onRigStateChanged) { + this.onRigStateChanged = onRigStateChanged; + } + + public OnConnectorStateChanged getOnConnectorStateChanged() { + return onConnectorStateChanged; + } + public boolean isConnected(){ + return connected; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java new file mode 100644 index 0000000..ab3a907 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java @@ -0,0 +1,184 @@ +package com.bg7yoz.ft8cn.connector; +/** + * 用于蓝牙连接的Connector,继承于BaseRigConnector + * + * @author BG7YOZ + * @date 2023-03-20 + */ + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialListener; +import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialService; +import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialSocket; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.io.IOException; + +public class BluetoothRigConnector extends BaseRigConnector implements ServiceConnection, BluetoothSerialListener { + private enum Connected {False, Pending, True} + private static BluetoothRigConnector connector=null; + + public static BluetoothRigConnector getInstance(Context context, String address, int controlMode){ + if (connector!=null){ + if (!connector.getDeviceAddress().equals(address)) { + if (connector.connected== Connected.True) { + connector.socketDisconnect(); + } + connector.setDeviceAddress(address); + connector.socketConnect(); + } + + return connector; + }else { + return new BluetoothRigConnector(context,address,controlMode); + } + } + + private static final String TAG = "BluetoothRigConnector"; + //private static ServiceConnection connection; + private boolean initialStart = true; + private BluetoothSerialService service = null; + private Connected connected = Connected.False; + private String deviceAddress; + private Context context; + + + public BluetoothRigConnector(Context context, String address, int controlMode) { + super(controlMode); + connector=this; + deviceAddress = address; + this.context = context; + + context.stopService(new Intent(context, BluetoothSerialService.class)); + context.bindService(new Intent(context, BluetoothSerialService.class), this, Context.BIND_AUTO_CREATE); + + } + + public String getDeviceAddress() { + return deviceAddress; + } + + public void setDeviceAddress(String deviceAddress) { + this.deviceAddress = deviceAddress; + } + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + service = ((BluetoothSerialService.SerialBinder) iBinder).getService(); + service.attach(this); + if (initialStart) { + initialStart = false; + //getActivity().runOnUiThread(this::connect); + socketConnect(); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + socketDisconnect(); + service = null; + } + + @Override + public void onSerialConnect() { + Log.d(TAG, "onSerialConnect: connected"); + connected = Connected.True; + getOnConnectorStateChanged().onConnected(); + } + + @Override + public void onSerialConnectError(Exception e) { + Log.e(TAG, "onSerialConnectError: " + e.getMessage()); + getOnConnectorStateChanged().onRunError(e.getMessage()); + socketDisconnect(); + } + + + @Override + public void onSerialRead(byte[] data) { + if (data.length > 0) { + //Log.d(TAG, "onSerialRead: " + BaseRig.byteToStr(data)); + if (getOnConnectReceiveData()!=null){ + getOnConnectReceiveData().onData(data); + } + } + } + + @Override + public void onSerialIoError(Exception e) { + Log.e(TAG, "onSerialIoError: " + e.getMessage()); + getOnConnectorStateChanged().onRunError(e.getMessage()); + socketDisconnect(); + } + + public void socketDisconnect() { + connected = Connected.False; + getOnConnectorStateChanged().onDisconnected(); + service.disconnect(); + } + + /* + * Serial + UI + */ + public void socketConnect() { + try { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.connect_bluetooth_spp) + ,deviceAddress)); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress); + Log.d(TAG, "connecting..."); + connected = Connected.Pending; + BluetoothSerialSocket socket = new BluetoothSerialSocket(context, device); + service.connect(socket); + } catch (Exception e) { + onSerialConnectError(e); + } + } + + public void sendCommand(byte[] data) { + //Log.d(TAG, "sendCommand: "+BaseRig.byteToStr(data) ); + if (connected != Connected.True) { + Log.e(TAG, "sendCommand: 蓝牙没连接"); + socketConnect(); + return; + } + + try { + service.write(data); + } catch (IOException e) { + getOnConnectorStateChanged().onRunError(e.getMessage()); + } + } + + @Override + public synchronized void sendData(byte[] data) { + sendCommand(data); + } + + @Override + public void setPttOn(byte[] command) { + sendData(command);//以CAT指令发送PTT + } + + @Override + public void connect() { + super.connect(); + socketConnect(); + } + @Override + public void disconnect() { + super.disconnect(); + socketDisconnect(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java new file mode 100644 index 0000000..6faf22e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java @@ -0,0 +1,75 @@ +package com.bg7yoz.ft8cn.connector; + +import android.content.Context; +import android.util.Log; + +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager; + +/** + * 有线连接方式的Connector,这里是指USB方式的,继承于BaseRigConnector + * + * @author BG7YOZ + * @date 2023-03-20 + */ +public class CableConnector extends BaseRigConnector { + private static final String TAG="CableConnector"; + + private final CableSerialPort cableSerialPort; + + + public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate + , int controlMode) { + super(controlMode); + cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged()); + cableSerialPort.ioListener=new SerialInputOutputManager.Listener() { + @Override + public void onNewData(byte[] data) { + if (getOnConnectReceiveData()!=null){ + getOnConnectReceiveData().onData(data); + } + } + + @Override + public void onRunError(Exception e) { + Log.e(TAG, "CableConnector error: "+e.getMessage() ); + getOnConnectorStateChanged().onRunError("与串口失去连接:"+e.getMessage()); + } + } ; + //connect(); + } + + @Override + public synchronized void sendData(byte[] data) { + cableSerialPort.sendData(data); + } + + + @Override + public void setPttOn(boolean on) { + //只处理RTS和DTR + switch (getControlMode()){ + case ControlMode.DTR: cableSerialPort.setDTR_On(on);//打开和关闭DTR + break; + case ControlMode.RTS:cableSerialPort.setRTS_On(on);//打开和关闭RTS + break; + } + } + + @Override + public void setPttOn(byte[] command) { + cableSerialPort.sendData(command);//以CAT指令发送PTT + } + + @Override + public void connect() { + super.connect(); + cableSerialPort.connect(); + } + + @Override + public void disconnect() { + super.disconnect(); + cableSerialPort.disconnect(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java new file mode 100644 index 0000000..85ddfd0 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java @@ -0,0 +1,374 @@ +package com.bg7yoz.ft8cn.connector; +/** + * 用于USB串口操作的类。USB串口驱动在serialport目录中,主要是CDC、CH34x、CP21xx、FTDI等。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.Build; +import android.util.Log; + +import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.serialport.CdcAcmSerialDriver; +import com.bg7yoz.ft8cn.serialport.UsbSerialDriver; +import com.bg7yoz.ft8cn.serialport.UsbSerialPort; +import com.bg7yoz.ft8cn.serialport.UsbSerialProber; +import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; + + +public class CableSerialPort { + private static final String TAG = "CableSerialPort"; + private OnConnectorStateChanged onStateChanged; + public static final int SEND_TIMEOUT = 2000; + + private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID; + + public enum UsbPermission {Unknown, Requested, Granted, Denied} + + private UsbPermission usbPermission = UsbPermission.Unknown; + + private BroadcastReceiver broadcastReceiver; + private final Context context; + + private int vendorId = 0x0c26;//设备号 + private int portNum = 0;//端口号 + private int baudRate = 19200;//波特率 + + private UsbSerialPort usbSerialPort; + private SerialInputOutputManager usbIoManager; + public SerialInputOutputManager.Listener ioListener = null; + + private UsbManager usbManager; + private UsbDeviceConnection usbConnection; + private UsbSerialDriver driver; + + + private boolean connected = false;//是否处于连接状态 + + public CableSerialPort(Context mContext, SerialPort serialPort, int baud, OnConnectorStateChanged connectorStateChanged) { + vendorId = serialPort.vendorId; + portNum = serialPort.portNum; + baudRate = baud; + context = mContext; + this.onStateChanged=connectorStateChanged; + doBroadcast(); + } + + public CableSerialPort(Context mContext) { + context = mContext; + doBroadcast(); + } + + private void doBroadcast() { + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (INTENT_ACTION_GRANT_USB.equals(intent.getAction())) { + usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) + ? UsbPermission.Granted : UsbPermission.Denied; + connect(); + } + } + }; + } + + private boolean prepare() { + registerRigSerialPort(context); + UsbDevice device = null; + usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + + //此处把connection设成Null,这样在后面来通过是否null判断是否有权限。 + usbConnection = null; + //此处是不是做个权限判断? + if (usbManager == null) { + return false; + } + + + for (UsbDevice v : usbManager.getDeviceList().values()) { + if (v.getVendorId() == vendorId) { + device = v; + } + } + if (device == null) { + Log.e(TAG, String.format("串口设备打开失败: 没有找到设备0x%04x", vendorId)); + return false; + } + driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if (driver == null) { + //试着把未知的设备加入到cdc驱动上 + driver = new CdcAcmSerialDriver(device); + } + if (driver.getPorts().size() < portNum) { + Log.e(TAG, "串口号不存在,无法打开。"); + return false; + } + Log.e(TAG, "connect: port size:" + String.valueOf(driver.getPorts().size())); + usbSerialPort = driver.getPorts().get(portNum); + usbConnection = usbManager.openDevice(driver.getDevice()); + + return true; + + } + + //@RequiresApi(api = Build.VERSION_CODES.S) + public boolean connect() { + connected = false; + if (!prepare()) { + //return false; + } + if (driver == null) { + if (onStateChanged!=null){ + onStateChanged.onRunError("无法连接串口,没有驱动或串口不存在!"); + } + return false; + } + if (usbConnection == null && usbPermission == UsbPermission.Unknown + && !usbManager.hasPermission(driver.getDevice())) { + usbPermission = UsbPermission.Requested; + + PendingIntent usbPermissionIntent; + + //在android12 开始,增加了PendingIntent.FLAG_MUTABLE保护机制,所以要做版本判断 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + usbPermissionIntent = PendingIntent.getBroadcast(context, 0 + , new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE); + } else { + usbPermissionIntent = PendingIntent.getBroadcast(context, 0 + , new Intent(INTENT_ACTION_GRANT_USB), 0); + } + + + //PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0 + // , new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE); + // , new Intent(INTENT_ACTION_GRANT_USB), 0); + + + usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); + prepare(); + } + if (usbConnection == null) { + if (onStateChanged!=null){ + onStateChanged.onRunError("无法连接串口,可能没有访问USB设备的权限!"); + } + + return false; + } + try { + usbSerialPort.open(usbConnection); + //波特率、停止位 + usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + usbIoManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() { + @Override + public void onNewData(byte[] data) { + if (ioListener != null) { + ioListener.onNewData(data); + } + } + + @Override + public void onRunError(Exception e) { + if (ioListener != null) { + ioListener.onRunError(e); + } + disconnect(); + } + }); + usbIoManager.start(); + Log.d(TAG, "串口打开成功!"); + connected = true; + + if (onStateChanged!=null){ + onStateChanged.onConnected(); + } + + + } catch (Exception e) { + Log.e(TAG, "串口打开失败: " + e.getMessage()); + if (onStateChanged!=null){ + onStateChanged.onRunError("串口打开失败: " + e.getMessage()); + } + disconnect(); + return false; + } + return true; + } + + public boolean sendData(final byte[] src) { + if (usbSerialPort != null) { + try { + usbSerialPort.write(src, SEND_TIMEOUT); + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "发送数据出错:" + e.getMessage()); + return false; + } + return true; + } else { + Log.e(TAG, "无法发送数据,串口没有打开。"); + return false; + } + + } + + public void disconnect() { + connected = false; + if (onStateChanged!=null){ + onStateChanged.onDisconnected(); + } + if (usbIoManager != null) { + usbIoManager.setListener(null); + usbIoManager.stop(); + } + usbIoManager = null; + try { + if (usbSerialPort != null) { + usbSerialPort.close(); + } + } catch (IOException ignored) { + } + usbSerialPort = null; + } + + public void registerRigSerialPort(Context context) { + Log.d(TAG, "registerRigSerialPort: registered!"); + context.registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); + } + + public void unregisterRigSerialPort(Activity activity) { + Log.d(TAG, "unregisterRigSerialPort: unregistered!"); + activity.unregisterReceiver(broadcastReceiver); + } + + + /** + * 打开和关闭RTS + * + * @param rts_on true:打开,false:关闭 + */ + public void setRTS_On(boolean rts_on) { + try { + EnumSet controlLines = usbSerialPort.getSupportedControlLines(); + if (controlLines.contains(UsbSerialPort.ControlLine.RTS)) { + usbSerialPort.setRTS(rts_on); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void setDTR_On(boolean dtr_on) { + try { + EnumSet controlLines = usbSerialPort.getSupportedControlLines(); + if (controlLines.contains(UsbSerialPort.ControlLine.DTR)) { + usbSerialPort.setDTR(dtr_on); + } + } catch (IOException e) { + e.printStackTrace(); + Log.d(TAG, "setDTR_On: " + e.getMessage()); + } + } + + public OnConnectorStateChanged getOnStateChanged() { + return onStateChanged; + } + + public void setOnStateChanged(OnConnectorStateChanged onStateChanged) { + this.onStateChanged = onStateChanged; + } + + public int getVendorId() { + return vendorId; + } + + public void setVendorId(int deviceId) { + this.vendorId = deviceId; + } + + public int getPortNum() { + return portNum; + } + + public void setPortNum(int portNum) { + this.portNum = portNum; + } + + public int getBaudRate() { + return baudRate; + } + + public void setBaudRate(int baudRate) { + this.baudRate = baudRate; + } + + /** + * 获取本机可用的串口设备串列表 + * + * @param context context + * @return 串口设备列表 + */ + public static ArrayList listSerialPorts(Context context) { + ArrayList serialPorts = new ArrayList<>(); + UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + + for (UsbDevice device : usbManager.getDeviceList().values()) { + UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if (driver == null) { + continue; + //试着把未知的设备加入到cdc驱动上 + //driver = new CdcAcmSerialDriver(device); + } + for (int i = 0; i < driver.getPorts().size(); i++) { + serialPorts.add(new SerialPort(device.getDeviceId(), device.getVendorId() + , device.getProductId(), i)); + } + } + return serialPorts; + } + + public boolean isConnected() { + return connected; + } + + + public static class SerialPort { + public int deviceId = 0; + public int vendorId = 0x0c26;//厂商号 + public int productId = 0;//设备号 + public int portNum = 0;//端口号 + + public SerialPort(int deviceId, int vendorId, int productId, int portNum) { + this.deviceId = deviceId; + this.vendorId = vendorId; + this.productId = productId; + this.portNum = portNum; + } + + @SuppressLint("DefaultLocale") + @Override + public String toString() { + return String.format("SerialPort:deviceId=0x%04X, vendorId=0x%04X, portNum=%d" + , deviceId, vendorId, portNum); + } + + @SuppressLint("DefaultLocale") + public String information() { + return String.format("\\0x%04X\\0x%04X\\0x%04X\\0x%d", deviceId, vendorId, productId, portNum); + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java new file mode 100644 index 0000000..4a43f33 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java @@ -0,0 +1,24 @@ +package com.bg7yoz.ft8cn.connector; + +/** + * 连接的模式 + * @author BGY70Z + * @date 2023-03-20 + */ +public class ConnectMode { + public static final int USB_CABLE=0; + public static final int BLUE_TOOTH=1; + public static final int NETWORK=2; + public static String getModeStr(int mode){ + switch (mode){ + case USB_CABLE: + return "USB Cable"; + case BLUE_TOOTH: + return "Bluetooth"; + case NETWORK: + return "Network"; + default: + return "-"; + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java new file mode 100644 index 0000000..d51dabe --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java @@ -0,0 +1,325 @@ +package com.bg7yoz.ft8cn.connector; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.FlexCommand; +import com.bg7yoz.ft8cn.flex.FlexMeterInfos; +import com.bg7yoz.ft8cn.flex.FlexMeterList; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.RadioTcpClient; +import com.bg7yoz.ft8cn.flex.VITA; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * 有线连接方式的Connector,这里是指USB方式的 + * @author BGY70Z + * @date 2023-03-20 + */ +public class FlexConnector extends BaseRigConnector { + public MutableLiveData mutableMeterList=new MutableLiveData<>(); + public FlexMeterInfos flexMeterInfos =new FlexMeterInfos(""); + public FlexMeterList meterList=new FlexMeterList(); + public interface OnWaveDataReceived{ + void OnDataReceived(int bufferLen,float[] buffer); + } + public int maxRfPower; + public int maxTunePower; + + private static final String TAG = "CableConnector"; + + private FlexRadio flexRadio; + + private OnWaveDataReceived onWaveDataReceived; + + + public FlexConnector(Context context, FlexRadio flexRadio, int controlMode) { + super(controlMode); + this.flexRadio = flexRadio; + maxTunePower=GeneralVariables.flexMaxTunePower; + maxRfPower=GeneralVariables.flexMaxRfPower; + setFlexRadioInterface(); + //connect(); + } + + public static int[] byteDataTo16BitData(byte[] buffer){ + int[] data=new int[buffer.length /2]; + for (int i = 0; i < buffer.length/2; i++) { + int res = (buffer[i*2+1] & 0x000000FF) | (((int) buffer[i*2]) << 8); + data[i]=res; + } + return data; + } + + + private void setFlexRadioInterface() { + flexRadio.setOnReceiveStreamData(new FlexRadio.OnReceiveStreamData() { + @Override + public void onReceiveAudio(byte[] data) { + if (onWaveDataReceived!=null){ + float[] buffer=getMonoFloatFromBytes(data);//把24000转成12000,立体声转成单声道 + onWaveDataReceived.OnDataReceived(buffer.length,buffer); + } + } + + @Override + public void onReceiveIQ(byte[] data) { + + } + + @Override + public void onReceiveFFT(VITA vita) { + //if (vita.streamId==0x40000000) { + // mutableVita.postValue(vita.showHeadStr() + "\n" + vita.showPayloadHex()); + //} + } + + @Override + public void onReceiveMeter(VITA vita) { + //Log.e(TAG, "onReceiveMeter: "+vita.showPayloadHex() ); + meterList.setMeters(vita.payload,flexMeterInfos); + + mutableMeterList.postValue(meterList); + + } + + @Override + public void onReceiveUnKnow(byte[] data) { + + } + }); + + + //当有命令返回值时的事件 + flexRadio.setOnCommandListener(new FlexRadio.OnCommandListener() { + @Override + public void onResponse(FlexRadio.FlexResponse response) { + if (response.resultValue!=0) {//只显示失败的命令 + //ToastMessage.show(response.resultStatus()); + //Log.e(TAG, "onResponse: "+response.resultStatus()); + } + + Log.e(TAG, "onResponse: command:"+response.flexCommand.toString()); + //Log.e(TAG, "onResponse: "+response.resultStatus()); + Log.e(TAG, "onResponse: "+response.rawData ); + + if (response.flexCommand== FlexCommand.METER_LIST){ + //FlexMeters flexMeters=new FlexMeters(response.exContent); + flexMeterInfos.setMeterInfos(response.exContent); + flexRadio.commandSubMeterAll();//显示全部仪表消息 + //flexMeters.getAllMeters(); + //Log.e(TAG, "onResponse: ----->>>"+flexMeters.getAllMeters() ); + } + if (response.flexCommand==FlexCommand.STREAM_CREATE_DAX_TX){ + flexRadio.streamTxId=response.daxTxStreamId; + } + +// if (response.flexCommand== FlexCommand.METER_LIST){ +// Log.e(TAG, "onResponse: ."+response.rawData.replace("#","\n") ); +// } + } + }); + + //当有状态信息接收到时 + flexRadio.setOnStatusListener(new FlexRadio.OnStatusListener() { + @Override + public void onStatus(FlexRadio.FlexResponse response) { + //显示状态消息 + //ToastMessage.show(response.content); + Log.e(TAG, "onStatus: "+response.rawData ); + } + }); + + + + flexRadio.setOnTcpConnectStatus(new FlexRadio.OnTcpConnectStatus() { + @SuppressLint("DefaultLocale") + @Override + public void onConnectSuccess(RadioTcpClient tcpClient) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.init_flex_operation) + ,flexRadio.getModel())); + + flexRadio.commandClientDisconnect();//断开之前的全部连接 + flexRadio.commandClientGui();//创建GUI + + flexRadio.commandSubDaxAll();//注册全部DAX流 + + + + flexRadio.commandClientSetEnforceNetWorkGui();//对网络MTU做设置 + + //flexRadio.commandSliceList();//列slice + flexRadio.commandSliceCreate();//创建slice + + + + + flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX + //todo 防止流的端口没有释放,把端口变换一下? + //FlexRadio.streamPort++; + + flexRadio.commandUdpPort();//设置UDP端口 + + + flexRadio.commandStreamCreateDaxRx(1);//创建流数据到DAX通道1 + flexRadio.commandStreamCreateDaxTx(1);//创建流数据到DAX通道1 + //TODO 是否设置??? dax tx T 或者 dax tx 1 + flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f)); + flexRadio.commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式 + flexRadio.commandSetFilter(0, 0, 3000);//设置滤波为3000HZ + + + flexRadio.commandMeterList();//列一下仪表 + //flexRadio.commandSubMeterAll();//此处订阅指令放到了接收响应部分 + + setMaxRfPower(maxRfPower);//设置发射功率 + setMaxTunePower(maxTunePower);//设置调谐功率 + + //flexRadio.commandSubMeterById(5);//列指定的仪表 + + //flexRadio.commandSliceSetNR(0, true); + //flexRadio.commandSliceSetNB(0, true); + + //flexRadio.commandDisplayPan(10, 10); + //flexRadio.commandSetFilter(0,0,3000); + // flexRadio.sendCommand(FlexCommand.FILT_SET, "filt 0 0 3000"); + //flexRadio.commandMeterCreateAmp(); + //flexRadio.commandMeterList(); + + + //flexRadio.sendCommand(FlexCommand.INFO, "info"); + //flexRadio.commandGetInfo(); + //flexRadio.commandSliceGetError(0); + + //flexRadio.sendCommand(FlexCommand.SLICE_GET_ERROR, "slice get_error 0"); + //flexRadio.sendCommand(FlexCommand.REMOTE_RADIO_RX_ON, "remote_audio rx_on on"); + // + //flexRadio.sendCommand("c1|client gui\n"); + //playData(); + + + } + + @Override + public void onConnectFail(RadioTcpClient tcpClient) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource + (R.string.flex_connect_failed),flexRadio.getModel())); + } + }); + + } + public void setMaxRfPower(int power){ + maxRfPower=power; + GeneralVariables.flexMaxRfPower=power; + flexRadio.commandSetRfPower(maxRfPower);//设置发射功率 + + } + public void setMaxTunePower(int power){ + maxTunePower=power; + GeneralVariables.flexMaxTunePower=power; + flexRadio.commandSetTunePower(maxTunePower);//设置调谐功率 + + } + public void startATU(){ + flexRadio.commandStartATU(); + } + public void tuneOnOff(boolean on){ + flexRadio.commandTuneTransmitOnOff(on); + } + public void subAllMeters(){ + if (flexMeterInfos.size()==0) { + flexRadio.commandMeterList();//列一下仪表 + flexRadio.commandSubMeterAll();//显示全部仪表消息 + } + } + + @Override + public void sendData(byte[] data) { + flexRadio.sendData(data); + } + + + @Override + public void setPttOn(boolean on) { + flexRadio.isPttOn=on; + flexRadio.commandPTTOnOff(on); + } + + @Override + public void setPttOn(byte[] command) { + //cableSerialPort.sendData(command);//以CAT指令发送PTT + } + + @Override + public void sendWaveData(float[] data) { + //Log.e(TAG, "sendWaveData: flexConnector:"+data.length ); + flexRadio.sendWaveData(data); + } + + @Override + public void connect() { + super.connect(); + flexRadio.openAudio(); + flexRadio.connect(); + flexRadio.openStreamPort(); + } + + @Override + public void disconnect() { + super.disconnect(); + flexRadio.closeAudio(); + flexRadio.closeStreamPort(); + flexRadio.disConnect(); + } + + public OnWaveDataReceived getOnWaveDataReceived() { + return onWaveDataReceived; + } + + public void setOnWaveDataReceived(OnWaveDataReceived onWaveDataReceived) { + this.onWaveDataReceived = onWaveDataReceived; + } + + /** + * 获取单声道的数据,24000hz采样率改为12000采样率,把立体声改为单声道 + * @param bytes 原始声音数据 + * @return 单声道数据 + */ + public static float[] getMonoFloatFromBytes(byte[] bytes) { + float[] floats = new float[bytes.length / 16]; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + for (int i = 0; i < floats.length; i++) { + try { + float f1,f2; + f1=dis.readFloat(); + dis.readFloat();//放弃一个声道 + f2=dis.readFloat(); + floats[i] = Math.max(f1,f2);//取最大值 + dis.readFloat();//放弃1个声道 + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + try { + dis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return floats; + } + + @Override + public boolean isConnected() { + return flexRadio.isConnect(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java new file mode 100644 index 0000000..18fb917 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java @@ -0,0 +1,112 @@ +package com.bg7yoz.ft8cn.connector; +/** + * ICom网络方式的连接器。 + * 注:ICom网络方式的音频数据包是Int类型,需要转换成Float类型 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.icom.IComWifiRig; + +public class IComWifiConnector extends BaseRigConnector{ + private static final String TAG = "IComWifiConnector"; + public interface OnWifiDataReceived{ + void OnWaveReceived(int bufferLen,float[] buffer); + void OnCivReceived(byte[] data); + } + + private IComWifiRig iComWifiRig; + private OnWifiDataReceived onWifiDataReceived; + + + public IComWifiConnector(int controlMode,IComWifiRig iComWifiRig) { + super(controlMode); + this.iComWifiRig=iComWifiRig; + + this.iComWifiRig.setOnIComDataEvents(new IComWifiRig.OnIComDataEvents() { + @Override + public void onReceivedCivData(byte[] data) { + if (getOnConnectReceiveData()!=null){ + getOnConnectReceiveData().onData(data); + } + if (onWifiDataReceived!=null) { + onWifiDataReceived.OnCivReceived(data); + } + } + + @Override + public void onReceivedWaveData(byte[] data) {//接收音频数据事件,把音频数据转换成float格式的。 + if (onWifiDataReceived!=null){ + float[] waveFloat=new float[data.length/2]; + for (int i = 0; i { + private final SQLiteDatabase db; + private final AfterCount afterCount; + + public DistanceCount(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL; + Cursor cursor; + double maxDistance=-1; + String maxDistanceGrid=""; + double minDistance=6553500f; + String minDistanceGrid=""; + + ArrayList values=new ArrayList<>(); + + ArrayList bands=new ArrayList<>(); + querySQL="select DISTINCT band from QSLTable q WHERE gridsquare<>\"\""; + cursor = db.rawQuery(querySQL,null); + + while (cursor.moveToNext()){ + bands.add(cursor.getString(cursor.getColumnIndex("band"))); + } + + for (String band:bands) { + + querySQL = "SELECT DISTINCT SUBSTR(gridsquare,1,4) as g FROM QSLTable q where (gridsquare <>\"\")and(band=?)"; + cursor = db.rawQuery(querySQL, new String[]{band}); + double max=-1; + String maxGrid=""; + double min=6553500f; + String minGrid=""; + + while (cursor.moveToNext()) { + String grid = cursor.getString(cursor.getColumnIndex("g")); + double distance = MaidenheadGrid.getDist(grid, GeneralVariables.getMyMaidenheadGrid()); + if (distance > maxDistance) { + maxDistance = distance; + maxDistanceGrid = grid; + } + if (distance < minDistance) { + minDistance = distance; + minDistanceGrid = grid; + } + + if (distance > max) { + max = distance; + maxGrid = grid; + } + if (distance < min) { + min = distance; + minGrid = grid; + } + + } + cursor.close(); + + if ((max>-1)&&(min<6553500f)){ + values.add(new CountValue((int) Math.round(max),String.format( + GeneralVariables.getStringFromResource(R.string.maximum_distance) + ,getQSLInfo(maxGrid)))); + values.add(new CountValue((int) Math.round(min),String.format( + GeneralVariables.getStringFromResource(R.string.minimum_distance) + ,getQSLInfo(minGrid)))); + } + } + + String info=String.format(GeneralVariables.getStringFromResource(R.string.count_distance_info) + ,maxDistance,maxDistanceGrid,minDistance,minDistanceGrid); + + if (afterCount!=null &&(maxDistance>0)&&(minDistance<6553500f)){ + afterCount.countInformation(new CountInfo(info + ,ChartType.None + ,GeneralVariables.getStringFromResource(R.string.distance_statistics) + ,values)); + } + return null; + } + + @SuppressLint("Range") + private String getQSLInfo(String grid){ + String querySQL="SELECT call,band,freq,qso_date,time_on,gridsquare FROM QSLTable q " + + "where SUBSTR(gridsquare,1,4) =? LIMIT 1"; + Cursor cursor = db.rawQuery(querySQL,new String[]{grid}); + StringBuilder result=new StringBuilder(); + int breakLine=0; + while (cursor.moveToNext()){ + String call=cursor.getString(cursor.getColumnIndex("call")); + String band=cursor.getString(cursor.getColumnIndex("band")); + String freq=cursor.getString(cursor.getColumnIndex("freq")); + //String qso_date=cursor.getString(cursor.getColumnIndex("qso_date")); + //String time_on=cursor.getString(cursor.getColumnIndex("time_on")); + String gridsquare=cursor.getString(cursor.getColumnIndex("gridsquare")); + + //获取呼号的位置 + CallsignInfo callsignInfo= GeneralVariables.callsignDatabase.getCallInfo(call); + + if (breakLine>0) result.append("\n"); + result.append(String.format("%s %s(%s) %s\n %s", call, freq, band + ,gridsquare,callsignInfo.toString())); + breakLine++; + } + return result.toString(); + } + + + } + + + /** + * 统计各波段比例 + */ + static class GetBandCount extends AsyncTask { + private final SQLiteDatabase db; + private final AfterCount afterCount; + + public GetBandCount(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint({"Range", "DefaultLocale"}) + @Override + protected Void doInBackground(Void... voids) { + int successCount=0; + ArrayList values=new ArrayList<>(); + String querySQL; + Cursor cursor; + querySQL="SELECT UPPER( band) as band ,count(*) as c FROM QSLTable q \n" + + "GROUP BY UPPER( band) ORDER BY COUNT(*) desc "; + cursor = db.rawQuery(querySQL,null); + while (cursor.moveToNext()){ + int count=cursor.getInt(cursor.getColumnIndex("c")); + successCount=successCount+count; + values.add(new CountValue(count + ,String.format("%s",cursor.getString(cursor.getColumnIndex("band"))))); + } + StringBuilder stringBuilder=new StringBuilder(); + stringBuilder.append(String.format(GeneralVariables.getStringFromResource(R.string.count_total),successCount)); + for (int i = 0; i < values.size(); i++) { + stringBuilder.append(String.format("\n%s:\t%d",values.get(i).name,values.get(i).value)); + } + cursor.close(); + if (afterCount!=null){ + afterCount.countInformation(new CountInfo( + stringBuilder.toString() + ,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.band_statistics) + ,values)); + } + + return null; + } + } + /** + * 统计通联数量 + */ + static class GetTotal extends AsyncTask{ + private final SQLiteDatabase db; + private final AfterCount afterCount; + + public GetTotal(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint({"DefaultLocale", "Range"}) + @Override + protected Void doInBackground(Void... voids) { + + int logCount=0;//日志的数量 + int callsignCount=0;//呼号的数量 + int isQslCount=0;//确认的数量 + int isLotwQslCount=0;//三方平台确认的数量 + + //通联的呼号数量 + String querySQL; + Cursor cursor; + querySQL="SELECT count(*) AS C FROM QSLTable q"; + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + logCount=cursor.getInt(cursor.getColumnIndex("C")); + cursor.close(); + + querySQL="SELECT count(DISTINCT \"call\") AS C FROM QSLTable q "; + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + callsignCount=cursor.getInt(cursor.getColumnIndex("C")); + cursor.close(); + + querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isQSL =1 or isLotW_QSL =1"; + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + isQslCount=cursor.getInt(cursor.getColumnIndex("C")); + cursor.close(); + + querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isLotW_QSL =1"; + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + isLotwQslCount=cursor.getInt(cursor.getColumnIndex("C")); + cursor.close(); + + float qslPercent=0; + if (logCount>0){ + qslPercent=100f*(float) isQslCount/(float) logCount; + } + //result.append(String.format("通联的呼号数量:%d",cursor.getInt(cursor.getColumnIndex("C")))); + + if (afterCount!=null){ + ArrayList values=new ArrayList<>(); + values.add(new CountValue(isQslCount,GeneralVariables.getStringFromResource(R.string.count_confirmed))); + values.add(new CountValue(logCount-isQslCount,GeneralVariables.getStringFromResource(R.string.count_unconfirmed))); + StringBuilder stringBuilder=new StringBuilder(); + stringBuilder.append(GeneralVariables.getStringFromResource(R.string.count_total_logs)); + stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_log)); + stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_lotw_confirmed)); + stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_manually_confirmed)); + stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_proportion)); + stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_tota_callsigns)); + afterCount.countInformation(new CountInfo( + //String.format("日志数:%d\n确认的日志数:%d\n平台确认的日志数:%d" + + // "\n手动确认的日志数:%d\n日志确认比例:%.1f%%\n呼号数量:%d" + String.format(stringBuilder.toString() + ,logCount,isQslCount,isLotwQslCount,isQslCount-isLotwQslCount,qslPercent,callsignCount) + ,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.confirmation_statistics) + ,values)); + } + + return null; + } + } + + /** + * 统计ITU分区数量 + */ + static class GetItuZoneCount extends AsyncTask { + private static final String TAG = "GetItuZoneCount"; + private final SQLiteDatabase db; + private final AfterCount afterCount; + + public GetItuZoneCount(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint({"Range", "DefaultLocale"}) + @Override + protected Void doInBackground(Void... voids) { + ArrayList countValues=new ArrayList<>(); + int ituZoneCount=0; + int successCount=0; + String querySQL; + Cursor cursor; + + + querySQL="SELECT count(DISTINCT itu) as c From ituList il "; + + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + ituZoneCount=cursor.getInt(cursor.getColumnIndex("c")); + cursor.close(); + + querySQL="SELECT il.itu ,count(*) as c FROM ituList il \n" + + "inner join QSLTable q\n" + + "on il.grid =UPPER(SUBSTR(q.gridsquare,1,4))\n" + + "GROUP BY il.itu ORDER BY COUNT(*) DESC"; + + cursor = db.rawQuery(querySQL,null); + while (cursor.moveToNext()){ + successCount++; + countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) + ,String.format(GeneralVariables.getStringFromResource(R.string.count_zone) + ,cursor.getString(cursor.getColumnIndex("itu"))))); + } + + if (afterCount!=null){ + afterCount.countInformation(new CountInfo("" + ,ChartType.Bar + ,GeneralVariables.getStringFromResource(R.string.itu_completion_statistics) + ,countValues)); + } + if (afterCount!=null&&ituZoneCount!=0){ + ArrayList values=new ArrayList<>(); + values.add(new CountValue(successCount + ,GeneralVariables.getStringFromResource(R.string.count_completed))); + values.add(new CountValue(ituZoneCount-successCount + ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); + afterCount.countInformation(new CountInfo( + String.format(GeneralVariables.getStringFromResource(R.string.count_total_itu) + ,ituZoneCount,successCount,100f*(float)successCount/(float) ituZoneCount) + ,ChartType.Pie + ,GeneralVariables.getStringFromResource(R.string.count_itu_completed_scale) + ,values)); + } + return null; + } + } + + + + /** + * 统计CQ分区 + */ + static class GetCqZoneCount extends AsyncTask { + private static final String TAG = "GetCqZoneCount"; + private final SQLiteDatabase db; + private final AfterCount afterCount; + + public GetCqZoneCount(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint({"Range", "DefaultLocale"}) + @Override + protected Void doInBackground(Void... voids) { + ArrayList countValues=new ArrayList<>(); + int cqZoneCount=0; + int successCount=0; + String querySQL; + Cursor cursor; + + + querySQL="SELECT count(DISTINCT cqzone) as c From cqzoneList cl "; + + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + cqZoneCount=cursor.getInt(cursor.getColumnIndex("c")); + cursor.close(); + + querySQL="SELECT cl.cqzone ,count(*) as c FROM cqzoneList cl \n" + + "inner join QSLTable q \n" + + "on cl.grid =UPPER(SUBSTR(q.gridsquare,1,4)) \n" + + "GROUP BY cl.cqzone ORDER BY COUNT(*) DESC"; + + cursor = db.rawQuery(querySQL,null); + while (cursor.moveToNext()){ + successCount++; + countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) + ,String.format(GeneralVariables.getStringFromResource(R.string.count_zone) + ,cursor.getString(cursor.getColumnIndex("cqzone"))))); + } + + if (afterCount!=null){ + afterCount.countInformation(new CountInfo("" + ,ChartType.Bar + ,GeneralVariables.getStringFromResource(R.string.count_cqzone_completed) + ,countValues)); + } + if (afterCount!=null&&cqZoneCount!=0){ + ArrayList values=new ArrayList<>(); + values.add(new CountValue(successCount + ,GeneralVariables.getStringFromResource(R.string.count_completed))); + values.add(new CountValue(cqZoneCount-successCount + ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); + afterCount.countInformation(new CountInfo( + String.format(GeneralVariables.getStringFromResource(R.string.count_total_cqzone) + ,cqZoneCount,successCount,100f*(float)successCount/(float) cqZoneCount) + ,ChartType.Pie + ,GeneralVariables.getStringFromResource(R.string.count_cqzone_proportion) + ,values)); + } + + + return null; + } + } + /** + * 统计DXCC分区的数据 + */ + static class GetDxccCount extends AsyncTask{ + private static final String TAG="GetDxccCount"; + private final SQLiteDatabase db; + private final AfterCount afterCount; + + class DxccInfo{ + String name; + int dxcc; + int count=0; + + public DxccInfo(String name, int dxcc, int count) { + this.name = name; + this.dxcc = dxcc; + this.count = count; + } + } + + public GetDxccCount(SQLiteDatabase db, AfterCount afterCount) { + this.db = db; + this.afterCount = afterCount; + } + + @SuppressLint({"Range", "DefaultLocale"}) + @Override + protected Void doInBackground(Void... voids) { + //DXCC的数量 + ArrayList dxccValues=new ArrayList<>(); + int dxccCount=0; + Cursor cursor; + String querySQL; + querySQL="SELECT count(*) as c FROM dxcclist"; + cursor = db.rawQuery(querySQL,null); + cursor.moveToFirst(); + dxccCount=cursor.getInt(cursor.getColumnIndex("c")); + cursor.close(); + + if (GeneralVariables.isChina) { + querySQL = "SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_grid dg\n" + + "inner join QSLTable q \n" + + "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + + "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; +// querySQL="SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_prefix dg\n" + +// "inner join QSLTable q\n" + +// //"on (q.call like (dg.prefix||'%'))\n" + +// "on ((SUBSTR( q.call,1,LENGTH( dg.prefix)) = (dg.prefix))) \n"+ +// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + +// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; + }else { +// querySQL="SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_prefix dg\n" + +// "inner join QSLTable q\n" + +// "on (q.call like (dg.prefix||'%'))\n" + +// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + +// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC"; + querySQL = "SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_grid dg\n" + + "inner join QSLTable q \n" + + "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" + + "GROUP BY dg.dxcc ,dl.aname ORDER BY count(*) DESC"; + + } + cursor = db.rawQuery(querySQL,null); + int successCount=0; + while (cursor.moveToNext()){ + dxccValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c")) + ,cursor.getString(cursor.getColumnIndex("dxccName")))); + successCount++; + } + + if (afterCount!=null){ + afterCount.countInformation(new CountInfo("" + ,ChartType.Bar + ,GeneralVariables.getStringFromResource(R.string.dxcc_completion_statistics) + ,dxccValues)); + } + + + if (afterCount!=null&&dxccCount!=0){ + ArrayList values=new ArrayList<>(); + values.add(new CountValue(successCount + ,GeneralVariables.getStringFromResource(R.string.count_completed))); + values.add(new CountValue(dxccCount-successCount + ,GeneralVariables.getStringFromResource(R.string.count_incomplete))); + afterCount.countInformation(new CountInfo( + String.format(GeneralVariables.getStringFromResource(R.string.count_total_dxcc) + ,dxccCount,successCount,100f*(float)successCount/(float) dxccCount) + ,ChartType.Pie + ,GeneralVariables.getStringFromResource(R.string.count_dxcc_proportion) + ,values)); + } + + return null; + } + } + + + + + public interface AfterCount{ + void countInformation(CountInfo countInfo); + } + + static public class CountInfo{ + public String title; + public String info; + public ArrayList values=null; + public ChartType chartType; + + public CountInfo(String info,ChartType chartType,String title + , ArrayList values) { + this.info = info; + this.values = values; + this.title=title; + this.chartType=chartType; + } + } + + static public class CountValue{ + public int value; + public String name; + + public CountValue(int value, String name) { + this.value = value; + this.name = name; + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java new file mode 100644 index 0000000..0a6f0d2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java @@ -0,0 +1,93 @@ +package com.bg7yoz.ft8cn.count; +/** + * 通联日志的统计界面 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.databinding.FragmentCountBinding; + +import java.util.ArrayList; + + +public class CountFragment extends Fragment { + private static final String TAG="CountFragment"; + private MainViewModel mainViewModel; + private FragmentCountBinding binding; + private ArrayList countInfoList=new ArrayList<>(); + private MutableLiveData> mutableInfoList=new MutableLiveData<>(); + + private RecyclerView countInfoListRecyclerView; + private CountInfoAdapter countInfoAdapter; + + private CountDbOpr.AfterCount afterCount=new CountDbOpr.AfterCount() { + @Override + public void countInformation(CountDbOpr.CountInfo countInfo) { + countInfoList.add(countInfo); + mutableInfoList.postValue(countInfoList); + } + }; + + + public CountFragment() { + // Required empty public constructor + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mainViewModel = MainViewModel.getInstance(this); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding=FragmentCountBinding.inflate(getLayoutInflater()); + mainViewModel = MainViewModel.getInstance(this); + countInfoList.clear(); + countInfoListRecyclerView=binding.countRecyclerView; + countInfoAdapter=new CountInfoAdapter(requireContext(),countInfoList); + countInfoListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + countInfoListRecyclerView.setAdapter(countInfoAdapter); + countInfoAdapter.notifyDataSetChanged(); + + + + CountDbOpr.getQSLTotal(mainViewModel.databaseOpr.getDb(),afterCount); + + + CountDbOpr.getDxcc(mainViewModel.databaseOpr.getDb(),afterCount); + CountDbOpr.getCQZoneCount(mainViewModel.databaseOpr.getDb(),afterCount); + CountDbOpr.getItuCount(mainViewModel.databaseOpr.getDb(),afterCount); + + CountDbOpr.getBandCount(mainViewModel.databaseOpr.getDb(),afterCount); + CountDbOpr.getDistanceCount(mainViewModel.databaseOpr.getDb(),afterCount); + + + + mutableInfoList.observe(requireActivity(), new Observer>() { + @Override + public void onChanged(ArrayList countInfos) { + countInfoAdapter.notifyDataSetChanged(); + } + }); + + return binding.getRoot(); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java new file mode 100644 index 0000000..2f0e619 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java @@ -0,0 +1,260 @@ +package com.bg7yoz.ft8cn.count; +/** + * 用于列出统计结果的Adapter + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.R; +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.components.Description; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.data.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.IndexAxisValueFormatter; + +import java.util.ArrayList; +import java.util.List; + +public class CountInfoAdapter extends RecyclerView.Adapter{ + private static final String TAG="CountInfoAdapter"; + private final ArrayList countInfoList; + private final Context context; + + public CountInfoAdapter(Context context,ArrayList countInfoList) { + this.countInfoList = countInfoList; + this.context=context; + } + + @NonNull + @Override + public CountInfoItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view ; + switch (viewType){ + case CountDbOpr.ChartPie: + view= layoutInflater.inflate(R.layout.count_info_pie_item, parent, false); + break; + case CountDbOpr.ChartBar: + view= layoutInflater.inflate(R.layout.count_info_bar_item, parent, false); + break; + default: + view= layoutInflater.inflate(R.layout.count_info_none_item, parent, false); + break; + } + + return new CountInfoItemHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull CountInfoItemHolder holder, int position) { + holder.countInfo=countInfoList.get(position); + holder.countInfoTextView.setText(holder.countInfo.info); + holder.countTitleTextView.setText(holder.countInfo.title); + switch (holder.countInfo.chartType){ + case Pie: + drawPie(holder); + break; + case Bar: + drawBar(holder); + break; + default: + addDetail(holder); + } + + } + + private void addDetail(@NonNull CountInfoItemHolder holder){ + if (holder.detailLayout==null) return; + holder.detailLayout.removeAllViews(); + for (int i = 0; i barEntries=new ArrayList<>(); + for (int i = 0; i (int) value) { + return holder.countInfo.values.get((int) value).name; + }else { + return ""; + } + } + }; + barChart.getXAxis().setValueFormatter(formatter); + + barChart.setDescription(null); + + barChart.animateY(500); + barChart.invalidate(); + + } + + private void drawPie(@NonNull CountInfoItemHolder holder){ + PieChart countPieChart; + countPieChart=(PieChart) holder.countChart; + countPieChart.getLegend().setTextColor(context.getResources().getColor(R.color.text_view_color)); + + List pieEntries=new ArrayList<>(); + for (int i = 0; i dxccObjects = loadDxccDataFromFile(); + for (DxccObject obj : dxccObjects) { + obj.insertToDb(sqLiteDatabase); + } + } + }).start(); + } + + } + + /** + * 把ITU分区的对应表导入数据库 + * + * @param sqLiteDatabase 数据库 + */ + private void createItuTables(SQLiteDatabase sqLiteDatabase) { + if (!checkTableExists(sqLiteDatabase, "ituList")) { + sqLiteDatabase.execSQL("CREATE TABLE ituList (itu INTEGER,grid TEXT)"); + new Thread(new Runnable() { + @Override + public void run() { + loadItuDataFromFile(sqLiteDatabase); + } + }).start(); + } + } + + private void createCqZoneTables(SQLiteDatabase sqLiteDatabase) { + if (!checkTableExists(sqLiteDatabase, "cqzoneList")) { + sqLiteDatabase.execSQL("CREATE TABLE cqzoneList (cqzone INTEGER,grid TEXT)"); + new Thread(new Runnable() { + @Override + public void run() { + loadICqZoneDataFromFile(sqLiteDatabase); + } + }).start(); + } + } + + /** + * 创建呼号与网格对应关系表 + * + * @param sqLiteDatabase db + */ + private void createCallsignQTHTables(SQLiteDatabase sqLiteDatabase) { + if (!checkTableExists(sqLiteDatabase, "CallsignQTH")) { + sqLiteDatabase.execSQL("CREATE TABLE CallsignQTH(callsign text, grid text" + + ",updateTime Int ,PRIMARY KEY(callsign))"); + } + } + + private void createSWLTables(SQLiteDatabase sqLiteDatabase) { + if (!checkTableExists(sqLiteDatabase, "SWLMessages")) { + sqLiteDatabase.execSQL("CREATE TABLE SWLMessages (\n" + + "\tID INTEGER PRIMARY KEY AUTOINCREMENT,\n" + + "\tI3 INTEGER,\n" + + "\tN3 INTEGER,\n" + + "\tProtocol TEXT,\n" + + "\tUTC TEXT,\n" + + "\tSNR INTEGER,\n" + + "\tTIME_SEC REAL,\n" + + "\tFREQ INTEGER,\n" + + "\tCALL_TO TEXT,\n" + + "\tCALL_FROM TEXT,\n" + + "\tEXTRAL TEXT,\n" + + "\tREPORT INTEGER,\n" + + "\tBAND INTEGER\n" + + ")"); + sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_CALL_TO_IDX " + + "ON SWLMessages (CALL_TO,CALL_FROM)"); + sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_UTC_IDX ON SWLMessages (UTC)"); + } + if (!checkTableExists(sqLiteDatabase, "SWLQSOTable")) { + sqLiteDatabase.execSQL("CREATE TABLE SWLQSOTable (\n" + + "\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n" + + "\t\"call\" TEXT,\n" + + "\tgridsquare TEXT,\n" + + "\tmode TEXT,\n" + + "\trst_sent TEXT,\n" + + "\trst_rcvd TEXT,\n" + + "\tqso_date TEXT,\n" + + "\ttime_on TEXT,\n" + + "\tqso_date_off TEXT,\n" + + "\ttime_off TEXT,\n" + + "\tband TEXT,\n" + + "\tfreq TEXT,\n" + + "\tstation_callsign TEXT,\n" + + "\tmy_gridsquare TEXT,\n" + + "\tcomment TEXT)"); + } + } + + + /** + * 创建索引,以提高导入速度 + * @param sqLiteDatabase 数据库 + */ + private void createIndex(SQLiteDatabase sqLiteDatabase) { + if (!checkIndexExists(sqLiteDatabase, "QslCallsigns_callsign_IDX")) { + sqLiteDatabase.execSQL("CREATE INDEX QslCallsigns_callsign_IDX ON QslCallsigns (callsign,startTime,finishTime,mode)"); + } + if (!checkIndexExists(sqLiteDatabase, "QSLTable_call_IDX")) { + sqLiteDatabase.execSQL("CREATE INDEX QSLTable_call_IDX ON QSLTable (\"call\",qso_date,time_on,mode)"); + } + } + + + public void loadItuDataFromFile(SQLiteDatabase db) { + AssetManager assetManager = context.getAssets(); + InputStream inputStream; + db.execSQL("delete from ituList"); + + String insertSQL = "INSERT INTO ituList (itu,grid)" + + "VALUES(?,?)"; + try { + inputStream = assetManager.open("ituzone.json"); + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + JSONObject jsonObject = new JSONObject(new String(bytes)); + JSONArray array = jsonObject.names(); + for (int i = 0; i < array.length(); i++) { + JSONObject ituObject = new JSONObject(jsonObject.getString(array.getString(i))); + JSONArray mh = ituObject.getJSONArray("mh"); + for (int j = 0; j < mh.length(); j++) { + db.execSQL(insertSQL, new Object[]{array.getString(i), mh.getString(j)}); + } + } + inputStream.close(); + } catch (IOException | JSONException e) { + e.printStackTrace(); + Log.e(TAG, "loadDataFromFile: " + e.getMessage()); + } + } + + public void loadICqZoneDataFromFile(SQLiteDatabase db) { + AssetManager assetManager = context.getAssets(); + InputStream inputStream; + db.execSQL("delete from cqzoneList"); + String insertSQL = "INSERT INTO cqzoneList (cqzone,grid)" + + "VALUES(?,?)"; + try { + inputStream = assetManager.open("cqzone.json"); + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + JSONObject jsonObject = new JSONObject(new String(bytes)); + JSONArray array = jsonObject.names(); + for (int i = 0; i < array.length(); i++) { + JSONObject ituObject = new JSONObject(jsonObject.getString(array.getString(i))); + JSONArray mh = ituObject.getJSONArray("mh"); + for (int j = 0; j < mh.length(); j++) { + db.execSQL(insertSQL, new Object[]{array.getString(i), mh.getString(j)}); + } + } + inputStream.close(); + } catch (IOException | JSONException e) { + e.printStackTrace(); + Log.e(TAG, "loadDataFromFile: " + e.getMessage()); + } + } + + + public ArrayList loadDxccDataFromFile() { + AssetManager assetManager = context.getAssets(); + InputStream inputStream; + ArrayList dxccObjects = new ArrayList<>(); + try { + inputStream = assetManager.open("dxcc_list.json"); + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + JSONObject jsonObject = new JSONObject(new String(bytes)); + JSONArray array = jsonObject.names(); + + for (int i = 0; i < array.length(); i++) { + if (array.getString(i).equals("-1")) continue; + JSONObject dxccObject = new JSONObject(jsonObject.getString(array.getString(i))); + DxccObject dxcc = new DxccObject(); + dxcc.id = Integer.parseInt(array.getString(i)); + dxcc.dxcc = dxccObject.getInt("dxcc"); + dxcc.cc = dxccObject.getString("cc"); + dxcc.ccc = dxccObject.getString("ccc"); + dxcc.name = dxccObject.getString("name"); + dxcc.continent = dxccObject.getString("continent"); + dxcc.ituZone = dxccObject.getString("ituzone") + .replace("[", "") + .replace("]", "") + .replace("\"", ""); + dxcc.cqZone = dxccObject.getString("cqzone") + .replace("[", "") + .replace("]", "") + .replace("\"", ""); + dxcc.timeZone = dxccObject.getInt("timezone"); + dxcc.cCode = dxccObject.getInt("ccode"); + dxcc.aName = dxccObject.getString("aname"); + dxcc.pp = dxccObject.getString("pp"); + dxcc.lat = dxccObject.getDouble("lat"); + dxcc.lon = dxccObject.getDouble("lon"); + + JSONArray mh = dxccObject.getJSONArray("mh"); + for (int j = 0; j < mh.length(); j++) { + dxcc.grid.add(mh.getString(j)); + } + JSONArray prefix = dxccObject.getJSONArray("prefix"); + for (int j = 0; j < prefix.length(); j++) { + dxcc.prefix.add(prefix.getString(j)); + } + dxccObjects.add(dxcc); + //Log.e(TAG, "loadDataFromFile: id:" + dxcc.id + " dxcc:" + dxcc.dxcc); + } + + inputStream.close(); + } catch (IOException | JSONException e) { + e.printStackTrace(); + Log.e(TAG, "loadDataFromFile: " + e.getMessage()); + } + return dxccObjects; + } + + + /** + * 把呼号和网格对应关系写入表中 + * + * @param callsign 呼号 + * @param grid 网格 + */ + public void addCallsignQTH(String callsign, String grid) { + if (grid.trim().length() < 4) return; + new AddCallsignQTH(db).execute(callsign, grid); + Log.d(TAG, String.format("addCallsignQTH: callsign:%s,grid:%s", callsign, grid)); + } + + //查询配置信息。 + public void getConfigByKey(String KeyName, OnAfterQueryConfig onAfterQueryConfig) { + new QueryConfig(db, KeyName, onAfterQueryConfig).execute(); + } + + public void getCallSign(String callsign, String fieldName, String tableName, OnGetCallsign getCallsign) { + new QueryCallsign(db, tableName, fieldName, callsign, getCallsign).execute(); + } + + /** + * 写配置信息,异步操作 + */ + public void writeConfig(String KeyName, String Value, OnAfterWriteConfig onAfterWriteConfig) { + Log.d(TAG, "writeConfig: Value:" + Value); + new WriteConfig(db, KeyName, Value, onAfterWriteConfig).execute(); + } + + public void writeMessage(ArrayList messages) { + new WriteMessages(db, messages).execute(); + } + + /** + * 读取关注的呼号列表 + * + * @param onAffterQueryFollowCallsigns 回调函数 + */ + public void getFollowCallsigns(OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { + new GetFollowCallSigns(db, onAffterQueryFollowCallsigns).execute(); + } + + /** + * 查询SWL MESSAGE各BAND的数量 + * @param onAfterQueryFollowCallsigns 回调 + */ + public void getMessageLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) { + new GetMessageLogTotal(db, onAfterQueryFollowCallsigns).execute(); + } + + /** + * 查询SWL QSO的在各个月的数量 + * @param onAfterQueryFollowCallsigns 回调 + */ + public void getSWLQsoLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) { + new GetSWLQsoTotal(db, onAfterQueryFollowCallsigns).execute(); + } + + + /** + * 向数据库中添加关注的呼号 + * + * @param callsign 呼号 + */ + public void addFollowCallsign(String callsign) { + new AddFollowCallSign(db, callsign).execute(); + } + + /** + * 清空关注的呼号 + */ + public void clearFollowCallsigns() { + new Thread(new Runnable() { + @Override + public void run() { + db.execSQL("delete from followCallsigns "); + } + }).start(); + } + + /** + * 删除通联的日志 + */ + public void clearLogCacheData() { + new Thread(new Runnable() { + @Override + public void run() { + db.execSQL("delete from SWLMessages "); + } + }).start(); + } + + /** + * 删除SWL QSO日志 + */ + public void clearSWLQsoData() { + new Thread(new Runnable() { + @Override + public void run() { + db.execSQL("delete from SWLQSOTable "); + } + }).start(); + } + /** + * 把通联成功的日志和呼号写到数据库中 + * + * @param qslRecord 通联记录 + */ + public void addQSL_Callsign(QSLRecord qslRecord) { + new AddQSL_Info(this, qslRecord).execute(); + } + + /** + * 把SWL的QSO保存到数据库,SWL的QSO标准:至少要有双方的信号报告。不包含自己的呼号。 + * @param qslRecord 通联日志记录 + */ + public void addSWL_QSO(QSLRecord qslRecord) { + new Add_SWL_QSO_Info(this, qslRecord).execute(); + } + + //删除数据库中关注的呼号 + public void deleteFollowCallsign(String callsign) { + new DeleteFollowCallsign(db, callsign).execute(); + } + + //获取所有配置参数 + public void getAllConfigParameter(OnAfterQueryConfig onAfterQueryConfig) { + new GetAllConfigParameter(db, onAfterQueryConfig).execute(); + } + + /** + * 查询全部成功通联的呼号,能通联的频率为条件 + */ + public void getAllQSLCallsigns() { + new LoadAllQSLCallsigns(db).execute(); + } + + + /** + * 按呼号查找QSL的呼号记录 + * + * @param callsign 呼号 + * @param onQueryQSLCallsign 回调 + */ + public void getQSLCallsignsByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLCallsign onQueryQSLCallsign) { + new GetQLSCallsignByCallsign(showAll,offset,db, callsign, filter, onQueryQSLCallsign).execute(); + } + + /** + * 查询已经QSO的网格,这个主要用在GridTracker上 + * 可以知道哪些网格是QSO,哪些是QSL + * + * @param onGetQsoGrids 当查询结束之后的事件。 + */ + public void getQsoGridQuery(OnGetQsoGrids onGetQsoGrids) { + new GetQsoGrids(db, onGetQsoGrids).execute(); + } + + /** + * 按呼号查询QSL记录 + * + * @param callsign 呼号 + * @param onQueryQSLRecordCallsign 回调 + */ + public void getQSLRecordByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) { + new GetQSLByCallsign(showAll,offset,db, callsign, filter, onQueryQSLRecordCallsign).execute(); + } + + /** + * 删除通联呼号 + * + * @param id id号 + */ + public void deleteQSLCallsign(int id) { + new DeleteQSLCallsignByID(db, id).execute(); + } + + /** + * 删除日志 + * + * @param id id号 + */ + public void deleteQSLByID(int id) { + new DeleteQSLByID(db, id).execute(); + } + + /** + * 修改日志的手工确认 + * + * @param isQSL 是否确认 + * @param id ID号 + */ + public void setQSLTableIsQSL(boolean isQSL, int id) { + new SetQSLTableIsQSL(db, id, isQSL).execute(); + } + + public void setQSLCallsignIsQSL(boolean isQSL, int id) { + new SetQSLCallsignIsQSL(db, id, isQSL).execute(); + } + + /** + * 到数据库中查呼号和网格的对应关系,查出后,会把数据写入到GeneralVariables的callsignAndGrids中 + * + * @param callsign 呼号 + */ + public void getCallsignQTH(String callsign) { + new GetCallsignQTH(db).execute(callsign); + } + + /** + * 把已经通联的DXCC分区列出来 + */ + @SuppressLint("Range") + public void getQslDxccToMap() { + new Thread(new Runnable() { + @Override + public void run() { + String querySQL; + Cursor cursor; + Log.d(TAG, "run: 开始导入分区..."); + + //导入已经通联的dxcc + querySQL = "SELECT DISTINCT dl.pp FROM dxcc_grid dg\n" + + "inner join QSLTable q\n" + + "on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc"; + cursor = db.rawQuery(querySQL, null); + while (cursor.moveToNext()) { + GeneralVariables.addDxcc(cursor.getString(cursor.getColumnIndex("pp"))); + } + cursor.close(); + + //导入已经通联的CQ分区 + querySQL = "SELECT DISTINCT cl.cqzone as cq FROM cqzoneList cl\n" + + "inner join QSLTable q\n" + + "on cl.grid =UPPER(SUBSTR(q.gridsquare,1,4)) "; + cursor = db.rawQuery(querySQL, null); + while (cursor.moveToNext()) { + GeneralVariables.addCqZone(cursor.getInt(cursor.getColumnIndex("cq"))); + } + cursor.close(); + + //导入已经通联的itu分区 + querySQL = "SELECT DISTINCT il.itu FROM ituList il\n" + + "inner join QSLTable q\n" + + "on il.grid =UPPER(SUBSTR(q.gridsquare,1,4))"; + cursor = db.rawQuery(querySQL, null); + while (cursor.moveToNext()) { + GeneralVariables.addItuZone(cursor.getInt(cursor.getColumnIndex("itu"))); + } + cursor.close(); + + Log.d(TAG, "run: 分区导入完毕..."); + } + }).start(); + + } + + + /** + * 检查通联的呼号是不是存在,如果存在,返回TRUE,并且更新isLotW_QSL, + * + * @param record 记录 + * @return 是否存在 + */ + @SuppressLint("Range") + public boolean checkQSLCallsign(QSLRecord record) { + QSLRecord newRecord = record; + newRecord.id = -1; + //检查是不是已经存在呼号了 + String querySQL = "select * from QslCallsigns WHERE (callsign=?)" + + "and (startTime=?) and(finishTime=?)" + + "and(mode=?)"; + + Cursor cursor = db.rawQuery(querySQL, new String[]{ + record.getToCallsign() + , record.getStartTime() + , record.getEndTime() + , record.getMode()}); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + newRecord.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1 + || record.isLotW_QSL; + newRecord.id = cursor.getLong(cursor.getColumnIndex("ID")); + } + cursor.close(); +// if (newRecord.id != -1) {//说明已经存在记录了 +// querySQL = "UPDATE QslCallsigns set isLotW_QSL=? WHERE ID=?"; +// db.execSQL(querySQL, new Object[]{newRecord.isLotW_QSL ? "1" : "0", newRecord.id}); +// } + return newRecord.id != -1;// + } + + @SuppressLint("Range") + public boolean checkIsQSL(QSLRecord record) { + QSLRecord newRecord = record; + newRecord.id = -1; + //检查是不是已经存在日志记录了 + String querySQL = "select * from QSLTable WHERE (call=?)" + + "and (qso_date=?) and(time_on=?)" + + "and(mode=?)"; + + Cursor cursor = db.rawQuery(querySQL, new String[]{ + record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + newRecord.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1 + || record.isLotW_QSL; + newRecord.id = cursor.getLong(cursor.getColumnIndex("id")); + } + cursor.close(); + +// if (newRecord.id != -1) {//说明已经存在记录了 +// querySQL = "UPDATE QSLTable set isLotW_QSL=? WHERE ID=?"; +// db.execSQL(querySQL, new Object[]{newRecord.isLotW_QSL ? "1" : "0", newRecord.id}); +// } + return newRecord.id != -1;// + } + + @SuppressLint("Range") + public boolean doInsertQSLData(QSLRecord record,AfterInsertQSLData afterInsertQSLData) { + if (record.getToCallsign() == null) { + if (afterInsertQSLData!=null){ + afterInsertQSLData.doAfterInsert(true,true);//说明是无效的QSL + } + return false; + } + + String querySQL; + if (!checkQSLCallsign(record)) {//如果不存在记录,就添加 + querySQL = "INSERT INTO QslCallsigns (callsign" + + ",isQSL,isLotW_import,isLotW_QSL" + + ",startTime,finishTime,mode,grid,band,band_i)" + + "values(?,?,?,?,?,?,?,?,?,?)"; + db.execSQL(querySQL, new Object[]{record.getToCallsign() + , record.isQSL ? 1 : 0//是否手工确认 + , record.isLotW_import ? 1 : 0//是否lotw导入 + , record.isLotW_QSL ? 1 : 0//是否lotw确认 + , record.getStartTime() + , record.getEndTime() + , record.getMode() + , record.getToMaidenGrid() + , BaseRigOperation.getFrequencyAllInfo(record.getBandFreq()) + , record.getBandFreq()}); + } else { + if (record.isQSL) { + db.execSQL("UPDATE QslCallsigns SET isQSL=? " + + "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" + , new Object[]{1, record.getToCallsign(), record.getStartTime() + , record.getEndTime(), record.getMode()}); + } + if (record.isLotW_import) { + db.execSQL("UPDATE QslCallsigns SET isLotW_import=? " + + "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" + , new Object[]{1, record.getToCallsign(), record.getStartTime() + , record.getEndTime(), record.getMode()}); + } + + if (record.isLotW_QSL) { + db.execSQL("UPDATE QslCallsigns SET isLotW_QSL=? " + + "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" + , new Object[]{1, record.getToCallsign(), record.getStartTime() + , record.getEndTime(), record.getMode()}); + } + if (record.getToMaidenGrid().length() >= 4) { + db.execSQL("UPDATE QslCallsigns SET grid=? " + + "WHERE (callsign=?)AND(startTime=?)AND(finishTime=?)AND(mode=?)" + , new Object[]{record.getToMaidenGrid(), record.getToCallsign(), record.getStartTime() + , record.getEndTime(), record.getMode()}); + } + + } + + + if (!checkIsQSL(record)) {//如果不存在日志数据就添加 + querySQL = "INSERT INTO QSLTable(call, isQSL,isLotW_import,isLotW_QSL,gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + + "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare," + + "comment)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + db.execSQL(querySQL, new String[]{record.getToCallsign() + , String.valueOf(record.isQSL ? 1 : 0) + , String.valueOf(record.isLotW_import ? 1 : 0) + , String.valueOf(record.isLotW_QSL ? 1 : 0) + , record.getToMaidenGrid() + , record.getMode() + , String.valueOf(record.getSendReport()) + , String.valueOf(record.getReceivedReport()) + , record.getQso_date() + , record.getTime_on() + + , record.getQso_date_off() + , record.getTime_off() + , record.getBandLength()//波长//RigOperationConstant.getMeterFromFreq(qslRecord.getBandFreq()) + , BaseRigOperation.getFrequencyFloat(record.getBandFreq()) + , record.getMyCallsign() + , record.getMyMaidenGrid() + , record.getComment()}); + if (afterInsertQSLData!=null){ + afterInsertQSLData.doAfterInsert(false,true);//说明是新的QSL + } + + } else { + if (record.isQSL) { + db.execSQL("UPDATE QSLTable SET isQSL=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{1, record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.isLotW_import) { + db.execSQL("UPDATE QSLTable SET isLotW_import=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{1, record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.isLotW_QSL) { + db.execSQL("UPDATE QSLTable SET isLotW_QSL=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{1, record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.getToMaidenGrid().length() >= 4) { + db.execSQL("UPDATE QSLTable SET gridsquare=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{record.getToMaidenGrid(), record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.getMyMaidenGrid().length() >= 4) { + db.execSQL("UPDATE QSLTable SET my_gridsquare=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{record.getMyMaidenGrid(), record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.getSendReport() > -100) { + db.execSQL("UPDATE QSLTable SET rst_sent=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{record.getSendReport(), record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + if (record.getReceivedReport() > -100) { + db.execSQL("UPDATE QSLTable SET rst_rcvd=? " + + " WHERE (call=?) and (qso_date=?) and(time_on=?) and(mode=?)" + , new Object[]{record.getReceivedReport(), record.getToCallsign() + , record.getQso_date() + , record.getTime_on() + , record.getMode()}); + } + + if (afterInsertQSLData!=null){ + afterInsertQSLData.doAfterInsert(false,false);//说明已经存在,需要更新的QSL + } + } + return true; + } + + + /** + * 查询配置信息的类 + */ + static class QueryConfig extends AsyncTask { + private final SQLiteDatabase db; + private final String KeyName; + private final OnAfterQueryConfig afterQueryConfig; + + public QueryConfig(SQLiteDatabase db, String keyName, OnAfterQueryConfig afterQueryConfig) { + this.db = db; + KeyName = keyName; + this.afterQueryConfig = afterQueryConfig; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (afterQueryConfig != null) { + afterQueryConfig.doOnBeforeQueryConfig(KeyName); + } + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL = "select keyName,Value from config where KeyName =?"; + Cursor cursor = db.rawQuery(querySQL, new String[]{KeyName.toString()}); + if (cursor.moveToFirst()) { + if (afterQueryConfig != null) { + afterQueryConfig.doOnAfterQueryConfig(KeyName, cursor.getString(cursor.getColumnIndex("Value"))); + } + } else { + if (afterQueryConfig != null) { + afterQueryConfig.doOnAfterQueryConfig(KeyName, ""); + } + } + cursor.close(); + return null; + } + } + + static class QueryCallsign extends AsyncTask { + private final SQLiteDatabase db; + private final String tableName; + private final String fieldName; + private final String callSign; + private OnGetCallsign onGetCallsign; + + public QueryCallsign(SQLiteDatabase db, String tableName, String fieldName + , String callSign, OnGetCallsign onGetCallsign) { + this.db = db; + this.tableName = tableName; + this.fieldName = fieldName; + this.callSign = callSign; + this.onGetCallsign = onGetCallsign; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String sql = String.format("select count(%s) as a FROM %s where %s=\"%s\" limit 1" + , fieldName, tableName, fieldName, callSign); + Cursor cursor = db.rawQuery(sql, null); + if (cursor.moveToFirst()) { + if (onGetCallsign != null) { + onGetCallsign.doOnAfterGetCallSign(cursor.getInt(cursor.getColumnIndex("a")) > 0); + } + } else { + if (onGetCallsign != null) { + onGetCallsign.doOnAfterGetCallSign(false); + } + + } + cursor.close(); + return null; + } + } + + /** + * 写配置信息的类 + */ + static class WriteConfig extends AsyncTask { + private final SQLiteDatabase db; + private final String KeyName; + private final String Value; + private final OnAfterWriteConfig afterWriteConfig; + + public WriteConfig(SQLiteDatabase db, String keyName, String Value, OnAfterWriteConfig afterWriteConfig) { + this.db = db; + this.KeyName = keyName; + this.afterWriteConfig = afterWriteConfig; + this.Value = Value; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL = "DELETE FROM config where KeyName =?"; + db.execSQL(querySQL, new String[]{KeyName.toString()}); + querySQL = "INSERT INTO config (KeyName,Value)Values(?,?)"; + db.execSQL(querySQL, new String[]{KeyName.toString(), Value.toString()}); + if (afterWriteConfig != null) { + afterWriteConfig.doOnAfterWriteConfig(true); + } + return null; + } + } + + /** + * 把消息写到数据库 + */ + static class WriteMessages extends AsyncTask { + private final SQLiteDatabase db; + private ArrayList messages; + + public WriteMessages(SQLiteDatabase db, ArrayList messages) { + this.db = db; + this.messages = messages; + } + + @Override + protected Void doInBackground(Void... voids) { + String sql = "INSERT INTO SWLMessages(I3,N3,Protocol,UTC,SNR,TIME_SEC,FREQ,CALL_FROM" + + ",CALL_TO,EXTRAL,REPORT,BAND)\n" + + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; + for (Ft8Message message : messages) {//只对与我有关的消息做保存 + db.execSQL(sql, new Object[]{message.i3, message.n3, "FT8" + ,UtcTimer.getDatetimeYYYYMMDD_HHMMSS(message.utcTime) + , message.snr, message.time_sec, Math.round(message.freq_hz) + , message.callsignFrom, message.callsignTo, message.extraInfo + , message.report, message.band}); + + } + return null; + } + } + + /** + * 把关注的呼号写到数据库 + */ + static class AddFollowCallSign extends AsyncTask { + private final SQLiteDatabase db; + private final String callSign; + + public AddFollowCallSign(SQLiteDatabase db, String callSign) { + this.db = db; + this.callSign = callSign; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL = "INSERT OR IGNORE INTO followCallsigns (callsign)values(?)"; + db.execSQL(querySQL, new String[]{callSign}); + return null; + } + } + + /** + * 向呼号网格对应表中写数据,AsyncTask中的String,是多参数,以数组形式给doInBackground + * 所以,写入数据第一个元素是呼号,第二个是网格 + */ + static class AddCallsignQTH extends AsyncTask { + private final SQLiteDatabase db; + + public AddCallsignQTH(SQLiteDatabase db) { + this.db = db; + } + + @Override + protected Void doInBackground(String... strings) { + if (strings.length == 2) { + String querySQL = "INSERT OR REPLACE INTO CallsignQTH (callsign,grid,updateTime)" + + "VALUES (Upper(?),?,?)"; + db.execSQL(querySQL, new Object[]{strings[0], strings[1], System.currentTimeMillis()}); + } + return null; + } + } + + static class Add_SWL_QSO_Info extends AsyncTask{ + private final DatabaseOpr databaseOpr; + private QSLRecord qslRecord; + public Add_SWL_QSO_Info(DatabaseOpr opr, QSLRecord qslRecord) { + this.databaseOpr = opr; + this.qslRecord = qslRecord; + } + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL; + //删除之前重复的记录 + querySQL = "DELETE FROM SWLQSOTable where ([call]=?) and (station_callsign=?) and (qso_date=?) and(time_on=?) and (freq=?)"; + databaseOpr.db.execSQL(querySQL, new String[]{ + qslRecord.getToCallsign() + , qslRecord.getMyCallsign() + , qslRecord.getQso_date() + , qslRecord.getTime_on() + , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) + }); + //添加记录 + querySQL = "INSERT INTO SWLQSOTable([call], gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + + "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare,comment)\n" + + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + databaseOpr.db.execSQL(querySQL, new String[]{qslRecord.getToCallsign() + , qslRecord.getToMaidenGrid() + , qslRecord.getMode() + , String.valueOf(qslRecord.getSendReport()) + , String.valueOf(qslRecord.getReceivedReport()) + , qslRecord.getQso_date() + , qslRecord.getTime_on() + + , qslRecord.getQso_date_off() + , qslRecord.getTime_off() + , qslRecord.getBandLength()//波长//RigOperationConstant.getMeterFromFreq(qslRecord.getBandFreq()) + , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) + , qslRecord.getMyCallsign() + , qslRecord.getMyMaidenGrid() + , qslRecord.getComment()}); + + + return null; + } + + } + + /** + * 把QSL成功的呼号写到库中 + */ + static class AddQSL_Info extends AsyncTask { + //private final SQLiteDatabase db; + private final DatabaseOpr databaseOpr; + private QSLRecord qslRecord; + + public AddQSL_Info(DatabaseOpr opr, QSLRecord qslRecord) { + this.databaseOpr = opr; + this.qslRecord = qslRecord; + } + + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + databaseOpr.doInsertQSLData(qslRecord,null);//添加日志和通联成功的呼号 + return null; + } + } + + + /** + * 从数据库中删除关注的呼号 + */ + static class DeleteFollowCallsign extends AsyncTask { + private final SQLiteDatabase db; + private final String callSign; + + public DeleteFollowCallsign(SQLiteDatabase db, String callSign) { + this.db = db; + this.callSign = callSign; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String querySQL = "DELETE from followCallsigns WHERE callsign=?"; + db.execSQL(querySQL, new String[]{callSign}); + return null; + } + } + + /** + * 向呼号与网格对应关系表中查网格,参数是呼号 + */ + static class GetCallsignQTH extends AsyncTask { + private final SQLiteDatabase db; + + GetCallsignQTH(SQLiteDatabase db) { + this.db = db; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(String... strings) { + if (strings.length == 0) return null; + String querySQL = "select grid from CallsignQTH cq \n" + + "WHERE callsign =?"; + Cursor cursor = db.rawQuery(querySQL, new String[]{strings[0]}); + if (cursor.moveToFirst()) { + GeneralVariables.addCallsignAndGrid(strings[0] + , cursor.getString(cursor.getColumnIndex("grid"))); + } + cursor.close(); + + return null; + } + } + + static class GetMessageLogTotal extends AsyncTask { + private final SQLiteDatabase db; + private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; + + public GetMessageLogTotal(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { + this.db = db; + this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; + } + + @Override + @SuppressLint({"Range", "DefaultLocale"}) + protected Void doInBackground(Void... voids) { + String querySQL = "SELECT BAND ,count(*) as c from SWLMessages m group by BAND order by BAND "; + Cursor cursor = db.rawQuery(querySQL, new String[]{}); + ArrayList callsigns = new ArrayList<>(); + callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total)); + callsigns.add("---------------------------------------"); + int sum = 0; + while (cursor.moveToNext()) { + long s = cursor.getLong(cursor.getColumnIndex("BAND")); //获取频段 + int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量 + callsigns.add(String.format("%.3fMHz \t %d", s / 1000000f, total)); + sum = sum + total; + } + callsigns.add(String.format("-----------Total %d -----------", sum)); + cursor.close(); + if (onAffterQueryFollowCallsigns != null) { + onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); + } + return null; + } + } + + + static class GetSWLQsoTotal extends AsyncTask { + private final SQLiteDatabase db; + private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; + + public GetSWLQsoTotal(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { + this.db = db; + this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; + } + + @Override + @SuppressLint({"Range", "DefaultLocale"}) + protected Void doInBackground(Void... voids) { + String querySQL = "select count(*) as c,substr(qso_date_off,1,6) as t \n" + + "from SWLQSOTable s\n" + + "group by substr(qso_date_off,1,6)"; + Cursor cursor = db.rawQuery(querySQL, new String[]{}); + ArrayList callsigns = new ArrayList<>(); + //callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total)); + callsigns.add("---------------------------------------"); + int sum = 0; + while (cursor.moveToNext()) { + String date = cursor.getString(cursor.getColumnIndex("t")); //获取频段 + int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量 + callsigns.add(String.format("%s \t %d ", date, total)); + sum = sum + total; + } + callsigns.add(String.format("-----------Total %d -----------", sum)); + cursor.close(); + if (onAffterQueryFollowCallsigns != null) { + onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); + } + return null; + } + } + + + + /** + * 从数据库中获取关注的呼号类 + */ + static class GetFollowCallSigns extends AsyncTask { + private final SQLiteDatabase db; + private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns; + + public GetFollowCallSigns(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) { + this.db = db; + this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns; + } + + @Override + protected Void doInBackground(Void... voids) { + String querySQL = "select callsign from followCallsigns"; + Cursor cursor = db.rawQuery(querySQL, new String[]{}); + ArrayList callsigns = new ArrayList<>(); + while (cursor.moveToNext()) { + @SuppressLint("Range") + String s = cursor.getString(cursor.getColumnIndex("callsign")); //获取第一列的值,第一列的索引从0开始 + if (s != null) { + callsigns.add(s); + } + } + cursor.close(); + if (onAffterQueryFollowCallsigns != null) { + onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns); + } + return null; + } + } + + public static class GetCallsignMapGrid extends AsyncTask { + SQLiteDatabase db; + + public GetCallsignMapGrid(SQLiteDatabase db) { + this.db = db; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + + String querySQL = "select DISTINCT callsign,grid from QslCallsigns qc \n" + + "where LENGTH(grid)>3\n" + + "order by ID "; + Cursor cursor = db.rawQuery(querySQL, null); + while (cursor.moveToNext()) { + GeneralVariables.addCallsignAndGrid(cursor.getString(cursor.getColumnIndex("callsign")) + , cursor.getString(cursor.getColumnIndex("grid"))); + + } + cursor.close(); + return null; + } + } + + public interface OnGetQsoGrids { + void onAfterQuery(HashMap grids); + } + + + static class GetQsoGrids extends AsyncTask { + SQLiteDatabase db; + HashMap grids = new HashMap<>(); + OnGetQsoGrids onGetQsoGrids; + + public GetQsoGrids(SQLiteDatabase db, OnGetQsoGrids onGetQsoGrids) { + this.db = db; + this.onGetQsoGrids = onGetQsoGrids; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + + String querySQL = "select qc.gridsquare ,count(*) as cc,SUM(isQSL)+SUM(isLotW_QSL)as isQSL\n" + + "from QSLTable qc\n" + + "WHERE LENGTH (qc.gridsquare)>2 \n" + + "group by qc.gridsquare\n" + + "ORDER by SUM(isQSL)+SUM(isLotW_QSL) desc"; + Cursor cursor = db.rawQuery(querySQL, null); + + while (cursor.moveToNext()) { + grids.put(cursor.getString(cursor.getColumnIndex("gridsquare")) + , cursor.getInt(cursor.getColumnIndex("isQSL")) != 0); + + } + cursor.close(); + if (onGetQsoGrids != null) { + onGetQsoGrids.onAfterQuery(grids); + } + return null; + } + } + + static class GetQSLByCallsign extends AsyncTask { + boolean showAll; + int offset; + SQLiteDatabase db; + String callsign; + int filter; + OnQueryQSLRecordCallsign onQueryQSLRecordCallsign; + + public GetQSLByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) { + this.showAll=showAll; + this.offset=offset; + this.db = db; + this.callsign = callsign; + this.filter = queryFilter; + this.onQueryQSLRecordCallsign = onQueryQSLRecordCallsign; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String filterStr; + switch (filter) { + case 1: + filterStr = "and((isQSL =1)or(isLotW_QSL =1))\n"; + break; + case 2: + filterStr = "and((isQSL =0)and(isLotW_QSL =0))\n"; + break; + default: + filterStr = ""; + } + String limitStr=""; + if (!showAll){ + limitStr="limit 100 offset "+offset; + } + String querySQL = "select * from QSLTable where ([call] like ?) \n" + + filterStr + + " ORDER BY qso_date DESC, time_off DESC\n"+ + //" order by ID desc\n"+ + limitStr; + Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"}); + ArrayList records = new ArrayList<>(); + while (cursor.moveToNext()) { + QSLRecordStr record = new QSLRecordStr(); + record.id = cursor.getInt(cursor.getColumnIndex("id")); + record.setCall(cursor.getString(cursor.getColumnIndex("call"))); + record.isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; + record.isLotW_import = cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1; + record.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; + record.setGridsquare(cursor.getString(cursor.getColumnIndex("gridsquare"))); + record.setMode(cursor.getString(cursor.getColumnIndex("mode"))); + record.setRst_sent(cursor.getString(cursor.getColumnIndex("rst_sent"))); + record.setRst_rcvd(cursor.getString(cursor.getColumnIndex("rst_rcvd"))); + record.setTime_on(String.format("%s-%s" + , cursor.getString(cursor.getColumnIndex("qso_date")) + , cursor.getString(cursor.getColumnIndex("time_on")))); + + record.setTime_off(String.format("%s-%s" + , cursor.getString(cursor.getColumnIndex("qso_date_off")) + , cursor.getString(cursor.getColumnIndex("time_off")))); + record.setBand(cursor.getString(cursor.getColumnIndex("band")));//波长 + record.setFreq(cursor.getString(cursor.getColumnIndex("freq")));//频率 + record.setStation_callsign(cursor.getString(cursor.getColumnIndex("station_callsign"))); + record.setMy_gridsquare(cursor.getString(cursor.getColumnIndex("my_gridsquare"))); + record.setComment(cursor.getString(cursor.getColumnIndex("comment"))); + records.add(record); + } + cursor.close(); + if (onQueryQSLRecordCallsign != null) { + onQueryQSLRecordCallsign.afterQuery(records); + } + return null; + } + } + + /** + * 通过呼号查询联通成功的呼号 + */ + static class GetQLSCallsignByCallsign extends AsyncTask { + SQLiteDatabase db; + String callsign; + int filter; + OnQueryQSLCallsign onQueryQSLCallsign; + int offset; + boolean showAll; + + public GetQLSCallsignByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLCallsign onQueryQSLCallsign) { + this.showAll=showAll; + this.offset=offset; + this.db = db; + this.callsign = callsign; + this.filter = queryFilter; + this.onQueryQSLCallsign = onQueryQSLCallsign; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + String filterStr; + switch (filter) { + case 1: + filterStr = "and((q.isQSL =1)or(q.isLotW_QSL =1))\n"; + break; + case 2: + filterStr = "and((q.isQSL =0)and(q.isLotW_QSL =0))\n"; + break; + default: + filterStr = ""; + } + String limitStr=""; + if (!showAll){ + limitStr="limit 100 offset "+offset; + } + String querySQL = "select q.[call] as callsign ,q.gridsquare as grid" + + ",q.band||\"(\"||q.freq||\" MHz)\" as band \n" + + ",q.qso_date as last_time ,q.mode ,q.isQSL,q.isLotW_QSL\n" + + "from QSLTable q inner join QSLTable q2 ON q.id =q2.id \n" + + "where (q.[call] like ?)\n" + + filterStr + + "group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.band\n" + + ",q.mode,q.isQSL,q.isLotW_QSL\n" + + "HAVING q.qso_date =MAX(q2.qso_date) \n" + + "order by q.qso_date desc\n"+ + limitStr; + + + Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"}); + ArrayList records = new ArrayList<>(); + while (cursor.moveToNext()) { + QSLCallsignRecord record = new QSLCallsignRecord(); + record.setCallsign(cursor.getString(cursor.getColumnIndex("callsign"))); + record.isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; + record.isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; + record.setLastTime(cursor.getString(cursor.getColumnIndex("last_time"))); + record.setMode(cursor.getString(cursor.getColumnIndex("mode"))); + record.setGrid(cursor.getString(cursor.getColumnIndex("grid"))); + record.setBand(cursor.getString(cursor.getColumnIndex("band"))); + records.add(record); + } + cursor.close(); + if (onQueryQSLCallsign != null) { + onQueryQSLCallsign.afterQuery(records); + } + return null; + } + } + + + /** + * 获取通联过的呼号 + */ + @SuppressLint("DefaultLocale") + static class GetAllQSLCallsign { + public static void get(SQLiteDatabase db) { + + //String querySQL = "select distinct [call] from QSLTable where freq=?"; + //改为以波长BAND取通联过的呼号 + String querySQL = "select distinct [call] from QSLTable where band=?"; + Cursor cursor = db.rawQuery(querySQL, new String[]{ + BaseRigOperation.getMeterFromFreq(GeneralVariables.band)}); + ArrayList callsigns = new ArrayList<>(); + while (cursor.moveToNext()) { + @SuppressLint("Range") + String s = cursor.getString(cursor.getColumnIndex("call")); + if (s != null) { + callsigns.add(s); + } + } + cursor.close(); + GeneralVariables.QSL_Callsign_list = callsigns; + + querySQL = "select distinct [call] from QSLTable where band<>?"; + cursor = db.rawQuery(querySQL, new String[]{ + BaseRigOperation.getMeterFromFreq(GeneralVariables.band)}); + + ArrayList other_callsigns = new ArrayList<>(); + while (cursor.moveToNext()) { + @SuppressLint("Range") + String s = cursor.getString(cursor.getColumnIndex("call")); + if (s != null) { + other_callsigns.add(s); + } + } + cursor.close(); + GeneralVariables.QSL_Callsign_list_other_band = other_callsigns; + } + + } + + + /** + * 通过ID删除通联呼号 + */ + static class DeleteQSLCallsignByID extends AsyncTask { + private final SQLiteDatabase db; + private final int id; + + public DeleteQSLCallsignByID(SQLiteDatabase db, int id) { + this.db = db; + this.id = id; + } + + + @Override + protected Void doInBackground(Void... voids) { + db.execSQL("delete from QslCallsigns where id=?", new Object[]{id}); + return null; + } + } + + + /** + * 通过ID删除日志 + */ + static class DeleteQSLByID extends AsyncTask { + private final SQLiteDatabase db; + private final int id; + + public DeleteQSLByID(SQLiteDatabase db, int id) { + this.db = db; + this.id = id; + } + + @Override + protected Void doInBackground(Void... voids) { + db.execSQL("delete from QSLTable where id=?", new Object[]{id}); + return null; + } + } + + static class SetQSLCallsignIsQSL extends AsyncTask { + private final SQLiteDatabase db; + private final int id; + private final boolean isQSL; + + public SetQSLCallsignIsQSL(SQLiteDatabase db, int id, boolean isQSL) { + this.db = db; + this.id = id; + this.isQSL = isQSL; + } + + @Override + protected Void doInBackground(Void... voids) { + db.execSQL("UPDATE QslCallsigns SET isQSL=? where id=?", new Object[]{isQSL ? "1" : "0", id}); + return null; + } + } + + /** + * 设置日志手工确认 + */ + static class SetQSLTableIsQSL extends AsyncTask { + private final SQLiteDatabase db; + private final int id; + private final boolean isQSL; + + public SetQSLTableIsQSL(SQLiteDatabase db, int id, boolean isQSL) { + this.db = db; + this.id = id; + this.isQSL = isQSL; + } + + @Override + protected Void doInBackground(Void... voids) { + db.execSQL("UPDATE QSLTable SET isQSL=? where id=?", new Object[]{isQSL ? "1" : "0", id}); + return null; + } + } + + + /** + * 查询全部通联成功的呼号,以通联时的频段为条件 + */ + static class LoadAllQSLCallsigns extends AsyncTask { + private final SQLiteDatabase db; + + public LoadAllQSLCallsigns(SQLiteDatabase db) { + this.db = db; + } + + @Override + protected Void doInBackground(Void... voids) { + GetAllQSLCallsign.get(db);//获取通联过的呼号 + return null; + } + } + + static class GetAllConfigParameter extends AsyncTask { + private final SQLiteDatabase db; + private OnAfterQueryConfig onAfterQueryConfig; + + public GetAllConfigParameter(SQLiteDatabase db, OnAfterQueryConfig onAfterQueryConfig) { + this.db = db; + this.onAfterQueryConfig = onAfterQueryConfig; + } + + @SuppressLint("Range") + private String getConfigByKey(String KeyName) { + String querySQL = "select keyName,Value from config where KeyName =?"; + Cursor cursor = db.rawQuery(querySQL, new String[]{KeyName}); + String result = ""; + if (cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex("Value")); + } + cursor.close(); + return result; + } + + @SuppressLint("Range") + @Override + protected Void doInBackground(Void... voids) { + + String querySQL = "select keyName,Value from config "; + Cursor cursor = db.rawQuery(querySQL, null); + while (cursor.moveToNext()) { + @SuppressLint("Range") + //String result = ""; + String result = cursor.getString(cursor.getColumnIndex("Value")); + String name = cursor.getString(cursor.getColumnIndex("KeyName")); + + if (name.equalsIgnoreCase("grid")) { + GeneralVariables.setMyMaidenheadGrid(result); + } + if (name.equalsIgnoreCase("callsign")) { + GeneralVariables.myCallsign = result; + String callsign = GeneralVariables.myCallsign; + if (callsign.length() > 0) { + Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); + Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); + Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); + } + } + if (name.equalsIgnoreCase("toModifier")) { + GeneralVariables.toModifier = result; + } + if (name.equalsIgnoreCase("freq")) { + float freq = 1000; + try { + freq = Float.parseFloat(result); + } catch (Exception e) { + Log.e(TAG, "doInBackground: " + e.getMessage()); + } + //GeneralVariables.setBaseFrequency(result.equals("") ? 1000 : Float.parseFloat(result)); + GeneralVariables.setBaseFrequency(freq); + } + if (name.equalsIgnoreCase("synFreq")) { + GeneralVariables.synFrequency = !(result.equals("") || result.equals("0")); + } + if (name.equalsIgnoreCase("transDelay")) { + if (result.matches("^\\d{1,4}$")) {//正则表达式,1-4位长度的数字 + GeneralVariables.transmitDelay = Integer.parseInt(result); + } else { + GeneralVariables.transmitDelay = FT8Common.FT8_TRANSMIT_DELAY; + } + } + + if (name.equalsIgnoreCase("civ")) { + GeneralVariables.civAddress = result.equals("") ? 0xa4 : Integer.parseInt(result, 16); + } + if (name.equalsIgnoreCase("baudRate")) { + GeneralVariables.baudRate = result.equals("") ? 19200 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("bandFreq")) { + GeneralVariables.band = result.equals("") ? 14074000 : Long.parseLong(result); + GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band); + } + if (name.equalsIgnoreCase("ctrMode")) { + GeneralVariables.controlMode = result.equals("") ? ControlMode.VOX : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("model")) {//电台型号 + GeneralVariables.modelNo = result.equals("") ? 0 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("instruction")) {//指令集 + GeneralVariables.instructionSet = result.equals("") ? 0 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("launchSupervision")) {//发射监管 + GeneralVariables.launchSupervision = result.equals("") ? + GeneralVariables.DEFAULT_LAUNCH_SUPERVISION : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("noReplyLimit")) {// + GeneralVariables.noReplyLimit = result.equals("") ? 0 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("autoFollowCQ")) {//自动关注CQ + GeneralVariables.autoFollowCQ = (result.equals("") || result.equals("1")); + } + if (name.equalsIgnoreCase("autoCallFollow")) {//自动呼叫关注 + GeneralVariables.autoCallFollow = (result.equals("") || result.equals("1")); + } + if (name.equalsIgnoreCase("pttDelay")) {//ptt延时设置 + GeneralVariables.pttDelay = result.equals("") ? 100 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("icomIp")) {//IcomIp地址 + GeneralVariables.icomIp = result.equals("") ? "255.255.255.255" : result; + } + if (name.equalsIgnoreCase("icomPort")) {//Icom端口 + GeneralVariables.icomUdpPort = result.equals("") ? 50001 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("icomUserName")) {//Icom用户名 + GeneralVariables.icomUserName = result.equals("") ? "ic705" : result; + } + if (name.equalsIgnoreCase("icomPassword")) {//Icom密码 + GeneralVariables.icomPassword = result; + } + if (name.equalsIgnoreCase("volumeValue")) {//输出音量大小 + GeneralVariables.volumePercent = result.equals("") ? 1.0f : Float.parseFloat(result) / 100f; + } + if (name.equalsIgnoreCase("excludedCallsigns")) {//排除的呼号 + GeneralVariables.addExcludedCallsigns(result); + } + if (name.equalsIgnoreCase("flexMaxRfPower")) {//指令集 + GeneralVariables.flexMaxRfPower = result.equals("") ? 10 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("flexMaxTunePower")) {//指令集 + GeneralVariables.flexMaxTunePower = result.equals("") ? 10 : Integer.parseInt(result); + } + if (name.equalsIgnoreCase("saveSWL")) {//保存解码信息 + GeneralVariables.saveSWLMessage = result.equals("1"); + } + if (name.equalsIgnoreCase("saveSWLQSO")) {//保存解码信息 + GeneralVariables.saveSWL_QSO = result.equals("1"); + } + if (name.equalsIgnoreCase("audioBits")) {//输出音频是否32位浮点 + GeneralVariables.audioOutput32Bit = result.equals("1"); + } + if (name.equalsIgnoreCase("audioRate")) {//输出音频是否32位浮点 + GeneralVariables.audioSampleRate =Integer.parseInt( result); + } + if (name.equalsIgnoreCase("deepMode")) {//是不是深度解码模式 + GeneralVariables.deepDecodeMode =result.equals("1"); + } + } + + cursor.close(); + + GetAllQSLCallsign.get(db);//获取通联过的呼号 + + if (onAfterQueryConfig != null) { + onAfterQueryConfig.doOnAfterQueryConfig(null, null); + } + + return null; + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java new file mode 100644 index 0000000..da3774d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java @@ -0,0 +1,49 @@ +package com.bg7yoz.ft8cn.database; +/** + * 与DXCC有关的3个表,dxccList、dxcc_grid、dxcc_prefix,用于保存各DXCC分区所在的国家、经纬度、网格等信息 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; + +public class DxccObject { + int id; + int dxcc; + String cc; + String ccc; + String name; + String continent; + String ituZone; + String cqZone; + int timeZone; + int cCode; + String aName; + String pp; + double lat; + double lon; + ArrayList grid=new ArrayList<>(); + ArrayList prefix=new ArrayList<>(); + + public void insertToDb(SQLiteDatabase db){ + String insertSQL="INSERT INTO dxccList (id,dxcc,cc,ccc,name,continent" + + ",ituzone,cqzone,timezone,ccode,aname,pp,lat,lon)"+ + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + db.execSQL(insertSQL,new Object[]{this.id,this.dxcc,this.cc,this.ccc,this.name,this.continent + ,this.ituZone,this.cqZone,this.timeZone,this.cCode,this.aName,this.pp,this.lat,this.lon}); + + String insertGrid="INSERT INTO dxcc_grid(dxcc,grid)VALUES(?,?)"; + for (int i = 0; i < this.grid.size(); i++) { + db.execSQL(insertGrid,new Object[]{this.dxcc,this.grid.get(i)}); + } + + String insert_Prefix="INSERT INTO dxcc_prefix(dxcc,prefix)VALUES(?,?)"; + for (int i = 0; i callsigns); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java new file mode 100644 index 0000000..7f4eb6e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.database; + +/** + * 保存配置信息的回调 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnAfterWriteConfig { + void doOnAfterWriteConfig(boolean writeDone); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java new file mode 100644 index 0000000..c779300 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.database; + +/** + * 查询呼号的回调 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnGetCallsign { + void doOnAfterGetCallSign(boolean exists); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java new file mode 100644 index 0000000..f2fc067 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java @@ -0,0 +1,158 @@ +package com.bg7yoz.ft8cn.database; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +/** + * 用于读取可用的载波波段列表,文件保存在assets/bands.txt中 + * @author BGY70Z + * @date 2023-03-20 + */ + +public class OperationBand { + private static final String TAG="OperationBand"; + private final Context context; + private static OperationBand operationBand = null; + + public static long getDefaultBand() { + return 14074000; + } + + public static String getDefaultWaveLength() { + return "20m"; + } + + public static ArrayList bandList = new ArrayList<>(); + public OperationBand(Context context) { + this.context = context; + //把波段数据导入到内存 + getBandsFromFile(); + } + + public static OperationBand getInstance(Context context) { + if (operationBand == null) { + operationBand=new OperationBand(context); + return operationBand; + } else { + return operationBand; + } + } + + /** + * 获取操作波段的数据,以列表的索引值查找,如果没有返回默认值14.074,20m + * @param index 索引 + * @return + */ + public Band getBandByIndex(int index){ + if (index==-1||index>=bandList.size()){ + return new Band(getDefaultBand(),getDefaultWaveLength()); + }else { + return bandList.get(index); + } + } + + /** + * 检查频率是不是在频率列表中,如果不在,把这个频率加到频段中 + * @param freq + * @return + */ + public static int getIndexByFreq(long freq){ + int result=-1; + for (int i = 0; i < bandList.size(); i++) { + if (bandList.get(i).band==freq){ + result=i; + break; + } + } + if (result==-1){ + bandList.add(new Band(freq, BaseRigOperation.getMeterFromFreq(freq))); + result=bandList.size()-1; + } + return result; + } + /** + * 从bands.txt文件中读出FT8信号列表。 + */ + public void getBandsFromFile(){ + AssetManager assetManager = context.getAssets(); + try { + bandList.clear(); + InputStream inputStream= assetManager.open("bands.txt"); + String[] st=getLinesFromInputStream(inputStream,"\n"); + for (int i = 0; i =bandList.size()){ + return bandList.get(0).getBandInfo(); + }else { + return bandList.get(index).getBandInfo(); + } + } + + /** + * 从InputStream中读出字符串 + * @param inputStream 输入流 + * @param deLimited 每行数据的分隔符。 + * @return String 返回字符串,如果失败,返回null + */ + public static String[] getLinesFromInputStream(InputStream inputStream, String deLimited) { + try { + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + return (new String(bytes)).split(deLimited); + }catch (IOException e){ + return null; + } + } + public static long getBandFreq(int index){ + if (index>bandList.size()){ + return 14074000; + } + return bandList.get(index).band; + } + + public static class Band { + public long band; + public String waveLength; + public boolean marked=false; + + public Band(long band, String waveLength) { + this.band = band; + this.waveLength = waveLength; + } + + public Band(String s) { + String[] info=s.split(":"); + marked= (info[0].equals("*")); + band=Long.parseLong(info[1]); + waveLength=info[info.length-1]; + } + @SuppressLint("DefaultLocale") + public String getBandInfo(){ + return String.format("%s %.3f MHz (%s)" + ,marked?"*":" " + ,(float)(band/1000000f) + ,waveLength); + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java new file mode 100644 index 0000000..019b0fa --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java @@ -0,0 +1,151 @@ +package com.bg7yoz.ft8cn.database; +/** + * 各电台信号的列表。文件在rigaddress.txt中 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +public class RigNameList { + private static final String TAG="RigNameList"; + private Context context; + private static RigNameList rigNameList = null; + + + public ArrayList rigList = new ArrayList<>(); + + public RigNameList(Context context) { + this.context = context; + //电台数据导入到内存 + getRigNamesFromFile(); + } + + public static RigNameList getInstance(Context context) { + if (rigNameList == null) { + return new RigNameList(context); + } else { + return rigNameList; + } + } + + /** + * 获取各电台参数数据,以列表的索引值查找,如果没有返回默认值以空 + * @param index 索引 + * @return 电台参数 + */ + public RigName getRigNameByIndex(int index){ + if (index==-1||index>=rigList.size()){ + return new RigName("",0xA4,19200,0); + }else { + return rigList.get(index); + } + } + + /** + * rigaddress.txt文件中读出各电台参数列表。 + */ + public void getRigNamesFromFile(){ + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream= assetManager.open("rigaddress.txt"); + String[] st=getLinesFromInputStream(inputStream,"\n"); + rigList.add(new RigName("",0xA4,19200,0)); + for (int i = 0; i { + private static final String TAG = "FlexMeters"; + public int sMeterId = -1; + public int tempCId = -1; + public int swrId = -1; + public int pwrId = -1; + public int alcId = -1; + + public FlexMeterInfos(String content) { + setMeterInfos(content); + } + + /** + * 根据电台回复的消息,获取每个METER的ID与meter定义的映射表 + * + * @param content 消息 + */ + public synchronized void setMeterInfos(String content) { + String[] temp; + if (content.length() == 0) return; + temp = content.substring(content.indexOf("meter ") + "meter ".length()).split("#"); + for (int i = 0; i < temp.length; i++) { + String[] val = temp[i].split("="); + if (val.length == 2) { + if (val[0].contains(".")) { + int index = Integer.parseInt(val[0].substring(0, val[0].indexOf("."))); + FlexMeterInfo meterInfo; + + meterInfo = this.get(index); + if (meterInfo == null) { + meterInfo = new FlexMeterInfo(); + this.put(index, meterInfo); + } + + + if (val[0].toLowerCase().contains(".src")) { + + meterInfo.src = val[1]; + } + if (val[0].toLowerCase().contains(".num")) { + meterInfo.num = val[1]; + } + if (val[0].toLowerCase().contains(".nam")) { + meterInfo.nam = val[1]; + //为了方便MeterList快速查询 + if (val[1].toUpperCase().contains("LEVEL")) { + sMeterId = index; + } else if (val[1].toUpperCase().contains("PATEMP")) { + tempCId = index; + } else if (val[1].toUpperCase().contains("SWR")) { + swrId = index; + } else if (val[1].toUpperCase().contains("FWDPWR")) { + pwrId = index; + } else if (val[1].toUpperCase().contains("ALC")) { + alcId = index; + } + + } + if (val[0].toLowerCase().contains(".low")) { + meterInfo.low = Float.parseFloat(val[1]); + } + if (val[0].toLowerCase().contains(".hi")) { + meterInfo.hi = Float.parseFloat(val[1]); + } + if (val[0].toLowerCase().contains(".desc")) { + meterInfo.desc = val[1]; + } + if (val[0].toLowerCase().contains(".unit")) { + String s = val[1].toUpperCase(); + if (s.contains("DB")) { + meterInfo.unit = FlexMeterType.dBm; + } else if (s.contains("SWR")) { + meterInfo.unit = FlexMeterType.swr; + } else if (s.contains("DEG")) { + meterInfo.unit = FlexMeterType.Temperature; + } else if (s.contains("VOLT")) { + meterInfo.unit = FlexMeterType.volt; + } else { + meterInfo.unit = FlexMeterType.other; + } + + } + if (val[0].toLowerCase().contains(".fps")) { + meterInfo.fps = val[1]; + } + if (val[0].toLowerCase().contains(".peak")) { + meterInfo.peak = val[1]; + } + } + } + } +// sMeterId=getMeterId("LEVEL"); +// tempCId=getMeterId("PATEMP"); +// swrId=getMeterId("SWR"); +// pwrId=getMeterId("FWDPWR"); +// alcId=getMeterId("ALC"); + } + + /** + * 查Meter得id,没有返回-1 + * + * @return id + */ + private int getMeterId(String s) { + for (int key : this.keySet()) { + if (get(key).nam.equalsIgnoreCase(s)) { + return key; + } + } + return -1; + } + + + public static class FlexMeterInfo { + public String src; + public String num; + public String nam; + public float low; + public float hi; + public String desc; + public FlexMeterType unit = FlexMeterType.other; + public String fps; + public String peak; + + @Override + public String toString() { + return "{" + + "src='" + src + '\'' + + ", num='" + num + '\'' + + ", nam='" + nam + '\'' + + ", low='" + low + '\'' + + ", hi='" + hi + '\'' + + ", desc='" + desc + '\'' + + ", unit='" + unit + '\'' + + ", fps='" + fps + '\'' + + ", peak='" + peak + '\'' + + '}'; + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java new file mode 100644 index 0000000..8e7cf3c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java @@ -0,0 +1,112 @@ +package com.bg7yoz.ft8cn.flex; +/** + * Meter的哈希表。Meter是2个32位的数,ID+VALUE。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; + +import java.util.HashMap; + +public class FlexMeterList extends HashMap { + public float sMeterVal=-150;//-150~10 + public float tempCVal=0;//0~100 + public float alcVal=-150;//-150~20 + public float swrVal=1;//1~999 + public float pwrVal=0;//0~100W + public synchronized void setMeters(byte[] data, FlexMeterInfos infos) { + for (int i = 0; i < data.length / 4; i++) { + int val = readShortData(data, i * 4); + FlexMeter meter = get(val); + if (meter == null) { + if (infos.get(val) == null) continue; + meter = new FlexMeter(); + meter.name = infos.get(val).nam; + meter.desc = infos.get(val).desc; + meter.type = infos.get(val).unit; + meter.id = val; + } + switch (meter.type) { + case dBm: + case swr: + meter.value = readShortData(data, i * 4 + 2) / 128f; + if (meter.name.contains("PWR")){//把dBm转换成功率值 + meter.value=(float) Math.pow(10,meter.value/10f)/1000f; + } + //节省资源,提前赋值 + if (meter.id==infos.sMeterId) sMeterVal=meter.value; + if (meter.id==infos.swrId) swrVal=meter.value; + if (meter.id==infos.pwrId) pwrVal=meter.value; + if (meter.id==infos.alcId) alcVal=meter.value; + break; + case volt: + meter.value = readShortData(data, i * 4 + 2) / 256f; + break; + case Temperature: + meter.value = readShortData(data, i * 4 + 2) / 64f; + //节省资源,提前赋值 + if (meter.id==infos.tempCId) tempCVal=meter.value; + break; + case other: + default: + meter.value = readShortData(data, i * 4 + 2); + } + + put(val, meter); + } + + } + + public synchronized String getMeters(){ + StringBuilder temp=new StringBuilder(); + int i=0; + for (int key:this.keySet()) { + i++; + temp.append(String.format("%-35s",get(key).toString())); + if (i%2==0){ + temp.append("\n"); + } + } + return temp.toString(); + } + + + + /** + * 把字节转换成short,不做小端转换!! + * + * @param data 字节数据 + * @return short + */ + public static short readShortData(byte[] data, int start) { + if (data.length - start < 2) return 0; + return (short) ((short) data[start + 1] & 0xff + | ((short) data[start] & 0xff) << 8); + } + + public static float readShortFloat(byte[] data, int start) { + if (data.length - start < 2) return 0.0f; + int accum = 0; + accum = accum | (data[start] & 0xff) << 0; + accum = accum | (data[start + 1] & 0xff) << 8; + return Float.intBitsToFloat(accum); + } + + + public static class FlexMeter { + public int id; + public float value; + public String name; + public String desc; + public FlexMeterType type=FlexMeterType.other; + + @SuppressLint("DefaultLocale") + @Override + public String toString() { + return String.format("%02d.%s : %.1f",id,name,value); + + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java new file mode 100644 index 0000000..164ecad --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.flex; + +/** + * meter的常用类型 + * @author BGY70Z + * @date 2023-03-20 + */ +public enum FlexMeterType { + dBm,swr,Temperature,volt,other +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java new file mode 100644 index 0000000..630b050 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java @@ -0,0 +1,1412 @@ +package com.bg7yoz.ft8cn.flex; +/** + * Flex的操作,命令使用TCP,数据流使用UDP。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.HashSet; + + +public class FlexRadio { + + public enum FlexMode {LSB, USB, AM, CW, DIGL, DIGU, SAM, FM, NFM, DFM, RTTY, RAW, ARQ, UNKNOW} + + public enum AntMode {ANT1, ANT2, RX_A, XVTA, UNKNOW} + + + private static final String TAG = "FlexRadio"; + public static int streamPort = 7051; + private int flexStreamPort = 4993; + public boolean isPttOn = false; + public long streamTxId = 0x084000000; + + public static int getStreamPort() {//获取用于流传输的UDP端口,防止重复,采用自增方式 + return ++streamPort; + } + + //private int streamPort;//当前用于流传输的UDP端口,这个是本实例的端口 + + + /********************* + * 电台的基本信息,从discovery协议中获取 + *************************/ + private String discovery_protocol_version;//=3.0.0.2 + private String model;//=FLEX-6400 + private String serial;//=1418-6579-6400-0461 + private String version;//=3.3.32.8203 + private String nickname;//=FlexRADIO + private String callsign;//=FlexRADIO + private String ip = "";//=192.168.3.86 + private int port = 4992;//=4992//用于控制电台的TCP端口 + private String status;//=Available + private String inUse_ip;//=192.168.3.5 + private String inUse_host;//=DESKTOP-RR564NK.local + private String max_licensed_version;//=v3 + private String radio_license_id;//=00-1C-2D-05-04-70 + private String requires_additional_license;//=0 + private String fpc_mac;//= + private int wan_connected;//=1 + private int licensed_clients;//=2 + private int available_clients;//=1 + private int max_panadapters;//=2 + private int available_panadapters;//=1 + private int max_slices;//=2 + private int available_slices;//=1 + private String gui_client_ips;//=192.168.3.5 + private String gui_client_hosts;//=DESKTOP-RR564NK.local + private String gui_client_programs;//=SmartSDR-Win + private String gui_client_stations;//=DESKTOP-RR564NK + private String gui_client_handles;//=0x19EAFA02 + + private long lastSeen;//最后一次消息的时间 + private boolean isAvailable = true;//电台是不是有效 + + + private int commandSeq = 1;//指令的序列 + private FlexCommand flexCommand; + private int handle = 0; + private String commandStr; + + + private final StringBuilder buffer = new StringBuilder();//指令的缓存 + private final RadioTcpClient tcpClient = new RadioTcpClient(); + private RadioUdpClient streamClient; + + private boolean allFlexRadioStatusEvent = false; + private String clientID = ""; + private long daxAudioStreamId = 0; + private long daxTxAudioStreamId = 0; + private long panadapterStreamId = 0; + private final HashSet streamIdSet = new HashSet<>(); + + //************************事件处理接口******************************* + private OnReceiveDataListener onReceiveDataListener;//当前接收到的数据事件 + private OnTcpConnectStatus onTcpConnectStatus;//当TCP连接状态变化的事件 + private OnReceiveStreamData onReceiveStreamData;//当接收到流数据后的处理事件 + private OnCommandListener onCommandListener;//触发命令事件 + private OnMessageListener onMessageListener;//触发消息事件 + private OnStatusListener onStatusListener;//触发状态事件 + //***************************************************************** + private AudioTrack audioTrack = null; + + public FlexRadio() { + updateLastSeen(); + } + + public FlexRadio(String discoverStr) { + update(discoverStr); + updateLastSeen(); + } + + public void updateLastSeen() { + this.lastSeen = System.currentTimeMillis(); + } + + /** + * 到参数列表中找指定的字符类型参数 + * + * @param parameters 参数列表 + * @param prefix 参数名前缀 + * @return 参数 + */ + private String getParameterStr(String[] parameters, String prefix) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { + return parameters[i].substring(prefix.length() + 1); + } + } + //如果没找到,返回空字符串 + return ""; + + } + + /** + * 到参数列表中找指定的int类型参数 + * + * @param parameters 参数列表 + * @param prefix 参数名前缀 + * @return 参数 + */ + private int getParameterInt(String[] parameters, String prefix) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { + try { + return Integer.parseInt(parameters[i].substring(prefix.length() + 1)); + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "getParameterInt exception: " + e.getMessage()); + return 0; + } + } + } + //如果没找到,返回0 + return 0; + + } + + /** + * 从discovery协议中更新参数 + * + * @param discoverStr 参数 + */ + public void update(String discoverStr) { + String[] paras = discoverStr.split(" "); + discovery_protocol_version = getParameterStr(paras, "discovery_protocol_version"); + model = getParameterStr(paras, "model"); + serial = getParameterStr(paras, "serial"); + version = getParameterStr(paras, "version"); + nickname = getParameterStr(paras, "nickname"); + callsign = getParameterStr(paras, "callsign"); + ip = getParameterStr(paras, "ip"); + port = getParameterInt(paras, "port"); + status = getParameterStr(paras, "status"); + inUse_ip = getParameterStr(paras, "inUse_ip"); + inUse_host = getParameterStr(paras, "inUse_host"); + max_licensed_version = getParameterStr(paras, "max_licensed_version"); + radio_license_id = getParameterStr(paras, "radio_license_id"); + requires_additional_license = getParameterStr(paras, "requires_additional_license"); + fpc_mac = getParameterStr(paras, "fpc_mac"); + wan_connected = getParameterInt(paras, "wan_connected"); + licensed_clients = getParameterInt(paras, "licensed_clients"); + available_clients = getParameterInt(paras, "available_clients"); + max_panadapters = getParameterInt(paras, "max_panadapters"); + available_panadapters = getParameterInt(paras, "available_panadapters"); + max_slices = getParameterInt(paras, "max_slices"); + available_slices = getParameterInt(paras, "available_slices"); + gui_client_ips = getParameterStr(paras, "gui_client_ips"); + gui_client_hosts = getParameterStr(paras, "gui_client_hosts"); + gui_client_programs = getParameterStr(paras, "gui_client_programs"); + gui_client_stations = getParameterStr(paras, "gui_client_stations"); + gui_client_handles = getParameterStr(paras, "gui_client_handles"); + } + + /** + * 检查这个实例是否是同一个电台 + * + * @param serialNum 电台序列号 + * @return 是/否 + */ + public boolean isEqual(String serialNum) { + return this.serial.equalsIgnoreCase(serialNum); + } + + + /** + * 连接到控制电台 + */ + public void connect() { + this.connect(this.ip, this.port); + } + + /** + * 连接控制到电台,TCP + * + * @param ip 地址 + * @param port 端口 + */ + public void connect(String ip, int port) { + if (tcpClient.isConnect()) { + tcpClient.disconnect(); + } + //Tcp连接触发的事件 + tcpClient.setOnDataReceiveListener(new RadioTcpClient.OnDataReceiveListener() { + @Override + public void onConnectSuccess() { + if (onTcpConnectStatus != null) { + onTcpConnectStatus.onConnectSuccess(tcpClient); + } + } + + @Override + public void onConnectFail() { + if (onTcpConnectStatus != null) { + onTcpConnectStatus.onConnectFail(tcpClient); + } + } + + @Override + public void onDataReceive(byte[] buffer) { + if (onReceiveDataListener != null) { + onReceiveDataListener.onDataReceive(buffer); + } + onReceiveData(buffer); + } + }); + clearBufferData();//清除一下缓存的指令数据 + tcpClient.connect(ip, port);//连接TCP + + //openStreamPort();//打开接收数据流的端口 + } + + /** + * 当接收到音频数据时的处理 + * + * @param data 音频数据 + */ + private void doReceiveAudio(byte[] data) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveAudio(data); + } + if (audioTrack != null) {//如果音频播放已经打开,就写音频流数据 + float[] sound = getFloatFromBytes(data); + audioTrack.write(sound, 0, sound.length, AudioTrack.WRITE_NON_BLOCKING); + } + } + + /** + * 当接收到IQ数据时的处理 + * + * @param data 数据 + */ + private void doReceiveIQ(byte[] data) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveIQ(data); + } + } + + /** + * 当接收到FFT数据时的处理 + * + * @param vita 数据 + */ + private void doReceiveFFT(VITA vita) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveFFT(vita); + } + } + + /** + * 当接收到仪表数据时的处理 + * + * @param vita 数据 + */ + private void doReceiveMeter(VITA vita) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveMeter(vita); + } + } + + /** + * 当接收到未知数据时的处理 + * + * @param data 数据 + */ + private void doReceiveUnKnow(byte[] data) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveUnKnow(data); + } + } + + /** + * 打开音频,流方式。当收到音频流的时候,播放数据 + */ + public void openAudio() { + AudioAttributes attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(); + AudioFormat myFormat = new AudioFormat.Builder().setSampleRate(24000) + .setEncoding(AudioFormat.ENCODING_PCM_FLOAT) + .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build(); + int mySession = 0; + audioTrack = new AudioTrack(attributes, myFormat + , 24000 * 4, AudioTrack.MODE_STREAM + , mySession); + audioTrack.play(); + } + + /** + * 关闭音频 + */ + public void closeAudio() { + if (audioTrack != null) { + audioTrack.stop(); + //audioTrack.release(); + audioTrack = null; + } + } + + private synchronized void addStreamIdToSet(long streamId) { + streamIdSet.add(streamId); + } + + /** + * 打开接收数据流的端口 + */ + public void openStreamPort() { + if (streamClient != null) { + if (streamClient.isActivated()) { + try { + streamClient.setActivated(false); + } catch (Exception e) { + e.printStackTrace(); + } + + } + } + + + RadioUdpClient.OnUdpEvents onUdpEvents = new RadioUdpClient.OnUdpEvents() { + @Override + public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { + if (flexStreamPort != packet.getPort()) flexStreamPort = packet.getPort(); + + VITA vita = new VITA(data); + addStreamIdToSet(vita.streamId); + + //Log.e(TAG, String.format("OnReceiveData: stream id:0x%x,class id:0x%x",vita.streamId,vita.classId) ); + switch (vita.classId) { + case VITA.FLEX_DAX_AUDIO_CLASS_ID://音频数据 + //Log.e(TAG, String.format("FLEX_DAX_AUDIO_CLASS_ID stream id:0x%x",vita.streamId )); + doReceiveAudio(vita.payload); + break; + case VITA.FLEX_DAX_IQ_CLASS_ID://IQ数据 + doReceiveIQ(vita.payload); + break; + case VITA.FLEX_FFT_CLASS_ID://频谱数据 + doReceiveFFT(vita); + //Log.e(TAG, String.format("OnReceiveData: FFT:%d,STREAM ID:0x%x",vita.payload.length,vita.streamId)); + break; + case VITA.FLEX_METER_CLASS_ID://仪表数据 + //Log.e(TAG, String.format("FLEX_METER_CLASS_ID: stream id:0x%x",vita.streamId )); + doReceiveMeter(vita); + //Log.e(TAG, String.format("OnReceiveData: METER class id:0x%x,stream id:0x%x,length:%d\n%s" + // ,vita.classId,vita.streamId,vita.payload.length,vita.showPayload() )); + break; + default://未知类型的数据 + doReceiveUnKnow(data); + break; + } + } + }; + + //此处要确定stream的udp端口 + streamPort = getStreamPort(); + streamClient = new RadioUdpClient(streamPort); + streamClient.setOnUdpEvents(onUdpEvents); + try { + streamClient.setActivated(true); + } catch (SocketException e) { + e.printStackTrace(); + Log.d(TAG, "onCreate: " + e.getMessage()); + } + + + } + + /** + * 关闭接收数据流的端口 + */ + public synchronized void closeStreamPort() { + if (streamClient != null) { + if (streamClient.isActivated()) { + try { + streamClient.setActivated(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + streamClient = null; + } + + /** + * 断开与电台的连接 + */ + public synchronized void disConnect() { + if (tcpClient.isConnect()) { + tcpClient.disconnect(); + } + } + + /** + * flexRadio要把12000采样率改为24000采样率,还要把单声道改为立体声 + * @param data 音频 + */ + public void sendWaveData(float[] data) { + float[] temp = new float[data.length * 2]; + for (int i = 0; i < data.length; i++) {//转成立体声,24000采样率 + temp[i * 2] = data[i]; + temp[i * 2 + 1] = data[i]; + } + //port=4991; + //streamTxId=0x084000001; + //每5毫秒一个包?立体声,共256个float + Log.e(TAG, String.format("sendWaveData: streamid:0x%x,ip:%s,port:%d",streamTxId,ip, port) ); + new Thread(new Runnable() { + @Override + public void run() { + + VITA vita = new VITA(); + + int count = 0; + int packetCount=0; + while (count temp.length) break; + } + + byte[] send = vita.audioDataToVita(packetCount, streamTxId, voice); + packetCount++; + try { + streamClient.sendData(send, ip, port); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + if (count>temp.length) break; + //} + while (isPttOn) { + if (System.currentTimeMillis() - now >= 5) {//5毫秒一个周期,每个周期256个float。 + break; + } + } + if (!isPttOn){ + // Log.e(TAG, String.format("count:%d,temp.length:%d",count,temp.length )); + } + + } + + +// for (int i = 0; i < (temp.length / (24 * 2 * 40)); i++) {//40毫秒的数据量 +// if (!isPttOn) return; +// long now = System.currentTimeMillis() - 1;//获取当前时间 +// +// float[] voice = new float[24 * 2 * 10]; +// for (int j = 0; j < 24 * 2 *10; j++) { +// voice[j] = temp[i * 24 * 2 * 10 + j]; +// } +// //Log.e(TAG, "sendWaveData: "+floatToStr(voice) ); +// //streamTxId=0x84000001; +// byte[] send = vita.audioDataToVita(count, streamTxId, voice); +// count++; +// +// try { +// streamClient.sendData(send, ip, port); +// } catch (UnknownHostException e) { +// throw new RuntimeException(e); +// } +// +// while (isPttOn) { +// if (System.currentTimeMillis() - now >= 41) {//40毫秒一个周期,每个周期3个包,每个包64个float。 +// break; +// } +// } +// } + } + }).start(); + + + //设置发送音频包 + //streamClient.sendData(); + } + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%02x ", data[i] & 0xff)); + } + return s.toString(); + } + @SuppressLint("DefaultLocale") + public static String floatToStr(float[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%f ", data[i])); + } + return s.toString(); + } + /** + * 电台是否连接 + * + * @return 是否 + */ + public boolean isConnect() { + return tcpClient.isConnect(); + } + + public synchronized void sendData(byte[] data) { + tcpClient.sendByte(data); + } + + /** + * 制作命令,命令序号规则:后3位是命令的种类,序号除1000,是命令的真正序号 + * + * @param command 命令的种类 + * @param cmdContent 命令的具体内容 + */ + @SuppressLint("DefaultLocale") + public void sendCommand(FlexCommand command, String cmdContent) { + if (tcpClient.isConnect()) { + commandSeq++; + flexCommand = command; + commandStr = String.format("C%d%03d|%s\n", commandSeq, command.ordinal() + , cmdContent); + tcpClient.sendByte(commandStr.getBytes()); + Log.e(TAG, "sendCommand: " + commandStr); + } + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + /** + * 当接收到数据时触发的事件,此处是TCP连接得到的数据 + * + * @param data 数据 + */ + private void onReceiveData(byte[] data) { + String s = new String(data); + if (!s.contains("\n")) {//不包含换行符,说明命令行没有接受完。 + buffer.append(s); + } else {//说明已经有命令行了。可能不止一个哦。在此部分要触发OnReceiveLine + String[] commands = s.split("\n"); + if (commands.length > 0) {//把收到数据的第一行,追加到之前接收的命令数据上 + buffer.append(commands[0]); + } + + //先把缓存中的数据触发出来 + doReceiveLineEvent(buffer.toString()); + clearBufferData(); + //从第二行开始触发,最后一行不触发,最后一行要看是不是换行结尾 + for (int i = 1; i < commands.length - 1; i++) { + doReceiveLineEvent(commands[i]); + } + + if (commands.length > 1) {//当数据是多行的时候,最后一行的处理 + if (s.endsWith("\n")) {//如果是以换行结尾,或者缓冲区没满(接收完全了),就触发事件 + doReceiveLineEvent(commands[commands.length - 1]); + } else {//如果不是以换行结尾,说明指令没有接收完全 + buffer.append(commands[commands.length - 1]); + } + } + } + } + + /** + * 当接收到数据行时,触发的事件。可以触发两种事件: + * 1.行数据事件onReceiveLineListener; + * 2.命令事件onCommandListener。 + * + * @param line 数据行 + */ + private void doReceiveLineEvent(String line) { + + FlexResponse response = new FlexResponse(line); + //更新一下句柄 + switch (response.responseStyle) { + case VERSION: + this.version = response.version; + break; + case HANDLE: + this.handle = response.handle; + break; + case RESPONSE: + if (response.daxStreamId != 0) { + this.daxAudioStreamId = response.daxStreamId; + } + if (response.panadapterStreamId != 0) { + this.panadapterStreamId = response.panadapterStreamId; + } + if (response.daxTxStreamId != 0) { + this.daxTxAudioStreamId = response.daxTxStreamId; + Log.e(TAG, String.format("doReceiveLineEvent: txStreamID:0x%x", daxTxAudioStreamId)); + } + + break; + } + + if (response.responseStyle == FlexResponseStyle.RESPONSE) { + if (getCommandStyleFromResponse(response) == FlexCommand.CLIENT_GUI) { + setClientIDFromResponse(response);//设置CLIENT ID + } + } + + //是不是显示其它终端的状态信息 + if (response.responseStyle == FlexResponseStyle.STATUS) { + if (!allFlexRadioStatusEvent && (!(handle == response.handle || response.handle == 0))) { + return; + } + + } + + switch (response.responseStyle) { + case RESPONSE://当接收到的是指令的返回消息 + doCommandResponse(response);//对一些指令返回的消息要处理一下。 + break; + case STATUS://当接收到的是状态消息 + if (onStatusListener != null) { + onStatusListener.onStatus(response); + } + break; + case MESSAGE://当接收到的是消息 + if (onMessageListener != null) { + onMessageListener.onMessage(response); + break; + } + } + } + + /** + * 处理命令返回的消息,同时触发命令返回消息事件 + * + * @param response 返回消息 + */ + private void doCommandResponse(FlexResponse response) { + if (onCommandListener != null) { + onCommandListener.onResponse(response); + } + } + + + private void setClientIDFromResponse(FlexResponse response) { + if (response.responseStyle != FlexResponseStyle.RESPONSE) return; + if (getCommandStyleFromResponse(response) != FlexCommand.CLIENT_GUI) return; + if (response.content.equals("0")) {//R3001|0|0BF06C76-EB9E-47E0-B570-EAFB7D556055 + String[] temp = response.rawData.split("\\|"); + if (temp.length < 3) return; + clientID = temp[2]; + } + } + + public FlexCommand getCommandStyleFromResponse(FlexResponse response) { + if (response.responseStyle != FlexResponseStyle.RESPONSE) { + return FlexCommand.UNKNOW; + } + //Log.e(TAG, "getCommandStyleFromResponse: "+response.rawData ); + + try { + return FlexCommand.values()[Integer.parseInt(response.head.substring(response.head.length() - 3))]; + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "getCommandStyleFromResponse exception: " + e.getMessage()); + } + return FlexCommand.UNKNOW; + } + + /** + * 检查是不是 刚刚 离线,离线条件:5秒内没有收到电台的广播数据包 + * + * @return 是否 + */ + public boolean isInvalidNow() { + if (isAvailable) {//如果标记在线,而大于5秒的时间没有收到数据包,就视为刚刚离线。 + isAvailable = System.currentTimeMillis() - lastSeen < 1000 * 5;//小于5秒,就视为在线 + return !isAvailable; + } else {//如果已经标记不在线了,就不是刚刚离弦的。 + return false; + } + } + + @NonNull + @Override + public String toString() { + return String.format("FlexRadio{version='%s', handle=%X}", version, handle); + } + + //**************封装FlexRadio各种指令*开始*********************** + public synchronized void commandClientDisconnect() { + sendCommand(FlexCommand.CLIENT_DISCONNECT, "client disconnect"); + } + + public synchronized void commandClientGui() { + sendCommand(FlexCommand.CLIENT_GUI, "client gui"); + } + + public synchronized void commandClientSetEnforceNetWorkGui() { + sendCommand(FlexCommand.CLIENT_SET_ENFORCE_NETWORK + , "client set enforce_network_mtu=1 network_mtu=1450"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceRemove(int sliceOder) { + sendCommand(FlexCommand.SLICE_REMOVE, String.format("slice r %d", sliceOder)); + } + + public synchronized void commandSliceList() { + sendCommand(FlexCommand.SLICE_LIST, "slice list"); + } + + public synchronized void commandSliceCreate() { + sendCommand(FlexCommand.SLICE_CREATE_FREQ, "slice create"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceTune(int sliceOder, String freq) { + sendCommand(FlexCommand.SLICE_TUNE, String.format("slice t %d %s", sliceOder, freq)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetRxAnt(int sliceOder, AntMode antMode) { + sendCommand(FlexCommand.SLICE_SET_RX_ANT, String.format("slice s %d rxant=%s", sliceOder, antMode.toString())); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetTxAnt(int sliceOder, AntMode antMode) { + sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d txant=%s", sliceOder, antMode.toString())); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetMode(int sliceOder, FlexMode mode) { + sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d mode=%s", sliceOder, mode.toString())); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetNR(int sliceOder, boolean on) { + sendCommand(FlexCommand.SLICE_SET_NR, String.format("slice s %d nr=%s", sliceOder, on ? "on" : "off")); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceGetError(int sliceOder) { + sendCommand(FlexCommand.SLICE_GET_ERROR, String.format("slice get_error %d", sliceOder)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetNB(int sliceOder, boolean on) { + sendCommand(FlexCommand.SLICE_SET_NB, String.format("slice s %d nb=%s", sliceOder, on ? "on" : "off")); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetDaxAudio(int channel, int sliceOder, boolean txEnable) { + sendCommand(FlexCommand.DAX_AUDIO, String.format("dax audio set %d slice=%d tx=%s", channel, sliceOder, txEnable ? "1" : "0")); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetDaxIQ(int channel, int panadapter, int rate) { + sendCommand(FlexCommand.DAX_IQ, String.format("dax iq set %d pan=%d rat=%d", channel, panadapter, rate)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandUdpPort() { + sendCommand(FlexCommand.CLIENT_UDPPORT, String.format("client udpport %d", streamPort)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandStreamCreateDaxRx(int channel) { + sendCommand(FlexCommand.STREAM_CREATE_DAX_RX, String.format("stream create type=dax_rx dax_channel=%d", channel)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandStreamCreateDaxTx(int channel) { + //sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx dax_channel=%d", channel)); +// sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx compression=none")); + sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=remote_audio_tx")); + } + + public synchronized void commandRemoveDaxStream() { + sendCommand(FlexCommand.STREAM_REMOVE, String.format("stream remove 0x%x", getDaxAudioStreamId())); + } + + public synchronized void commandRemoveAllStream() { + for (Long id : streamIdSet) { + sendCommand(FlexCommand.STREAM_REMOVE, String.format("stream remove 0x%x", id)); + } + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetFilter(int sliceOrder, int filt_low, int filt_high) { + sendCommand(FlexCommand.FILT_SET, String.format("filt %d %d %d", sliceOrder, filt_low, filt_high)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandStartATU() { + sendCommand(FlexCommand.FILT_SET, "atu start"); + } + + public synchronized void commandGetInfo() { + sendCommand(FlexCommand.INFO, "info"); + } + + public synchronized void commandPanadapterCreate() { + sendCommand(FlexCommand.PANADAPTER_CREATE, "display pan c freq=9.5 ant=ANT1 x=800 y=400"); + } + + public synchronized void commandPanadapterRemove() { + sendCommand(FlexCommand.PANADAPTER_REMOVE, String.format("display pan r 0x%x", panadapterStreamId)); + //sendCommand(FlexCommand.PANADAPTER_REMOVE,"display pan r 0x40000001"); + //sendCommand(FlexCommand.PANADAPTER_REMOVE,"display pan r 0x40000000"); + } + + public synchronized void commandMeterCreateAmp() { + sendCommand(FlexCommand.METER_CREATE_AMP, "meter create name=AFRAMP type=AMP min=-150.0 max=20.0 units=AMPS"); + } + + public synchronized void commandMeterList() { + sendCommand(FlexCommand.METER_LIST, "meter list"); + } + + public synchronized void commandSubClientAll() { + sendCommand(FlexCommand.SUB_CLIENT_ALL, "sub client all"); + } + + public synchronized void commandSubTxAll() { + sendCommand(FlexCommand.SUB_TX_ALL, "sub client all"); + } + + public synchronized void commandSubAtuAll() { + sendCommand(FlexCommand.SUB_ATU_ALL, "sub atu all"); + } + + public synchronized void commandSubAmplifierAtuAll() { + sendCommand(FlexCommand.SUB_amplifier_ALL, "sub amplifier all"); + } + + public synchronized void commandSubMeterAll() { + sendCommand(FlexCommand.SUB_METER_ALL, "sub meter all"); + //sendCommand(FlexCommand.SUB_METER_ALL,"sub meter 15"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSubMeterById(int id) { + sendCommand(FlexCommand.SUB_METER_ID, String.format("sub meter %d", id)); + } + + public synchronized void commandSubPanAll() { + sendCommand(FlexCommand.SUB_PAN_ALL, "sub pan all"); + } + + public synchronized void commandSubSliceAll() { + sendCommand(FlexCommand.SUB_METER_ALL, "sub slice all"); + } + + public synchronized void commandSubAudioStreamAll() { + sendCommand(FlexCommand.SUB_AUDIO_STREAM_ALL, "sub audio_stream all"); + } + + public synchronized void commandSubDaxIqAll() { + sendCommand(FlexCommand.SUB_DAX_IQ_ALL, "sub daxiq all"); + } + + public synchronized void commandSubDaxAll() { + sendCommand(FlexCommand.SUB_DAX_ALL, "sub dax all"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetRfPower(int power) { + sendCommand(FlexCommand.TRANSMIT_MAX_POWER, String.format("transmit set max_power_level=%d", power)); + sendCommand(FlexCommand.TRANSMIT_POWER, String.format("transmit set rfpower=%d", power)); + //sendCommand(FlexCommand.TRANSMIT_MAX_POWER,"info"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetTunePower(int power) { + sendCommand(FlexCommand.AUT_TUNE_MAX_POWER, String.format("transmit set tunepower=%d", power)); + } + + public synchronized void commandPTTOnOff(boolean on) { + if (on) { + sendCommand(FlexCommand.PTT_ON, "xmit 1"); + } else { + sendCommand(FlexCommand.PTT_ON, "xmit 0"); + } + } + + public synchronized void commandTuneTransmitOnOff(boolean on) { + if (on) { + sendCommand(FlexCommand.PTT_ON, "transmit tune on"); + } else { + sendCommand(FlexCommand.PTT_ON, "transmit tune off"); + } + } + + + @SuppressLint("DefaultLocale") + public synchronized void commandDisplayPan(int x, int y) { + sendCommand(FlexCommand.DISPLAY_PAN, String.format("display pan set 0x%X xpixels=%d", 0x40000000, x)); + sendCommand(FlexCommand.DISPLAY_PAN, String.format("display pan set 0x%X ypixels=%d", 0x40000000, y)); + } + //**************封装FlexRadio各种指令*结束*********************** + + @Override + protected void finalize() throws Throwable { + closeStreamPort(); + super.finalize(); + } + + + //**************各种接口********************** + + /** + * 当TCP接收到数据 + */ + public interface OnReceiveDataListener { + void onDataReceive(byte[] data); + } + + /** + * 当接收到指令回复 + */ + public interface OnCommandListener { + void onResponse(FlexResponse response); + } + + public interface OnStatusListener { + void onStatus(FlexResponse response); + } + + public interface OnMessageListener { + void onMessage(FlexResponse response); + } + + /** + * 当TCP连接状态变化 + */ + public interface OnTcpConnectStatus { + void onConnectSuccess(RadioTcpClient tcpClient); + + void onConnectFail(RadioTcpClient tcpClient); + } + + /** + * 当接收到流数据时的事件 + */ + public interface OnReceiveStreamData { + void onReceiveAudio(byte[] data);//音频数据 + + void onReceiveIQ(byte[] data);//IQ数据 + + void onReceiveFFT(VITA vita);//频谱数据 + + void onReceiveMeter(VITA vita);//仪表数据 + + void onReceiveUnKnow(byte[] data);//未知数据 + } + //******************************************* + + + /** + * 电台TCP回复数据的基础类 + */ + public static class FlexResponse { + private static final String TAG = "FlexResponse"; + public FlexResponseStyle responseStyle; + public String head;//消息头 + public String content;//消息内容 + public String exContent;//扩展潇潇兮,有的返回消息分为3段,取第3段消息 + public String rawData;//原始数据 + public int seq_number;//32位int,指令序号 + public int handle;//句柄,32位,16进制 + public String version;//版本信息 + public int message_num;//消息号,32位,16进制。其中位24-25包含消息的严重性(0=信息,1=警告,2=错误,3=致命错误) + public long daxStreamId = 0; + public long daxTxStreamId = 0; + public long panadapterStreamId = 0; + public FlexCommand flexCommand = FlexCommand.UNKNOW; + public long resultValue = 0; + + public FlexResponse(String line) { + //Log.e(TAG, "FlexResponse: line--->"+line ); + rawData = line; + char header; + if (line.length() > 0) { + header = line.toUpperCase().charAt(0); + } else { + header = 0; + } + switch (header) { + case 'S': + responseStyle = FlexResponseStyle.STATUS; + getHeadAndContent(line, "\\|"); + try { + this.handle = Integer.parseInt(head.substring(1), 16);//解析16进制 + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "FlexResponse status handle exception: " + e.getMessage()); + } + break; + case 'R': + responseStyle = FlexResponseStyle.RESPONSE; + getHeadAndContent(line, "\\|"); + try { + seq_number = Integer.parseInt(head.substring(1));//解析指令序号 + flexCommand = FlexCommand.values()[seq_number % 1000]; + switch (flexCommand) { + case STREAM_CREATE_DAX_RX: + this.daxStreamId = getStreamId(line); + break; + case PANADAPTER_CREATE: + this.panadapterStreamId = getStreamId(line); + break; + case STREAM_CREATE_DAX_TX: + this.daxTxStreamId = getStreamId(line); + break; + } + resultValue = Integer.parseInt(content, 16);//取命令的返回值 + + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "FlexResponse parseInt seq_number exception: " + e.getMessage()); + } + break; + case 'H': + responseStyle = FlexResponseStyle.HANDLE; + head = line; + content = line; + Log.e(TAG, "FlexResponse: handle:" + line.substring(1)); + try { + this.handle = Integer.parseInt(line.substring(1), 16);//解析16进制 + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "FlexResponse parseInt handle exception: " + e.getMessage()); + } + + break; + case 'V': + responseStyle = FlexResponseStyle.VERSION; + head = line; + content = line; + this.version = line.substring(1); + break; + case 'M': + responseStyle = FlexResponseStyle.MESSAGE; + getHeadAndContent(line, "\\|"); + try { + //Log.e(TAG, "FlexResponse: "+line ); + this.message_num = Integer.parseInt(head.substring(2), 16);//消息号,32位,16进制 + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "FlexResponse parseInt message_num exception: " + e.getMessage()); + } + + break; + case 'C': + responseStyle = FlexResponseStyle.COMMAND; + getHeadAndContent(line, "\\|"); + int index = 1; + if (head.length() > 2) { + if (head.toUpperCase().charAt(1) == 'D') index = 2; + } + try { + seq_number = Integer.parseInt(head.substring(index)); + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "FlexResponse parseInt seq_number exception: " + e.getMessage()); + } + + break; + case 0: + default: + responseStyle = FlexResponseStyle.UNKNOW; + break; + } + } + + private long getStreamId(String line) { + String[] lines = line.split("\\|"); + if (lines.length > 2) { + if (lines[1].equals("0")) { + try { + return Long.parseLong(lines[2], 16);//stream id,16进制 + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "getDaxStreamId exception: " + e.getMessage()); + } + } + } + return 0; + } + + /** + * 分割消息的头和内容,并分别负值给head和content + * + * @param line 消息 + * @param split 分隔符 + */ + private void getHeadAndContent(String line, String split) { + String[] temp = line.split(split); + if (line.length() > 1) { + head = temp[0]; + content = temp[1]; + } else { + head = ""; + content = ""; + + } + + if (temp.length > 2) { + exContent = temp[2]; + } else { + exContent = ""; + } + + } + + public String resultStatus() { + if (resultValue == 0) { + return String.format(GeneralVariables.getStringFromResource( + R.string.instruction_success), flexCommand.toString()); + } else { + return String.format(GeneralVariables.getStringFromResource( + R.string.instruction_failed), flexCommand.toString(), rawData); + } + } + + } + + + // ********事件的Getter和Setter********* + + public OnReceiveDataListener getOnReceiveDataListener() { + return onReceiveDataListener; + } + + public void setOnReceiveDataListener(OnReceiveDataListener onReceiveDataListener) { + this.onReceiveDataListener = onReceiveDataListener; + } + + public OnCommandListener getOnCommandListener() { + return onCommandListener; + } + + public void setOnCommandListener(OnCommandListener onCommandListener) { + this.onCommandListener = onCommandListener; + } + + public RadioTcpClient getTcpClient() { + return tcpClient; + } + + public String getVersion() { + return version; + } + + public int getHandle() { + return handle; + } + + public String getHandleStr() { + return String.format("%X", handle); + } + + public void setHandle(int handle) { + this.handle = handle; + } + + public boolean isAllFlexRadioStatusEvent() { + return allFlexRadioStatusEvent; + } + + public void setAllFlexRadioStatusEvent(boolean allFlexRadioStatusEvent) { + this.allFlexRadioStatusEvent = allFlexRadioStatusEvent; + } + + public String getClientID() { + return clientID; + } + + public void setClientID(String clientID) { + clientID = clientID; + } + + public int getCommandSeq() { + return commandSeq * 1000 + flexCommand.ordinal(); + } + + public String getCommandStr() { + return commandStr; + } + + public long getDaxAudioStreamId() { + return daxAudioStreamId; + } + + public String getDiscovery_protocol_version() { + return discovery_protocol_version; + } + + public String getModel() { + return model; + } + + public String getSerial() { + return serial; + } + + public String getNickname() { + return nickname; + } + + public String getCallsign() { + return callsign; + } + + public String getIp() { + return ip; + } + + public int getPort() { + return port; + } + + public String getStatus() { + return status; + } + + public String getInUse_ip() { + return inUse_ip; + } + + public String getInUse_host() { + return inUse_host; + } + + public String getMax_licensed_version() { + return max_licensed_version; + } + + public String getRadio_license_id() { + return radio_license_id; + } + + public String getRequires_additional_license() { + return requires_additional_license; + } + + public String getFpc_mac() { + return fpc_mac; + } + + public int getWan_connected() { + return wan_connected; + } + + public int getLicensed_clients() { + return licensed_clients; + } + + public int getAvailable_clients() { + return available_clients; + } + + public int getMax_panadapters() { + return max_panadapters; + } + + public int getAvailable_panadapters() { + return available_panadapters; + } + + public int getMax_slices() { + return max_slices; + } + + public int getAvailable_slices() { + return available_slices; + } + + public String getGui_client_ips() { + return gui_client_ips; + } + + public String getGui_client_hosts() { + return gui_client_hosts; + } + + public String getGui_client_programs() { + return gui_client_programs; + } + + public String getGui_client_stations() { + return gui_client_stations; + } + + public String getGui_client_handles() { + return gui_client_handles; + } + + public boolean isAvailable() { + return isAvailable; + } + + public OnTcpConnectStatus getOnTcpConnectStatus() { + return onTcpConnectStatus; + } + + public void setOnTcpConnectStatus(OnTcpConnectStatus onTcpConnectStatus) { + this.onTcpConnectStatus = onTcpConnectStatus; + } + + public OnReceiveStreamData getOnReceiveStreamData() { + return onReceiveStreamData; + } + + public void setOnReceiveStreamData(OnReceiveStreamData onReceiveStreamData) { + this.onReceiveStreamData = onReceiveStreamData; + } + + public OnStatusListener getOnStatusListener() { + return onStatusListener; + } + + public void setOnStatusListener(OnStatusListener onStatusListener) { + this.onStatusListener = onStatusListener; + } + + public RadioUdpClient getStreamClient() { + return streamClient; + } + + public AudioTrack getAudioTrack() { + return audioTrack; + } + + + public static float[] getFloatFromBytes(byte[] bytes) { + float[] floats = new float[bytes.length / 4]; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + for (int i = 0; i < floats.length; i++) { + try { + floats[i] = dis.readFloat(); + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "getFloat: ------>>" + e.getMessage()); + break; + } + } + try { + dis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return floats; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public void setModel(String model) { + this.model = model; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java new file mode 100644 index 0000000..c89d532 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java @@ -0,0 +1,185 @@ +package com.bg7yoz.ft8cn.flex; + +import android.util.Log; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + +// VITA 形成的发现消息解析器的枚举定义 +enum VitaTokens { + nullToken , + ipToken, + portToken, + modelToken, + serialToken, + callsignToken, + nameToken, + dpVersionToken, + versionToken, + statusToken, +}; +/** + * RadioFactory 当前发现的所有收音机。 + * RadioFactory: 实例化这个类来创建一个 Radio Factory,它将为网络上发现的无线电维护FlexRadio列表flexRadios。 + * + * 通过Upd协议,在4992端口的广播数据中获取vita协议数据,并解析出序列号,用于更新电台列表flexRadios。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class FlexRadioFactory { + private static final String TAG="FlexRadioFactory"; + private static final int FLEX_DISCOVERY_PORT =4992; + private static FlexRadioFactory instance=null; + private final RadioUdpClient broadcastClient ; + private OnFlexRadioEvents onFlexRadioEvents; + + private Timer refreshTimer=null; + private TimerTask refreshTask=null; + + public ArrayList flexRadios=new ArrayList<>(); + + /** + * 获取电台列表实例 + * @return 电台列表实例 + */ + public static FlexRadioFactory getInstance(){ + if (instance==null){ + instance= new FlexRadioFactory(); + } + return instance; + } + + + + public FlexRadioFactory() { + broadcastClient = new RadioUdpClient(FLEX_DISCOVERY_PORT); + + broadcastClient.setOnUdpEvents(new RadioUdpClient.OnUdpEvents() { + @Override + public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { + VITA vita = new VITA(data); + if (vita.isAvailable//如果数据包有效,且classId=0x534CFFFF,StreamId=0x800,更新电台列表 + &&vita.informationClassCode==VITA.FLEX_CLASS_ID + &&vita.packetClassCode==VITA.VS_Discovery + &&vita.streamId==VITA.FLEX_Discovery_stream_ID){ + updateFlexRadioList(new String(vita.payload)); + } + } + }); + try { + broadcastClient.setActivated(true); + } catch (SocketException e) { + e.printStackTrace(); + Log.e(TAG, "FlexRadioFactory: "+e.getMessage()); + } + + } + + + public void startRefreshTimer(){ + if (refreshTimer==null) { + refreshTask=new TimerTask() { + @Override + public void run() { + Log.e(TAG, "run: 检查离线" ); + checkOffLineRadios(); + } + }; + refreshTimer=new Timer(); + refreshTimer.schedule(refreshTask, 1000, 1000);//检查电台列表中的电台是否在线(每一秒) + } + } + public void cancelRefreshTimer(){ + if (refreshTimer!=null){ + refreshTimer.cancel(); + refreshTimer=null; + refreshTask.cancel(); + refreshTask=null; + } + } + + /** + * 从数据中查找电台的序列号 + * @param s 数据 + * @return 序列号 + */ + private String getSerialNum(String s){ + String[] strings=s.split(" "); + for (int i = 0; i > 28) +public static intVH_C(x) ((x & 0x08000000) >> 26) +public static intVH_T(x) ((x & 0x04000000) >> 25) +public static intVH_TSI(x) ((x & 0x00c00000) >> 21) +public static intVH_TSF(x) ((x & 0x00300000) >> 19) +public static intVH_PKT_CNT(x) ((x & 0x000f0000) >> 16) +public static intVH_PKT_SIZE(x) (x & 0x0000ffff) + */ + +// Enumerates for field values + +import android.annotation.SuppressLint; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.text.SimpleDateFormat; +import java.util.Date; + +enum VitaPacketType { + IF_DATA,//IF Data packet without Stream Identifier + IF_DATA_WITH_STREAM,//IF Data packet with Stream Identifier + EXT_DATA,//Extension Data packet without Stream Identifier + EXT_DATA_WITH_STREAM,//Extension Data packet with Stream Identifier + IF_CONTEXT,//IF Context packet(see Section 7) + EXT_CONTEXT//Extension Context packet(see Section 7); +}; + +//时间戳的类型 +//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, +//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 +//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, +//所有的时间带来都是在以一个采样数据为该reference-point 时间 +enum VitaTSI { + TSI_NONE,//No Integer-seconds Timestamp field included + TSI_UTC,//Coordinated Universal Time(UTC) + TSI_GPS,//GPS time + TSI_OTHER//Other +}; + +//时间戳小数部分类型 +//小数部分主要有三种: +// 一种是sample-count ,以采样周期为最小分辨率, +// 一种是real-time以ps为最小单位, +// 第三种是以任意选择的时间进行累加得出的, +// 前面两种时间戳可以直接与整数部分叠加, +// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 +// 小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, +// 所有的时间带来都是在以一个采样数据为该参考点(reference-point)的时间。 +enum VitaTSF { + TSF_NONE,//No Fractional-seconds Timestamp field included. 不包括分数秒时间戳字段 + TSF_SAMPLE_COUNT,//Sample Count Timestamp. 样本计数时间戳 + TSF_REALTIME,//Real Time(Picoseconds) Timestamp. 实时(皮秒)时间戳 + TSF_FREERUN,//Free Running Count Timestamp. 自由运行计数时间戳 +}; + +public class VITA { + private static final String TAG = "VITA"; + + // 最小有效的VITA包长度 + private static final int VITAmin = 28; + + public static final int FRS_OUI = 0x12cd; + //public static final int VITA_PORT = 4991; + + public static final int FLEX_CLASS_ID = 0x534C; + public static final int FLEX_DAX_AUDIO_CLASS_ID = 0x534C03E3; + public static final int FLEX_DAX_IQ_CLASS_ID = 0x534C00E3; + public static final int FLEX_FFT_CLASS_ID = 0x534C8003; + public static final int FLEX_METER_CLASS_ID = 0x534C8002; + public static final int FLEX_Discovery_stream_ID = 0x800; + + + public static final int VS_Meter = 0x8002; + public static final int VS_PAN_FFT = 0x8003; + public static final int VS_Waterfall = 0x8004; + public static final int VS_Opus = 0x8005; + public static final int DAX_IQ_24Khz = 0x00e3; + public static final int DAX_IQ_48Khz = 0x00e4; + public static final int DAX_IQ_96Khz = 0x00e5; + public static final int DAX_IQ_192KHz = 0x00e6; + public static final int VS_DAX_Audio = 0x03e3; + public static final int VS_Discovery = 0xffff; + + + private byte[] buffer; + public VitaPacketType packetType; + public boolean classIdPresent;//指示数据包中是否包含类标识符(类ID)字段 + public boolean trailerPresent;//指示数据包是否包含尾部。 + public VitaTSI tsi;//时间戳的类型。 + public VitaTSF tsf;//时间戳小数部分类型 + public int packetCount;//包计数器,可以对连续的IF data packet进行计数,这些packet具有相同的Stream Identifier 和packet type。 + public int packetSize;//表示有多少32bit数在IF Data packet 里面 + + //时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位,小数部分64位。 + public long integerTimestamp;//u_int32,long是64位的 + public long fracTimeStamp; + public long oui; + public int informationClassCode;//无用了,用classId代替 + public int packetClassCode;//无用了,用classId代替 + public int classId;//FLEX应该是0x534CFFF,是informationClassCode与packetClassCode合并的 + public byte[] payload = null; + public long trailer; + public boolean isAvailable = false;//电台对象是否有效。 + + public boolean streamIdPresent;//是否有流字符 + + //用来区分不同的 packet stream 。 + //stream ID 不是必须的,如果仅有一个数据包在单一数据链路传递的话就可以不用要, + //如果 packet stream想用同一 stream ID 的话那每一个packet都得有, + //在系统内部,不同的packet stream 之间的 Stream ID是不同的。 + //如果要用到 data-context 配对,那么IF data packet需要 Stream ID + public long streamId;//流ID,32位,FLEX应当是0x0800 + + /* + VITA前28个字节是VITA头 + */ + + + /** + * 生成音频流的VITA数据包,id应当是电台create stream是赋给的 + * + * @param id streamId + * @param data 音频流数据 + * @return vita数据包 + */ + public byte[] audioDataToVita(int count,long id, float[] data) { + byte[] result = new byte[data.length*4 + 28];//一个float占用4个字节,28字节是包头的长度7个word + //packetType = VitaPacketType.EXT_DATA_WITH_STREAM; + packetType = VitaPacketType.IF_DATA_WITH_STREAM; + classIdPresent = true; + trailerPresent = false;//没有尾巴 + tsi = VitaTSI.TSI_NONE;// +// tsi = VitaTSI.TSI_OTHER;// + //tsf = VitaTSF.TSF_SAMPLE_COUNT;//--TODO---查一下这个数字是不是变化 + tsf = VitaTSF.TSF_NONE;//--TODO---查一下这个数字是不是变化 + //packetCount动态变化 + //packetCount=?应该是这个全部音频流的总包数 + + //packetSize是以word(32位,4字节)为单位, + //packetSize值为263居多估计以音频,还有其它的长度,263是包含7个word(28字节)的头长度。 + packetSize = (data.length ) + 7;//7个word是VITA的包头 + //----以上是Header,32位,第一个word------- + + streamId = id;//第二个word,此id是电台赋给的。经常是0x40000xx。 + + oui = 0x00001c2d;//第三个word,FlexRadio Systems OUI + classId = 0x534c0123;//第四个word,64位 + //classId = 0x534c03e3;//第四个word,64位 + + //integerTimestamp =0;// System.currentTimeMillis() / 1000;//第五个word,时间戳的整数部分,以秒为单位。应该是取当前时间 + //fracTimeStamp = 0;//第六七个word,时间戳的小数部分,64位,此处为0。 + //fracTimeStamp = frac;//第六七个word,时间戳的小数部分,64位,此处为0。 + + byte temp = 0; + if (classIdPresent) { + temp = 0x08; + } + if (trailerPresent) { + temp |= 0x04; + } + //----HEADER--No.1 word------ +// result[0]=0x18; + result[0] = (byte) (packetType.ordinal() << 4);//packetType + result[0] |= temp;//其实就是0011 1000,0x38//CTRR,classIdPresent、trailerPresent、R、R + result[0] |= 0x03c0;//CTRR,classIdPresent、trailerPresent、R、R + + result[1]=(byte) 0xd0; + result[1]|=(byte)(count&0xf);//packet count + result[1] = (byte) (tsi.ordinal() << 6);//TSI + result[1] |= (byte) (tsf.ordinal() << 4);//TSF + result[1] |= (byte) (packetCount & 0xff);//packetCount + + //packetSize默认263(words) + result[2] = (byte) ((packetSize >> 8) & 0xff);//packetSize 1(高8位) + result[3] = (byte) (packetSize & 0xff);//packetSize 2(低8位) + + //-----Stream Identifier--No.2 word---- + //streamId=id;//最后两位应当是Dax编号 + result[4] = (byte) ((streamId& 0x00ff000000 >> 24) & 0xff); + result[5] = (byte) (((streamId & 0x00ff0000) >> 16) & 0xff); + result[6] = (byte) (((streamId & 0x0000ff00) >> 8) & 0xff); + result[7] = (byte) (streamId & 0x000000ff); + + //----OUI--No.3 words---- + //OUI = 0x001C2D + result[8] = 0x00; + result[9] = 0x00; + result[10] = 0x1c; + result[11] = 0x2d; + //---Class Identifier--No.4 word---- + //class id=0x534c0123 + result[12] = 0x53; + result[13] = 0x4c; + result[14] = (byte) 0x01; + result[15] = (byte) 0x23; + + //---Timestamp--No.5 word---- + //integerTimestamp=0x01020304 + +// result[16] = (byte) 0x01; +// result[17] = (byte) 0x02; +// result[18] = (byte) 0x03; +// result[19] = (byte) 0x04; + + //---FracTimeStamp No.5~6 words---- + //fracTimeStamp=0x10200300506070c0 +// result[20] = 0x10; +// result[21] = 0x20; +// result[22] = 0x03; +// result[23] = 0x00; +// result[24] = 0x50; +// result[25] = 0x60; +// result[26] = 0x70; +// result[27] = (byte) 0xc0; +// result[20] = (byte) ((fracTimeStamp >> 56) & 0x000000ff); +// result[21] = (byte) ((fracTimeStamp >> 48) & 0x000000ff); +// result[22] = (byte) ((fracTimeStamp >> 40) & 0x000000ff); +// result[23] = (byte) ((fracTimeStamp >> 32) & 0x000000ff); +// +// result[24] = (byte) ((fracTimeStamp >> 24) & 0x000000ff); +// result[25] = (byte) ((fracTimeStamp >> 16) & 0x000000ff); +// result[26] = (byte) ((fracTimeStamp >> 8) & 0x000000ff); +// result[27] = (byte) (fracTimeStamp & 0x000000ff); + for (int i = 0; i < data.length; i++) { + byte[] bytes=ByteBuffer.allocate(4).putFloat(data[i]).array();//float转byte[] + result[i*4+28]= bytes[0]; + result[i*4+29]= bytes[1]; + result[i*4+30]= bytes[2]; + result[i*4+31]= bytes[3]; + } + + /* + 也就是payload的长度+28字节:byte[] result=new byte[data.length+28]; + streamIdPresent=true; + streamIdPresent=packetType==VitaPacketType.IF_DATA_WITH_STREAM + ||packetType==VitaPacketType.EXT_DATA_WITH_STREAM; + streamId:0x4000008,此处应该是STREAM_CREATE_DAX_TX的值 + classIdPresent:0x534c03e3,packetSize:263 + integerTimestamp=now/1000;以秒为单位 + fracTimeStamp=0; + */ + + + return result; + } + + public VITA() { + } + + public VITA(byte[] data) { + this.buffer = data; + //如果包的长度太小,或包为空,就退出计算 + if (data == null) return; + if (data.length < VITAmin) return; + + isAvailable = true;//数据长度达到28个字节,说明是有效的。 + packetType = VitaPacketType.values()[(data[0] >> 4) & 0x0f]; + classIdPresent = (data[0] & 0x8) == 0x8;//指示数据包中是否包含类标识符(类ID)字段 + trailerPresent = (data[0] & 0x4) == 0x4;//指示数据包是否包含尾部。 + tsi = VitaTSI.values()[(data[1] >> 6) & 0x3];//如果有时间戳的话指示时间戳的整数部分是啥类型的 + tsf = VitaTSF.values()[(data[1] >> 4) & 0x3]; + packetCount = data[1] & 0x0f; + packetSize = ((((int) data[2]) & 0x00ff) << 8) | ((int) data[3]) & 0x00ff; + + int offset = 4;//定位 + //检查是否有流字符 + streamIdPresent = packetType == VitaPacketType.IF_DATA_WITH_STREAM + || packetType == VitaPacketType.EXT_DATA_WITH_STREAM; + + if (streamIdPresent) {//是否有流ID,获取流ID,32位 + streamId = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) + | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; + offset += 4; + } + + if (classIdPresent) { + //只取24位,前8位保留 + oui = ((((int) data[offset + 1]) & 0x00ff) << 16) + | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; + + informationClassCode = ((((int) data[offset + 4]) & 0x00ff) << 8) | ((int) data[offset + 5]) & 0x00ff; + packetClassCode = ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; + + classId = ((((int) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16) + | ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; + offset += 8; + } + //Log.e(TAG, "VITA: "+String.format("id: 0x%x, classIdPresent:0x%x,packetSize:%d",streamId,classId,packetSize) ); + + //获取时间戳,以秒为单位的时间戳,32位。 + //时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, + //小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 + //小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, + //所有的时间带来都是在以一个采样数据为该reference-point 时间 + if (tsi != VitaTSI.TSI_NONE) {//32位, + integerTimestamp = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) + | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; + offset += 4; + } + //获取时间戳的小数部分,64位。 + if (tsf != VitaTSF.TSF_NONE) { + fracTimeStamp = ((((long) data[offset]) & 0x00ff) << 56) | ((((long) data[offset + 1]) & 0x00ff) << 48) + | ((((long) data[offset + 2]) & 0x00ff) << 36) | ((int) data[offset + 3]) & 0x00ff + | ((((long) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16) + | ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; + offset += 8; + } + + + //Log.e(TAG, String.format("VITA: data length:%d,offset:%d",data.length,offset) ); + if (offset < data.length) { + payload = new byte[data.length - offset - (trailerPresent ? 2 : 0)];//如果有尾部,就减去一个word的位置 + System.arraycopy(data, offset, payload, 0, payload.length); + } + if (trailerPresent) { + trailer = ((((int) data[data.length - 2]) & 0x00ff) << 8) | ((int) data[data.length - 1]) & 0x00ff; + } + } + + /** + * 获取payload的长度,如果没有数据,payload长度为0; + * + * @return payload长度 + */ + public int getPayloadLength() { + if (buffer == null) { + return 0; + } else { + return buffer.length; + } + } + + /** + * 显示扩展数据 + * + * @return string + */ + public String showPayload() { + if (payload != null) { + return new String(payload).replace(" ", "\n"); + } else { + return ""; + } + } + + public String showPayloadHex() { + if (payload != null) { + return byteToStr(payload); + } else { + return ""; + } + } + + /** + * 显示VITA 49的包头信息 + * + * @return string + */ + @SuppressLint("DefaultLocale") + public String showHeadStr() { + return String.format("包类型(packetType): %s\n" + + "包数量(packetCount): %d\n" + + "包大小(packetSize): %d\n" + + "是否有流ID(streamIdPresent): %s\n" + + "流ID(streamId): 0x%X\n" + + "是否有类ID(classIdPresent): %s\n" + + "类ID(classId): 0x%X\n" + + "类高位(informationClassCode): 0x%X\n" + + "类低位(packetClassCode): 0x%X\n" + + "公司标识码(oui): 0x%X\n" + + "时间戳类型(tsi): %s\n" + + "时间戳整数部分(integerTimestamp):%s\n" + + "时间戳小数部分类型(tsf): %s\n" + + "时间戳小数部分值(fracTimeStamp): %d\n" + + "负载长度(payloadLength): %d\n" + + "是否有尾部(trailerPresent): %s\n" + + , packetType.toString() + , packetCount + , packetSize + , streamIdPresent ? "是" : "否" + , streamId + , classIdPresent ? "是" : "否" + , classId + , informationClassCode + , packetClassCode + , oui + , tsi.toString() + , timestampToDateStr(integerTimestamp * 1000) + , tsf.toString() + , fracTimeStamp + , (payload == null ? 0 : payload.length) + , trailerPresent ? "是" : "否" + ); + } + + /** + * 显示VITA 49 包数据 + * + * @return string + */ + @SuppressLint("DefaultLocale") + @Override + public String toString() { + return String.format("%s负载(payload):\n%s\n" + , showHeadStr() + , (payload == null ? "" : new String(payload)) + ); + + + } + + public static String timestampToDateStr(Long timestamp) { + //final String DATETIME_CONVENTIONAL_CN = "yyyy-MM-dd HH:mm:ss"; + //SimpleDateFormat sdf = new SimpleDateFormat(DATETIME_CONVENTIONAL_CN); + @SuppressLint("SimpleDateFormat") + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String sd = sdf.format(new Date(timestamp)); // 时间戳转换日期 + //System.out.println(sd); + return sd; + } + + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%02x ", data[i] & 0xff)); + } + return s.toString(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java new file mode 100644 index 0000000..618b442 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java @@ -0,0 +1,381 @@ +package com.bg7yoz.ft8cn.floatview; +/** + * FloatButton的主界面 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; +import androidx.constraintlayout.widget.Constraints; + +import java.util.ArrayList; + + +public class FloatView extends ConstraintLayout { + private static final String TAG = "FloatView"; + + public enum FLOAT_BOARD { + LEFT, RIGHT, TOP, BUTTON + } + + + private int parentViewHeight = 100;//上一级view的高度 + private int parentViewWidth = 100;//上一级view的宽度 + private float mDownX = 0; + private float mDownY = 0; + private int lastLeft = 0; + private int lastTop = 0; + + + //------------悬浮窗口的属性-------------------- + private int buttonSize = 96;//按钮的大小 + private final ArrayList buttons = new ArrayList<>(); + private boolean originalFromTop = false;//是否上下靠边 + private boolean originalFromLeft = true;//是否左右靠边 + private int buttonBackgroundResourceId = -1;//按钮的背景 + private int backgroundResourceId = -1;//浮窗的背景 + private int buttonMargin = 0;//按钮在浮窗中的边界宽度 + private int floatMargin = 40;//浮窗距离边界的距离 + private FLOAT_BOARD floatBoard = FLOAT_BOARD.LEFT; + + + /** + * 构造函数,需要大小 + * + * @param context context + * @param buttonSize 按钮大小,正方形 + */ + public FloatView(@NonNull Context context, int buttonSize) { + this(context); + this.buttonSize = buttonSize; + } + + public FloatView(@NonNull Context context) { + this(context, null); + } + + public FloatView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + + public FloatView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + public FloatViewButton addButton(String name, int imageResourceId, OnClickListener onClickListener) { + FloatViewButton floatViewButton=getButtonByName(name); + if (floatViewButton==null){ + floatViewButton =addButton(View.generateViewId(), imageResourceId, onClickListener); + } + floatViewButton.setName(name); + return floatViewButton; + } + + public FloatViewButton addButton(int id, String name, int imageResourceId, OnClickListener onClickListener) { + FloatViewButton floatViewButton=getButtonByName(name); + if (floatViewButton==null){ + floatViewButton = addButton(id, imageResourceId, onClickListener); + } + floatViewButton.setName(name); + return floatViewButton; + } + + public FloatViewButton addButton(int id, int imageResourceId, OnClickListener onClickListener) { + FloatViewButton imageButton = new FloatViewButton(getContext()); + //imageButton.setScaleType(ImageView.ScaleType.FIT_CENTER); + imageButton.setImageResource(imageResourceId); + if (buttonBackgroundResourceId != -1) { + imageButton.setBackgroundResource(buttonBackgroundResourceId); + } + //imageButton.setId(R.id.float_nav); + imageButton.setId(id); + imageButton.setOnClickListener(onClickListener); + + imageButton.setAlpha(0.5f); + + addView(imageButton); + buttons.add(imageButton); + resetView(); + + return imageButton; + } + + /** + * 通过按钮的名称删除按钮 + * + * @param name 按钮的名称 + */ + public void deleteButtonByName(String name) { + for (int i = buttons.size() - 1; i >= 0; i--) { + FloatViewButton floatViewButton = buttons.get(i); + if (floatViewButton.getName().equals(name)) { + buttons.remove(i); + removeView(floatViewButton); + } + resetView(); + } + } + + public void deleteButtonByIndex(int index) { + if (buttons.size() > index && index > -1) { + FloatViewButton floatViewButton = buttons.get(index); + buttons.remove(index); + removeView(floatViewButton); + resetView(); + } + } + + public FloatViewButton getButtonByName(String name) { + for (FloatViewButton button : buttons) { + if (button.getName().equals(name)) { + return button; + } + } + return null; + } + + /** + * 把dp值转换为像素点 + * + * @param dp dp值 + * @return 像素点 + */ + private int dpToPixel(int dp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp + , getResources().getDisplayMetrics()); +// return (int) (dp*getResources().getDisplayMetrics().density); + } + + /** + * 重新设置一下按钮 + */ + public void resetView() { + for (int i = 0; i < buttons.size(); i++) { + //LayoutParams buttonLp = new LayoutParams(buttonSize, buttonSize); + LayoutParams buttonLp = new LayoutParams(dpToPixel(buttonSize), dpToPixel(buttonSize)); + buttonLp.startToStart = ConstraintSet.PARENT_ID; + buttonLp.endToEnd = ConstraintSet.PARENT_ID; + buttonLp.leftMargin = buttonMargin; + buttonLp.rightMargin = buttonMargin; + if (i == 0) { + buttonLp.topToTop = ConstraintSet.PARENT_ID; + buttonLp.topMargin = buttonMargin; + } else { + buttonLp.topToBottom = buttons.get(i - 1).getId(); + buttonLp.topMargin = buttonMargin+dpToPixel(4);//按钮之间留一点点空隙 + } + if (i == buttons.size() - 1) { + buttonLp.bottomToBottom = ConstraintSet.PARENT_ID; + buttonLp.bottomMargin = buttonMargin; + } + buttons.get(i).setLayoutParams(buttonLp); + } + + + } + + @SuppressLint("ClickableViewAccessibility") + private void initView() { + LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);// LayoutParams.WRAP_CONTENT); + lp.startToStart = ConstraintSet.PARENT_ID;//只连接窗口的左边和上边 + lp.topToTop = ConstraintSet.PARENT_ID; + this.setLayoutParams(lp); + } + + public void initLocation() { + initLocation(this.floatBoard); + } + + + /** + * 初始化浮窗的位置,默认在窗口的右侧, + */ + public void initLocation(FLOAT_BOARD float_board) { + this.floatBoard = float_board; + getParentViewHeightAndWidth(); + int width; + int height; + + if (parentViewWidth == 0 && parentViewHeight == 0) { + WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + width = wm.getDefaultDisplay().getWidth(); + height = wm.getDefaultDisplay().getHeight(); + } else {//这部分基本没有执行过 + width = parentViewWidth; + height = parentViewHeight; + } + switch (float_board) { + case RIGHT: + setLayoutLeftTop(width - dpToPixel(floatMargin * 2 - buttonSize) - 10 + , (int) (height / 2f - dpToPixel(buttonMargin + buttonSize * buttons.size())/2f)); + break; + case LEFT: + setLayoutLeftTop(floatMargin + 10 + , (int) (height / 2f - dpToPixel(buttonMargin + buttonSize * buttons.size() )/2f)); + break; + case TOP: + setLayoutLeftTop((int) (width / 2f - dpToPixel(buttonMargin - buttonSize) / 2f), floatMargin); + break; + case BUTTON: + setLayoutLeftTop((int) (width / 2f - dpToPixel(buttonMargin - buttonSize) / 2f) + , height - dpToPixel(floatMargin - buttonMargin * 2 - buttonSize * buttons.size())); + break; + } + + } + + /** + * 获取父View的高度和宽度 + */ + private void getParentViewHeightAndWidth() { + View view = (View) getParent(); + if (view != null) { + parentViewHeight = view.getHeight(); + parentViewWidth = view.getWidth(); + } + + } + + public void setLayoutLeftTop(int left, int top) { + ConstraintLayout.LayoutParams layoutParams = new Constraints.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT + , ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.topToTop = ConstraintSet.PARENT_ID; + layoutParams.startToStart = ConstraintSet.PARENT_ID; + layoutParams.leftMargin = left; + layoutParams.topMargin = top; + setLayoutParams(layoutParams); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mDownX = event.getX(); + mDownY = event.getY(); + break; + case MotionEvent.ACTION_MOVE: + offsetTopAndBottom((int) (event.getY() - mDownY)); + offsetLeftAndRight((int) (event.getX() - mDownX)); + + lastLeft = getLeft(); + lastTop = getTop(); + setLayoutLeftTop(getLeft(), getTop()); + + case MotionEvent.ACTION_UP: + + setLayoutLeftTop(lastLeft, lastTop); + + adsorbTopAdnBottom();//吸附上下 + adsorbLeftAndRight();//吸附左右 + break; + case MotionEvent.ACTION_CANCEL: + break; + + } + return super.onInterceptTouchEvent(event); + } + + + private void adsorbTopAdnBottom() { + if (originalFromTop) { + getParentViewHeightAndWidth(); + + float boundaryLine = parentViewHeight / 4f; + if (getTop() < boundaryLine) { + setLayoutLeftTop(getLeft(), floatMargin); + } else if (getBottom() > parentViewHeight - boundaryLine) { + setLayoutLeftTop(getLeft(), parentViewHeight - getHeight() - floatMargin); + } + } + } + + private void adsorbLeftAndRight() { + if (originalFromLeft) { + getParentViewHeightAndWidth(); + float boundaryLine = parentViewWidth / 4f; + if (getLeft() < boundaryLine) { + setLayoutLeftTop(floatMargin, getTop()); + } else if (getRight() > parentViewWidth - boundaryLine) { + setLayoutLeftTop(parentViewWidth - getWidth() - floatMargin, getTop()); + //animate().setInterpolator(new DecelerateInterpolator()).setDuration(300).x(parentViewWidth - getWidth() - floatMargin).start(); + } + } + } + + + public boolean isOriginalFromTop() { + return originalFromTop; + } + + public void setOriginalFromTop(boolean originalFromTop) { + this.originalFromTop = originalFromTop; + } + + public boolean isOriginalFromLeft() { + return originalFromLeft; + } + + public void setOriginalFromLeft(boolean originalFromLeft) { + this.originalFromLeft = originalFromLeft; + } + + public int getButtonBackgroundResourceId() { + return buttonBackgroundResourceId; + } + + public void setButtonBackgroundResourceId(int buttonBackgroundResourceId) { + this.buttonBackgroundResourceId = buttonBackgroundResourceId; + for (ImageButton button : this.buttons) { + button.setBackgroundResource(buttonBackgroundResourceId); + } + } + + public int getBackgroundResourceId() { + return backgroundResourceId; + } + + public void setBackgroundResourceId(int backgroundResourceId) { + this.setBackgroundResource(backgroundResourceId); + this.backgroundResourceId = backgroundResourceId; + } + + public int getButtonMargin() { + return buttonMargin; + } + + public void setButtonMargin(int buttonMargin) { + this.buttonMargin = buttonMargin; + } + + public int getFloatMargin() { + return floatMargin; + } + + public void setFloatMargin(int floatMargin) { + this.floatMargin = floatMargin; + } + + public FLOAT_BOARD getFloatBoard() { + return floatBoard; + } + + public void setFloatBoard(FLOAT_BOARD float_board) { + this.floatBoard = float_board; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java new file mode 100644 index 0000000..cba7670 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java @@ -0,0 +1,39 @@ +package com.bg7yoz.ft8cn.floatview; +/** + * 自定义FloatButton + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageButton; + +@SuppressLint("AppCompatCustomView") +public class FloatViewButton extends ImageButton { + private String name; + public FloatViewButton(Context context) { + super(context); + } + + public FloatViewButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FloatViewButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public String getName() { + if (name==null){ + return ""; + }else { + return name; + } + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java new file mode 100644 index 0000000..c516314 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java @@ -0,0 +1,31 @@ +package com.bg7yoz.ft8cn.ft8listener; + +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; + +import java.util.ArrayList; + +public class A91List { + + public ArrayList list=new ArrayList<>(); + public void clear(){ + list.clear(); + } + public void add(byte[] data,float freq,float sec){ + A91 a91=new A91(data,sec,freq); + list.add(a91); + } + public int size(){ + return list.size(); + } + public static class A91{ + public byte[] a91 ;//= new byte[GenerateFT8.FTX_LDPC_K_BYTES]; + public float time_sec = 0;//时间偏移(秒) + public float freq_hz = 0;//频率 + + public A91(byte[] a91, float time_sec, float freq_hz) { + this.a91 = a91; + this.time_sec = time_sec; + this.freq_hz = freq_hz; + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java new file mode 100644 index 0000000..45f600e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java @@ -0,0 +1,369 @@ +package com.bg7yoz.ft8cn.ft8listener; +/** + * 用于监听音频的类。监听通过时钟UtcTimer来控制周期,通过OnWaveDataListener接口来读取音频数据。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.FT8Common; +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; +import com.bg7yoz.ft8cn.timer.OnUtcTimer; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; +import com.bg7yoz.ft8cn.wave.WaveFileReader; +import com.bg7yoz.ft8cn.wave.WaveFileWriter; + +import java.util.ArrayList; + +public class FT8SignalListener { + private static final String TAG = "FT8SignalListener"; + private final UtcTimer utcTimer; + //private HamRecorder hamRecorder; + private final OnFt8Listen onFt8Listen;//当开始监听,解码结束后触发的事件 + //private long band; + public MutableLiveData decodeTimeSec = new MutableLiveData<>();//解码的时长 + public long timeSec=0;//解码的时长 + + private OnWaveDataListener onWaveDataListener; + + + private DatabaseOpr db; + + private final A91List a91List = new A91List();//a91列表 + + + static { + System.loadLibrary("ft8cn"); + } + + public interface OnWaveDataListener { + void getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone); + } + + public FT8SignalListener(DatabaseOpr db, OnFt8Listen onFt8Listen) { + //this.hamRecorder = hamRecorder; + this.onFt8Listen = onFt8Listen; + this.db = db; + + //创建动作触发器,与UTC时间同步,以15秒一个周期,DoOnSecTimer是在周期起始时触发的事件。150是15秒 + utcTimer = new UtcTimer(FT8Common.FT8_SLOT_TIME_M, false, new OnUtcTimer() { + @Override + public void doHeartBeatTimer(long utc) {//不触发时的时钟信息 + } + + @Override + public void doOnSecTimer(long utc) {//当指定间隔时触发时 + Log.d(TAG, String.format("触发录音,%d", utc)); + runRecorde(utc); + } + }); + } + + public void startListen() { + utcTimer.start(); + } + + public void stopListen() { + utcTimer.stop(); + } + + public boolean isListening() { + return utcTimer.isRunning(); + } + + /** + * 获取当前时间的偏移量,这里包括总的时钟偏移,也包括本实例的偏移 + * + * @return int + */ + public int time_Offset() { + return utcTimer.getTime_sec() + UtcTimer.delay; + } + + /** + * 录音。在后台以多线程的方式录音,录音自动生成一个临时的Wav格式文件。 + * 有两个回调函数,用于开始录音时和结束录音时。当结束录音时,激活解码程序。 + * + * @param utc 当前解码的UTC时间 + */ + private void runRecorde(long utc) { + Log.d(TAG, "开始录音..."); + + if (onWaveDataListener != null) { + onWaveDataListener.getVoiceData(FT8Common.FT8_SLOT_TIME_MILLISECOND, true + , new OnGetVoiceDataDone() { + @Override + public void onGetDone(float[] data) { + Log.d(TAG, "开始解码...###"); + decodeFt8(utc, data); + } + }); + } + } + + public void decodeFt8(long utc, float[] voiceData) { + + //此处是测试用代码------------------------- +// String fileName = getCacheFileName("test_01.wav"); +// Log.e(TAG, "onClick: fileName:" + fileName); +// WaveFileReader reader = new WaveFileReader(fileName); +// int data[][] = reader.getData(); + //---------------------------------------------------------- + + new Thread(new Runnable() { + @Override + public void run() { + long time = System.currentTimeMillis(); + if (onFt8Listen != null) { + onFt8Listen.beforeListen(utc); + } + +// float[] tempData = ints2floats(data); + + + ///读入音频数据,并做预处理 + //其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了 + long ft8Decoder = InitDecoder(utc, FT8Common.SAMPLE_RATE + , voiceData.length, true); +// , tempData.length, true); + DecoderMonitorPressFloat(voiceData, ft8Decoder);//读入音频数据 +// DecoderMonitorPressFloat(tempData, ft8Decoder);//读入音频数据 + + + ArrayList allMsg = new ArrayList<>(); +// ArrayList msgs = runDecode(utc, voiceData,false); + ArrayList msgs = runDecode(ft8Decoder, utc, false); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, false); + } + + + if (GeneralVariables.deepDecodeMode) {//进入深度解码模式 + //float[] newSignal=tempData; + msgs = runDecode(ft8Decoder, utc, true); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); + } + + + do { + if (timeSec > FT8Common.DEEP_DECODE_TIMEOUT) break;//此处做超时检测,超过一定时间(7秒),就不做减码操作了 + //减去解码的信号 + ReBuildSignal.subtractSignal(ft8Decoder, a91List); + + //再做一次解码 + msgs = runDecode(ft8Decoder, utc, true); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); + } + + } while (msgs.size() > 0 ); + + } + //移到finalize() 方法中调用了 + DeleteDecoder(ft8Decoder); + + Log.d(TAG, String.format("解码耗时:%d毫秒", System.currentTimeMillis() - time)); + + } + }).start(); + } + + + private ArrayList runDecode(long ft8Decoder, long utc, boolean isDeep) { + ArrayList ft8Messages = new ArrayList<>(); + Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE); + + ft8Message.utcTime = utc; + ft8Message.band = GeneralVariables.band; + a91List.clear(); + + setDecodeMode(ft8Decoder, isDeep);//设置迭代次数,isDeep==true,迭代次数增加 + + int num_candidates = DecoderFt8FindSync(ft8Decoder);//最多120个 + //long startTime = System.currentTimeMillis(); + for (int idx = 0; idx < num_candidates; ++idx) { + //todo 应当做一下超时计算 + try {//做一下解码失败保护 + if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) { + + if (ft8Message.isValid) { + Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉 + byte[] a91 = DecoderGetA91(ft8Decoder); + a91List.add(a91, ft8Message.freq_hz, ft8Message.time_sec); + + if (checkMessageSame(ft8Messages, msg)) { + continue; + } + + msg.isWeakSignal = isDeep;//是不是弱信号 + ft8Messages.add(msg); + + } + } + } catch (Exception e) { + Log.e(TAG, "run: " + e.getMessage()); + } + + } + + + return ft8Messages; + } + + /** + * 计算平均时间偏移值 + * + * @param messages 消息列表 + * @return 偏移值 + */ + private float averageOffset(ArrayList messages) { + if (messages.size() == 0) return 0f; + float dt = 0; + //int dtAverage = 0; + for (Ft8Message msg : messages) { + dt += msg.time_sec; + } + return dt / messages.size(); + } + + /** + * 把消息添加到列表中 + * + * @param allMsg 消息列表 + * @param newMsg 新的消息 + */ + private void addMsgToList(ArrayList allMsg, ArrayList newMsg) { + for (int i = newMsg.size() - 1; i >= 0; i--) { + if (checkMessageSame(allMsg, newMsg.get(i))) { + newMsg.remove(i); + } else { + allMsg.add(newMsg.get(i)); + } + } + } + + /** + * 检查消息列表里同样的内容是否存在 + * + * @param ft8Messages 消息列表 + * @param ft8Message 消息 + * @return boolean + */ + private boolean checkMessageSame(ArrayList ft8Messages, Ft8Message ft8Message) { + for (Ft8Message msg : ft8Messages) { + if (msg.getMessageText().equals(ft8Message.getMessageText())) { + if (msg.snr < ft8Message.snr) { + msg.snr = ft8Message.snr; + } + return true; + } + } + return false; + } + + @Override + protected void finalize() throws Throwable { + //DeleteDecoder(ft8Decoder); + super.finalize(); + } + + public OnWaveDataListener getOnWaveDataListener() { + return onWaveDataListener; + } + + public void setOnWaveDataListener(OnWaveDataListener onWaveDataListener) { + this.onWaveDataListener = onWaveDataListener; + } + + + public String getCacheFileName(String fileName) { + return GeneralVariables.getMainContext().getCacheDir() + "/" + fileName; + } + + public float[] ints2floats(int data[][]) { + float temp[] = new float[data[0].length]; + for (int i = 0; i < data[0].length; i++) { + temp[i] = data[0][i] / 32768.0f; + } + return temp; + } + + public int[] floats2ints(float data[]) { + int temp[] = new int[data.length]; + for (int i = 0; i < data.length; i++) { + temp[i] = (int) (data[i] * 32767.0f); + } + return temp; + } + + /** + * 解码的第一步,初始化解码器,获取解码器的地址。 + * + * @param utcTime UTC时间 + * @param sampleRat 采样率,12000 + * @param num_samples 缓冲区数据的长度 + * @param isFt8 是否是FT8信号 + * @return 返回解码器的地址 + */ + public native long InitDecoder(long utcTime, int sampleRat, int num_samples, boolean isFt8); + + /** + * 解码的第二步,读取Wav数据。 + * + * @param buffer Wav数据缓冲区 + * @param decoder 解码器数据的地址 + */ + public native void DecoderMonitorPress(int[] buffer, long decoder); + + public native void DecoderMonitorPressFloat(float[] buffer, long decoder); + + + /** + * 解码的第三步,同步数据。 + * + * @param decoder 解码器地址 + * @return 中标信号的数量 + */ + public native int DecoderFt8FindSync(long decoder); + + /** + * 解码的第四步,分析出消息。(需要在一个循环里) + * + * @param idx 中标信号的序号 + * @param decoder 解码器的地址 + * @param ft8Message 解出来的消息 + * @return boolean + */ + public native boolean DecoderFt8Analysis(int idx, long decoder, Ft8Message ft8Message); + + /** + * 解码的最后一步,删除解码器数据 + * + * @param decoder 解码器数据的地址 + */ + public native void DeleteDecoder(long decoder); + + public native void DecoderFt8Reset(long decoder, long utcTime, int num_samples); + + public native byte[] DecoderGetA91(long decoder);//获取当前message的a91数据 + + public native void setDecodeMode(long decoder, boolean isDeep);//设置解码的模式,isDeep=true是多次迭代,=false是快速迭代 +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java new file mode 100644 index 0000000..04492d0 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java @@ -0,0 +1,27 @@ +package com.bg7yoz.ft8cn.ft8listener; +/** + * 监听音频的回调,当结束解码后,调用afterDecode来通知解码的消息 + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.Ft8Message; + +import java.util.ArrayList; + +public interface OnFt8Listen { + /** + * 当开始监听时触发的事件 + * @param utc 当前的UTC时间 + */ + void beforeListen(long utc); + + /** + *当解码结束后触发的事件 + * @param utc 当前周期的UTC时间 + * @param time_sec 此次平均的偏移时间(秒) + * @param sequential 当前的时序 + * @param messages 消息列表 + */ + void afterDecode(long utc,float time_sec,int sequential, ArrayList messages,boolean isDeep); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java new file mode 100644 index 0000000..5e2a3a5 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java @@ -0,0 +1,28 @@ +package com.bg7yoz.ft8cn.ft8listener; + +import android.util.Log; + +import com.bg7yoz.ft8cn.FT8Common; +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; +import com.bg7yoz.ft8cn.wave.WaveFileWriter; + +import java.util.ArrayList; + +public class ReBuildSignal { + private static String TAG = "ReBuildSignal"; + static { + System.loadLibrary("ft8cn"); + } + + + public static void subtractSignal(long decoder,A91List a91List){ + for (A91List.A91 a91 : a91List.list) { + doSubtractSignal(decoder,a91.a91,FT8Common.SAMPLE_RATE,a91.freq_hz,a91.time_sec); + } + } + + private static native void doSubtractSignal(long decoder,byte[] payload,int sample_rate + ,float frequency,float time_sec); + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java new file mode 100644 index 0000000..749e199 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java @@ -0,0 +1,378 @@ +package com.bg7yoz.ft8cn.ft8signal; +/** + * 按照FT8协议打包符号。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; + +public class FT8Package { + private static final String TAG = "FT8Package"; + public static final int NTOKENS = 2063592; + public static final int MAX22 = 4194304; + public static final int MAXGRID4 = 32400; + + + private static final String A1 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String A2 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String A3 = "0123456789"; + private static final String A4 = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String A5 = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"; + + static { + System.loadLibrary("ft8cn"); + } + + + public static byte[] generatePack77_i4(Ft8Message message) { + + String toCall = message.callsignTo.replace("<", "").replace(">", ""); + String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); + int hash12; + if (message.checkIsCQ()) {//如果是CQ,就把自己的呼号的哈希加上 + hash12 = getHash12(fromCall); + } else { + hash12 = getHash12(toCall); + } + if (fromCall.length() > 11) {//非标准呼号的长度不等长于11位 + fromCall = fromCall.substring(0, 11); + } + + byte[] data = new byte[10]; + long n58 = 0; + for (int i = 0; i < fromCall.length(); i++) { + n58 = n58 * 38 + A5.indexOf(fromCall.charAt(i)); + } + //n58=3479529522318088L; + + data[0] = (byte) ((hash12 & 0x00000fff) >> 4); + data[1] = (byte) ((hash12 & 0x0000000f) << 4); + data[1] = (byte) (data[1] | ((n58 & 0x0fff_ffff_ffff_ffffL) >> 54)); + data[2] = (byte) (((n58 & 0x00ff_ffff_ffff_ffffL) >> 54 - 8)); + data[3] = (byte) (((n58 & 0x0000_ffff_ffff_ffffL) >> 54 - 8 - 8)); + data[4] = (byte) (((n58 & 0x0000_00ff_ffff_ffffL) >> 54 - 8 - 8 - 8)); + data[5] = (byte) (((n58 & 0x0000_0000_ffff_ffffL) >> 54 - 8 - 8 - 8 - 8)); + data[6] = (byte) (((n58 & 0x0000_0000_00ff_ffffL) >> 54 - 8 - 8 - 8 - 8 - 8)); + data[7] = (byte) (((n58 & 0x0000_0000_0000_ffffL) >> 54 - 48)); + data[8] = (byte) (((n58 & 0x0000_0000_0000_00ffL) << 2)); + //RRR=1,RR73=2,73=3,""=0 + if (message.checkIsCQ()) { + //data[8]=(byte) (data[8]| );//h1=0,r2=0 i3-4 + data[9] = (byte) 0x60; + } else { + //data[9]=(byte)(data[9]&0xbf);//h1=0; + data[9] = (byte) 0x20; + int r2; + if (message.extraInfo.equals("RRR")) {//r2=1 + data[8] = (byte) (data[8] & 0xfe); + data[9] = (byte) (data[9] | 0x80); + r2 = 1; + } else if (message.extraInfo.equals("RR73")) {//r2=2 + data[8] = (byte) (data[8] | 0x01); + data[9] = (byte) (data[9] | 0x80); + r2 = 2; + } else if (message.extraInfo.equals("73")) {//r2=3 + data[8] = (byte) (data[8] | 0x01); + data[9] = (byte) (data[9] | 0x80); + r2 = 3; + } + } + + return data; + } + + + /** + * i1=1,i1=2,在FT8协议的定义当中,分别是标准消息,和欧盟甚高频(EU VHF),这两个消息的唯一区别是: + * i1=1,消息可以带/R,i1=2,消息是可以带/P + * 所以,这两个消息可以合并为一个类型。 + * + * @param message 原始消息 + * @return + */ + public static byte[] generatePack77_i1(Ft8Message message) { + //Log.e(TAG, "generatePack77_i1: "+message.toString() ); + Log.e(TAG, "generatePack77_i1: message.callsignTo " + message.callsignTo); + Log.e(TAG, "generatePack77_i1: message.callsignFrom " + message.callsignFrom); + String toCall = message.callsignTo.replace("<", "").replace(">", ""); + String fromCall = message.callsignFrom.replace("<", "").replace(">", ""); + + if (message.checkIsCQ()&& message.modifier!=null){//把修饰符加上 + if (message.modifier.length()>0) { + toCall = toCall + " " + message.modifier; + } + } + + //如果以/P 或/R结尾的呼号,要把这个/P /R去掉 + if (toCall.endsWith("/P") || toCall.endsWith("/R")) { + toCall = toCall.substring(0, toCall.length() - 2); + } + + if (fromCall.endsWith("/P") || fromCall.endsWith("/R")) { + fromCall = message.callsignFrom.substring(0, message.callsignFrom.length() - 2); + } + + //当双方都是复合呼号或非标准呼号时(带/的呼号),我的呼号变成标准呼号 + if ((toCall.contains("/")) && fromCall.contains("/")) { + fromCall = fromCall.substring(0, fromCall.indexOf("/")); + } + + + byte[] data = new byte[12]; + data[0] = (byte) ((pack_c28(toCall) & 0x0fffffff) >> 20); + data[1] = (byte) ((pack_c28(toCall) & 0x00ffffff) >> 12); + data[2] = (byte) ((pack_c28(toCall) & 0x0000ffff) >> 4); + data[3] = (byte) ((pack_c28(toCall) & 0x0000000f) << 4); + data[3] = (byte) (data[3] | (pack_r1_p1(message.callsignTo) << 3)); + data[3] = (byte) (data[3] | (pack_c28(fromCall) & 0x00fffffff) >> 25); + + + data[4] = (byte) ((pack_c28(fromCall) & 0x003ffffff) >> 25 - 8); + data[5] = (byte) ((pack_c28(fromCall) & 0x00003ffff) >> 25 - 8 - 8); + data[6] = (byte) ((pack_c28(fromCall) & 0x0000003ff) >> 25 - 8 - 8 - 8); + data[7] = (byte) ((pack_c28(fromCall) & 0x0000000ff) << 7); + + + data[7] = (byte) (data[7] | (pack_r1_p1(message.getCallsignFrom())) << 6); + data[7] = (byte) (data[7] | (pack_R1_g15(message.extraInfo) & 0x0ffff) >> 10); + data[8] = (byte) ((pack_R1_g15(message.extraInfo) & 0x0003fff) >> 2); + data[9] = (byte) ((pack_R1_g15(message.extraInfo) & 0x00000ff) << 6); + data[9] = (byte) (data[9] | (message.i3 & 0x3) << 3); + return data; + } + + /** + * 生成R1+g15数据(网格、或信号报告),实际是16位,包括前面R1。如R-17:R1=1,-17:R1=0 + * + * @param grid4 网格或信号报告 + * @return 返回R1+g15数据 + */ + public static int pack_R1_g15(String grid4) { + if (grid4 == null)// 只有两个呼号,没有信号报告和网格 + { + return MAXGRID4 + 1; + } + if (grid4.length() == 0) {// 只有两个呼号,没有信号报告和网格 + return MAXGRID4 + 1; + } + + // 特殊的报告,RRR,RR73,73 + if (grid4.equals("RRR")) + return MAXGRID4 + 2; + if (grid4.equals("RR73")) + return MAXGRID4 + 3; + if (grid4.equals("73")) + return MAXGRID4 + 4; + + + // 检查是不是标准的4字符网格 + if (grid4.matches("[A-Z][A-Z][0-9][0-9]")) { + int igrid4 = grid4.charAt(0) - 'A'; + igrid4 = igrid4 * 18 + (grid4.charAt(1) - 'A'); + igrid4 = igrid4 * 10 + (grid4.charAt(2) - '0'); + igrid4 = igrid4 * 10 + (grid4.charAt(3) - '0'); + return igrid4; + } + + + // 检查是不是信号报告: +dd / -dd / R+dd / R-dd + // 信号报告在-30到99dB之间 + // 信号报告的正则:[R]?[+-][0-9]{1,2} + String s = grid4; + if (grid4.charAt(0) == 'R') { + s = grid4.substring(1); + int irpt = 35 + Integer.parseInt(s); + return (MAXGRID4 + irpt) | 0x8000; // R1 = 1 + } else { + int irpt = 35 + Integer.parseInt(grid4); + return (MAXGRID4 + irpt); // R1 = 0 + } + + } + + public static byte pack_r1_p1(String callsign) { + String s = callsign.substring(callsign.length() - 2); + if (s.equals("/R") || s.equals("/P")) { + return 1; + } else { + return 0; + } + } + + /** + * 根据呼号生成c28数据。呼号为标准呼号时,不带/R或/P。如果呼号不是标准呼号,用hash22+2063592; + * + * @param callsign 呼号 + * @return c28数据 + */ + public static int pack_c28(String callsign) { + //byte[] data=new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}; + switch (callsign) { + case "DE": + return 0; + case "QRZ": + return 1; + case "CQ": + return 2; + } + + //判断是否有修饰符000-999,A-Z,AA-ZZ,AAA-ZZZ,AAAA-ZZZZ + if (callsign.startsWith("CQ ")&&callsign.length()>3){ + String temp=callsign.substring(3).trim().toUpperCase(); + if (temp.matches("[0-9]{3}")){ + int i=Integer.parseInt(temp); + return i+3; + } + if (temp.matches("[A-Z]{1,4}")) { + + int a0 = 0; + int a1 = 0; + int a2 = 0; + int a3 = 0; + if (temp.length() == 1) {//A-Z + a0= (int) temp.charAt(0) - 65; + return a0+1004; + } + if (temp.length() == 2) {//AA-ZZ + a0 = (int) temp.charAt(0) - 65; + a1 = (int) temp.charAt(1) - 65; + return a0*27+a1+1031; + } + if (temp.length() == 3) {//AAA-ZZZ + a0 = (int) temp.charAt(0) - 65; + a1 = (int) temp.charAt(1) - 65; + a2 = (int) temp.charAt(2) - 65; + return a0*27*27+a1*27+a2+1760; + } + if (temp.length() == 4) {//AAAA-ZZZZ + a0 = (int) temp.charAt(0) - 65; + a1 = (int) temp.charAt(1) - 65; + a2 = (int) temp.charAt(2) - 65; + a3 = (int) temp.charAt(3) - 65; + return a0*27*27*27+a1*27*27+a2*27+a3+21443; + } + } + } + + + //格式化成标准的呼号。6位、第3位带数字 + //c6也可以是非标准呼号。大于6位的都是非标准呼号 + String c6 = formatCallsign(callsign); + //if (c6.length()>6){//生成HASH22+2063592 + if (!GenerateFT8.checkIsStandardCallsign(callsign)) {//生成HASH22+2063592 + return NTOKENS + getHash22(callsign); + } + + //6位呼号取值 + int i0, i1, i2, i3, i4, i5; + i0 = A1.indexOf(c6.substring(0, 1)); + i1 = A2.indexOf(c6.substring(1, 2)); + i2 = A3.indexOf(c6.substring(2, 3)); + i3 = A4.indexOf(c6.substring(3, 4)); + i4 = A4.indexOf(c6.substring(4, 5)); + i5 = A4.indexOf(c6.substring(5, 6)); + + int n28 = i0; + n28 = n28 * 36 + i1; + n28 = n28 * 10 + i2; + n28 = n28 * 27 + i3; + n28 = n28 * 27 + i4; + n28 = n28 * 27 + i5; + + + return NTOKENS + MAX22 + n28; + + } + + public static long pack_c58(String callsign) { + //byte[] data=new byte[]{(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00}; + switch (callsign) { + case "DE": + return 0; + case "QRZ": + return 1; + case "CQ": + return 2; + } + + //格式化成标准的呼号。6位、第3位带数字 + String c6 = formatCallsign(callsign); + +// n58 = ((uint64_t) (a77[1] & 0x0F) << 54); //57 ~ 54 : 4 +// n58 |= ((uint64_t) a77[2] << 46); //53 ~ 46 : 12 +// n58 |= ((uint64_t) a77[3] << 38); //45 ~ 38 : 12 +// n58 |= ((uint64_t) a77[4] << 30); //37 ~ 30 : 12 +// n58 |= ((uint64_t) a77[5] << 22); //29 ~ 22 : 12 +// n58 |= ((uint64_t) a77[6] << 14); //21 ~ 14 : 12 +// n58 |= ((uint64_t) a77[7] << 6); //13 ~ 6 : 12 +// n58 |= ((uint64_t) a77[8] >> 2); //5 ~ 0 : 765432 10 + + + //6位呼号取值 + int i0, i1, i2, i3, i4, i5; + i0 = A1.indexOf(c6.substring(0, 1)); + i1 = A2.indexOf(c6.substring(1, 2)); + i2 = A3.indexOf(c6.substring(2, 3)); + i3 = A4.indexOf(c6.substring(3, 4)); + i4 = A4.indexOf(c6.substring(4, 5)); + int n28 = i0; + n28 = n28 * 36 + i1; + n28 = n28 * 10 + i2; + n28 = n28 * 27 + i3; + n28 = n28 * 27 + i4; + + if (c6.length() >= 5) { + i5 = A4.indexOf(c6.substring(5, 6)); + n28 = n28 * 27 + i5; + } + + return NTOKENS + MAX22 + n28; + } + + /** + * 格式化标准的呼号 + * 标准呼号是6位,前缀是1~2个字母+1个数字,后缀对多3个字母 + * 格式化的内容: + * 1.斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ + * 2.几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ + * 3.第2位是数字的呼号,前面用空格补充。A0XYZ->" A0XYZ" + * 4.后缀不足3位的,也要补足空格。BA2BI->"BA2BI " + * + * @param callsign 呼号 + * @return 返回C28值,以int的值来表示 + */ + private static String formatCallsign(String callsign) { + String c6 = callsign; + // 解决斯威士兰(Swaziland)的呼号前缀问题: 3DA0XYZ -> 3D0XYZ + if (callsign.length() > 3 && callsign.substring(0, 4).equals("3DA0") && callsign.length() <= 7) { + c6 = "3D0" + callsign.substring(4); + // 解决几内亚(Guinea)呼号前缀问题: 3XA0XYZ -> QA0XYZ + } else if (callsign.length() > 3 && callsign.substring(0, 3).matches("3X[A-Z]") && callsign.length() <= 7) { + c6 = "Q" + callsign.substring(2); + } else { + // 第2位是数字,第3位是字母的,要在前面补充空格:A0XYZ -> " A0XYZ",A6开头的除外 + if (callsign.substring(0, 3).matches("[A-Z][0-9][A-Z]")) { + c6 = " " + callsign; + } + } + + if (c6.length() < 6) {//如果长度不足6位,结尾补充空格 + for (int i = 0; i < 6 - c6.length() + 1; i++) { + c6 = c6 + " "; + } + } + + return c6; + } + + public static native int getHash12(String callsign); + + + public static native int getHash10(String callsign); + + public static native int getHash22(String callsign); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java new file mode 100644 index 0000000..14626ce --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java @@ -0,0 +1,1106 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 与发射信号有关的类。包括分析通联过程的自动程序。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import com.bg7yoz.ft8cn.FT8Common; +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.log.QSLRecord; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.OnUtcTimer; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class FT8TransmitSignal { + private static final String TAG = "FT8TransmitSignal"; + + private boolean transmitFreeText = false; + private String freeText = "FREE TEXT"; + + private final DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库 + private TransmitCallsign toCallsign;//目标呼号 + public MutableLiveData mutableToCallsign = new MutableLiveData<>(); + + private int functionOrder = 6; + public MutableLiveData mutableFunctionOrder = new MutableLiveData<>();//指令的顺序变化 + private boolean activated = false;//是否处于可以发射的模式 + public MutableLiveData mutableIsActivated = new MutableLiveData<>(); + public int sequential;//发射的时序 + public MutableLiveData mutableSequential = new MutableLiveData<>(); + private boolean isTransmitting = false; + public MutableLiveData mutableIsTransmitting = new MutableLiveData<>();//是否处于发射状态 + public MutableLiveData mutableTransmittingMessage = new MutableLiveData<>();//当前消息的内容 + + //public MutableLiveData currentOrder = new MutableLiveData<>();//当前要发射的指令 + + //******************************************** + //此处的信息是用于保存QSL的 + private long messageStartTime = 0;//消息开始的时间 + private long messageEndTime = 0;//消息结束的时间 + private String toMaidenheadGrid = "";//目标的网格信息 + private int sendReport = 0;//我发送到对方的报告 + private int sentTargetReport = -100;// + + + private int receivedReport = 0;//我接收到的报告 + private int receiveTargetReport = -100;//发送给对方的信号报告 + //******************************************** + private final OnTransmitSuccess onTransmitSuccess;//一般是用于保存QSL数据 + + + //防止播放中止,变量不能放在方法中 + private AudioAttributes attributes = null; + private AudioFormat myFormat = null; + private AudioTrack audioTrack = null; + + public UtcTimer utcTimer; + + + public ArrayList functionList = new ArrayList<>(); + public MutableLiveData> mutableFunctions = new MutableLiveData<>(); + + private final OnDoTransmitted onDoTransmitted;//一般是用于打开关闭PTT + private final ExecutorService doTransmitThreadPool = Executors.newCachedThreadPool(); + private final DoTransmitRunnable doTransmitRunnable = new DoTransmitRunnable(this); + + static { + System.loadLibrary("ft8cn"); + } + + /** + * 发射模块的构造函数,需要两个回调,一个是当发射时(有两个动作,用于打开/关闭PTT),另一个时当成功时(用于保存QSL)。 + * + * @param databaseOpr 数据库 + * @param doTransmitted 当发射前后时的回调 + * @param onTransmitSuccess 当发射成功时的回调 + */ + public FT8TransmitSignal(DatabaseOpr databaseOpr + , OnDoTransmitted doTransmitted, OnTransmitSuccess onTransmitSuccess) { + this.onDoTransmitted = doTransmitted;//用于打开关闭PTT的事件 + this.onTransmitSuccess = onTransmitSuccess;//用于保存QSL的事件 + this.databaseOpr = databaseOpr; + + setTransmitting(false); + setActivated(false); + + + //观察音量设置的变化 + GeneralVariables.mutableVolumePercent.observeForever(new Observer() { + @Override + public void onChanged(Float aFloat) { + if (audioTrack != null) { + audioTrack.setVolume(aFloat); + } + } + }); + + utcTimer = new UtcTimer(FT8Common.FT8_SLOT_TIME_M, false, new OnUtcTimer() { + @Override + public void doHeartBeatTimer(long utc) { + + } + + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void doOnSecTimer(long utc) { + //超过自动监管时间,就停止 + if (GeneralVariables.isLaunchSupervisionTimeout()) { + setActivated(false); + return; + } + if (UtcTimer.getNowSequential() == sequential && activated) { + if (GeneralVariables.myCallsign.length() < 3) { + //我的呼号不正确,不能发射! + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); + return; + } + doTransmit();//发射动作还是准确按时间来,延迟是音频信号的延迟 + } + } + }); + + + //utcTimer.setTime_sec(GeneralVariables.transmitDelay);//默认晚500毫秒发射,确保上一时序解码结束 + utcTimer.start(); + + } + + /** + * 立即发射 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + public void transmitNow() { + if (GeneralVariables.myCallsign.length() < 3) { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); + return; + } + ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.adjust_call_target) + , toCallsign.callsign)); + + //把信号报告相关的复位 + resetTargetReport(); + + if (UtcTimer.getNowSequential() == sequential) { + if ((UtcTimer.getSystemTime() % 15000) < 2500) { + setTransmitting(false); + doTransmit(); + } + } + } + + //发射信号 + //@RequiresApi(api = Build.VERSION_CODES.N) + public void doTransmit() { + if (!activated) { + return; + } + //检测是不是黑名单频率,WSPR-2的频率,频率=电台频率+声音频率 + if (BaseRigOperation.checkIsWSPR2( + GeneralVariables.band + Math.round(GeneralVariables.getBaseFrequency()))) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.use_wspr2_error) + , BaseRigOperation.getFrequencyAllInfo(GeneralVariables.band))); + setActivated(false); + return; + } + Log.d(TAG, "doTransmit: 开始发射..."); + doTransmitThreadPool.execute(doTransmitRunnable); +// new Thread(new Runnable() { +// @SuppressLint("DefaultLocale") +// @Override +// public void run() { +// //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来 +// if (functionOrder == 1 || functionOrder == 2) {//当消息处于1或2时,说明开始了通联 +// messageStartTime = UtcTimer.getSystemTime(); +// } +// if (messageStartTime == 0) {//如果起始时间没有,就取现在的 +// messageStartTime = UtcTimer.getSystemTime(); +// } +// +// //用于显示将要发射的消息内容 +// Ft8Message msg; +// if (transmitFreeText){ +// msg=new Ft8Message("CQ",GeneralVariables.myCallsign,freeText); +// msg.i3=0; +// msg.n3=0; +// }else { +// msg = getFunctionCommand(functionOrder); +// } +// +// if (onDoTransmitted != null) { +// //此处用于处理PTT等事件 +// onDoTransmitted.onBeforeTransmit(msg, functionOrder); +// } +// //short[] buffer = new short[FT8Common.SAMPLE_RATE * FT8Common.FT8_SLOT_TIME]; +// //79个符号,每个符号0.16秒,采样率12000, +// short[] buffer = new short[(int) (0.5f + +// GenerateFT8.num_tones * GenerateFT8.symbol_period +// * GenerateFT8.sample_rate)]; // 数据信号中的采样数0.5+79*0.16*12000]; +// +// +// isTransmitting = true; +// mutableIsTransmitting.postValue(true); +// +// +// mutableTransmittingMessage.postValue(String.format(" (%.0fHz) %s" +// , GeneralVariables.getBaseFrequency() +// , msg.getMessageText())); +// if (!GenerateFT8.generateFt8(msg +// , GeneralVariables.getBaseFrequency(), buffer)) { +// return; +// } +// ; +// //电台动作可能有要有个延迟时间,所以时间并不一定完全准确 +// try {//给电台一个100毫秒的响应时间 +// Thread.sleep(GeneralVariables.pttDelay);//给PTT指令后,电台一个响应时间,默认100毫秒 +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// if (onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 +// onDoTransmitted.onAfterGenerate(buffer); +// } +// //播放音频 +// playFT8Signal(buffer); +// } +// }).start(); + mutableFunctions.postValue(functionList); + } + + /** + * 设置呼叫,生成发射消息列表 + * + * @param transmitCallsign 目标呼号 + * @param functionOrder 命令顺序 + * @param toMaidenheadGrid 目标网格 + */ + @SuppressLint("DefaultLocale") + //@RequiresApi(api = Build.VERSION_CODES.N) + public void setTransmit(TransmitCallsign transmitCallsign + , int functionOrder, String toMaidenheadGrid) { + + messageStartTime = 0;//复位起始的时间 + + Log.d(TAG, "准备发射数据..."); + if (GeneralVariables.checkFun1(toMaidenheadGrid)) { + this.toMaidenheadGrid = toMaidenheadGrid; + } else { + this.toMaidenheadGrid = ""; + } + mutableToCallsign.postValue(transmitCallsign);//设定呼叫的目标对象(含报告、时序,频率,呼号) + toCallsign = transmitCallsign;//设定呼叫的目标 + //mutableToCallsign.postValue(toCallsign);//设定呼叫的目标 + + if (functionOrder == -1) {//说明是回复消息 + //此时的toMaidenheadGrid是extraInfo + this.functionOrder = GeneralVariables.checkFunOrderByExtraInfo(toMaidenheadGrid) + 1; + if (this.functionOrder == 6) {//如果已经是73了,就改到消息1 + this.functionOrder = 1; + } + } else { + this.functionOrder = functionOrder;//当前指令的序号 + } + + if (transmitCallsign.frequency == 0) { + transmitCallsign.frequency = GeneralVariables.getBaseFrequency(); + } + if (GeneralVariables.synFrequency) {//如果是同频发送,就与目标呼号频率一致 + setBaseFrequency(transmitCallsign.frequency); + } + + sequential = (toCallsign.sequential + 1) % 2;//发射的时序 + mutableSequential.postValue(sequential);//通知发射时序改变 + generateFun(); + mutableFunctionOrder.postValue(functionOrder); + + } + + @SuppressLint("DefaultLocale") + public void setBaseFrequency(float freq) { + GeneralVariables.setBaseFrequency(freq); + //写到数据中 + databaseOpr.writeConfig("freq", String.format("%.0f", freq), null); + } + + /** + * 根据消息号,生成对应的消息 + * + * @param order 消息号 + * @return FT8消息 + */ + public Ft8Message getFunctionCommand(int order) { + switch (order) { + //发射模式1,BG7YOY BG7YOZ OL50 + case 1: + resetTargetReport();//把给对方的信号报告记录复位成-100 + return new Ft8Message(1, 0, toCallsign.callsign, GeneralVariables.myCallsign + , GeneralVariables.getMyMaidenhead4Grid()); + //发射模式2,BG7YOY BG7YOZ -10 + case 2: + sentTargetReport = toCallsign.snr; + + return new Ft8Message(1, 0, toCallsign.callsign + , GeneralVariables.myCallsign, toCallsign.getSnr()); + //发射模式3,BG7YOY BG7YOZ R-10 + case 3: + sentTargetReport = toCallsign.snr; + return new Ft8Message(1, 0, toCallsign.callsign + , GeneralVariables.myCallsign, "R" + toCallsign.getSnr()); + //发射模式4,BG7YOY BG7YOZ RRR + case 4: + return new Ft8Message(1, 0, toCallsign.callsign + , GeneralVariables.myCallsign, "RR73"); + //发射模式5,BG7YOY BG7YOZ 73 + case 5: + return new Ft8Message(1, 0, toCallsign.callsign + , GeneralVariables.myCallsign, "73"); + //发射模式6,CQ BG7YOZ OL50 + case 6: + resetTargetReport();//把给对方的信号报告,接收到对方的信号报告记录复位成-100 + Ft8Message msg = new Ft8Message(1, 0, "CQ", GeneralVariables.myCallsign + , GeneralVariables.getMyMaidenhead4Grid()); + msg.modifier = GeneralVariables.toModifier; + return msg; + } + + return new Ft8Message("CQ", GeneralVariables.myCallsign + , GeneralVariables.getMyMaidenhead4Grid()); + } + + /** + * 生成指令序列 + */ + public void generateFun() { + //ArrayList functions = new ArrayList<>(); + GeneralVariables.noReplyCount = 0; + functionList.clear(); + for (int i = 1; i <= 6; i++) { + if (functionOrder == 6) {//如果当前的指令序列是6(CQ),那么就只生成一个消息 + functionList.add(new FunctionOfTransmit(6, getFunctionCommand(6), false)); + break; + } else { + functionList.add(new FunctionOfTransmit(i, getFunctionCommand(i), false)); + } + } + mutableFunctions.postValue(functionList); + setCurrentFunctionOrder(functionOrder);//设置当前消息 + } + + /** + * 为了最大限度兼容,把32位浮点转换成16位整型,有些声卡不支持32位的浮点。 + * @param buffer 32位浮点音频 + * @return 16位整型 + */ + private short[] float2Short(float[] buffer) { + short[] temp = new short[buffer.length + 8];//多出8个为0的数据包,是为了兼容QP-7C的RP2040音频判断 + for (int i = 0; i < buffer.length; i++) { + float x = buffer[i]; + if (x > 1.0) + x = 1.0f; + else if (x < -1.0) + x = -1.0f; + temp[i] = (short) (x * 32767.0); + } + return temp; + } + + //private void playFT8Signal(float[] buffer) { + private void playFT8Signal(Ft8Message msg) { + + if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//网络方式就不播放音频了 + Log.d(TAG, "playFT8Signal: 进入网络发射程序,等待音频发送。"); + + + if (onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 + onDoTransmitted.onTransmitByWifi(msg); + } + + + long now = System.currentTimeMillis(); + while (isTransmitting) {//等待音频数据包发送完毕再退出,以触发afterTransmitting + try { + Thread.sleep(1); + long current = System.currentTimeMillis() - now; + if (current > 13000) {//实际发射的时长 + isTransmitting = false; + break; + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + Log.d(TAG, "playFT8Signal: 退出网络音频发送。"); + afterPlayAudio(); + return; + } + + + //进入声卡模式 + float[] buffer; + buffer = GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency() + , GeneralVariables.audioSampleRate); + if (buffer == null) { + afterPlayAudio(); + return; + } + + Log.d(TAG, String.format("playFT8Signal: 准备声卡播放....位数:%s,采样率:%d" + , GeneralVariables.audioOutput32Bit ? "Float32" : "Int16" + , GeneralVariables.audioSampleRate)); + attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(); + + //myFormat = new AudioFormat.Builder().setSampleRate(FT8Common.SAMPLE_RATE) + myFormat = new AudioFormat.Builder().setSampleRate(GeneralVariables.audioSampleRate) + .setEncoding(GeneralVariables.audioOutput32Bit ? //浮点与整型 + AudioFormat.ENCODING_PCM_FLOAT : AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); + int mySession = 0; + audioTrack = new AudioTrack(attributes, myFormat + , GeneralVariables.audioOutput32Bit ? GeneralVariables.audioSampleRate * 15 * 4 + : GeneralVariables.audioSampleRate * 15 * 2//浮点与整型 + , AudioTrack.MODE_STATIC + , mySession); + + //区分32浮点和整型 + int writeResult; + if (GeneralVariables.audioOutput32Bit) { + writeResult = audioTrack.write(buffer, 0, buffer.length + , AudioTrack.WRITE_NON_BLOCKING); + } else { + short[] audio_data = float2Short(buffer); + writeResult = audioTrack.write(audio_data, 0, audio_data.length + , AudioTrack.WRITE_NON_BLOCKING); + } + + if (buffer.length > writeResult) { + Log.e(TAG, String.format("播放缓冲区不足:%d--->%d", buffer.length, writeResult)); + } + + //检查写入的结果,如果是异常情况,则直接需要释放资源 + if (writeResult == AudioTrack.ERROR_INVALID_OPERATION + || writeResult == AudioTrack.ERROR_BAD_VALUE + || writeResult == AudioTrack.ERROR_DEAD_OBJECT + || writeResult == AudioTrack.ERROR) { + //出异常情况 + Log.e(TAG, String.format("播放出错:%d", writeResult)); + afterPlayAudio(); + return; + } + audioTrack.setNotificationMarkerPosition(buffer.length); + audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { + @Override + public void onMarkerReached(AudioTrack audioTrack) { + afterPlayAudio(); + } + + @Override + public void onPeriodicNotification(AudioTrack audioTrack) { + + } + }); + if (audioTrack != null) { + audioTrack.play(); + audioTrack.setVolume(GeneralVariables.volumePercent);//设置播放的音量 + } + } + + /** + * 播放完声音后的处理动作。包括回调onAfterTransmit,用于关闭PTT + */ + private void afterPlayAudio() { + if (onDoTransmitted != null) { + onDoTransmitted.onAfterTransmit(getFunctionCommand(functionOrder), functionOrder); + } + isTransmitting = false; + mutableIsTransmitting.postValue(false); + if (audioTrack != null) { + audioTrack.release(); + audioTrack = null; + } + } + + //当通联成功时的动作 + private void doComplete() { + messageEndTime = UtcTimer.getSystemTime();//获取结束的时间 + + //如对方没有网格,就从历史呼号与网格对应表中查找 + toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); + + if (messageStartTime == 0) {//如果起始时间没有,就取现在的 + messageStartTime = UtcTimer.getSystemTime(); + } + + + //从历史记录中查信号报告 + //此处处理信号报告,是因为保存的信号报告经常与实际通联的信号报告不一致。 + //遍历接收到对方的信号报告 + for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { + Ft8Message message = GeneralVariables.transmitMessages.get(i); + if ((GeneralVariables.checkFun3(message.extraInfo) + || GeneralVariables.checkFun2(message.extraInfo)) + && (message.callsignFrom.equals(toCallsign.callsign) + && message.callsignTo.equals(GeneralVariables.myCallsign))) { + receiveTargetReport = getReportFromExtraInfo(message.extraInfo); + break; + } + } + //遍历我发送给对方的信号报告 + for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { + Ft8Message message = GeneralVariables.transmitMessages.get(i); + if ((GeneralVariables.checkFun3(message.extraInfo) + || GeneralVariables.checkFun2(message.extraInfo)) + && (message.callsignTo.equals(toCallsign.callsign) + && message.callsignFrom.equals(GeneralVariables.myCallsign))) { + sentTargetReport = getReportFromExtraInfo(message.extraInfo); + break; + } + } + + + messageEndTime = UtcTimer.getSystemTime(); + if (onDoTransmitted != null) {//用于保存通联记录 + onTransmitSuccess.doAfterTransmit(new QSLRecord( + messageStartTime, + messageEndTime, + GeneralVariables.myCallsign, + GeneralVariables.getMyMaidenhead4Grid(), + toCallsign.callsign, + toMaidenheadGrid, + sentTargetReport != -100 ? sentTargetReport : sendReport, + receiveTargetReport != -100 ? receiveTargetReport : receivedReport,//如果给对方的信号报告是不是-100,就用发给对方的信号报告记录 + "FT8", + GeneralVariables.band, + Math.round(GeneralVariables.getBaseFrequency()) + )); + + GeneralVariables.addQSLCallsign(toCallsign.callsign);//把通联成功的呼号添加到列表中 + ToastMessage.show(String.format("QSO : %s , at %s", toCallsign.callsign + , BaseRigOperation.getFrequencyAllInfo(GeneralVariables.band))); + } + + } + + /** + * 设置当前要发射的指令顺序 + * + * @param order 顺序 + */ + public void setCurrentFunctionOrder(int order) { + functionOrder = order; + for (int i = 0; i < functionList.size(); i++) { + functionList.get(i).setCurrentOrder(order); + } + if (order == 1) { + resetTargetReport();//复位信号报告 + } + if (order == 4 || order == 5) { + updateQSlRecordList(order, toCallsign); + } + mutableFunctions.postValue(functionList); + } + + + /** + * 当目标是复合呼号(非标准信号),JTDX回复可能会缩短 + * + * @param fromCall 对方的呼号 + * @param toCall 我的目标呼号 + * @return 是不是 + */ + private boolean checkCallsignIsCallTo(String fromCall, String toCall) { + if (toCall.contains("/")) {//当对方的呼号在斜线时,JTDX会把/后面的字符去掉 + return toCall.contains(fromCall); + } else { + return fromCall.equals(toCall); + } + } + + /** + * 检查消息中from中有目标呼号的数量。当有目标呼号呼叫我的消息,返回0, + * @param messages 消息列表 + * @return 0:有目标呼叫我的,1:没有任何目标呼号发出的消息,>1:有目标呼号呼叫别人的消息 + */ + private int checkTargetCallMe(ArrayList messages){ + int fromCount=1; + for (int i = messages.size() - 1; i >= 0; i--) { + Ft8Message ft8Message = messages.get(i); + if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 + if (toCallsign == null) { + continue; + } + if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { + return 0; + } + if (checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)){ + fromCount++;//计数器,from是目标呼号的情况 + } + } + return fromCount; + } + /** + * 检测本消息列表中对方回复消息的序号,如果没有,返回-1 + * + * @param messages 消息列表 + * @return 消息的序号 + */ + private int checkFunctionOrdFromMessages(ArrayList messages) { + for (int i = messages.size() - 1; i >= 0; i--) { + Ft8Message ft8Message = messages.get(i); + if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 + if (toCallsign == null) { + continue; + } + //是双方的呼叫信息 + if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { + //--TODO ----检查起始时间是不是0,如果是0,补充起始时间。因为有的呼叫会越过第一步 + + //检测是不是对方给我的信号报告 + if (GeneralVariables.checkFun3(ft8Message.extraInfo) + || GeneralVariables.checkFun2(ft8Message.extraInfo)) { + //从消息中取信号报告,如果不正确(-100),那么就取消息中的信号报告 + receivedReport = getReportFromExtraInfo(ft8Message.extraInfo); + receiveTargetReport = receivedReport;//对方给我的信号报告,要保存下来 + if (receivedReport == -100) {//如果不正确,就取消息的报告 + receivedReport = ft8Message.report; + } + } + sendReport = messages.get(i).snr;//把接收到的信号保存下来 + + int order = GeneralVariables.checkFunOrder(ft8Message);//检查消息的序号 + if (order != -1) return order;//说明成功解析出序号 + } + } + + return -1;//说明没找到消息 + } + + /** + * 从扩展消息中获取对方给的信号报告,获取失败,值-100 + * + * @param extraInfo 扩展消息 + * @return 信号报告 + */ + private int getReportFromExtraInfo(String extraInfo) { + String s = extraInfo.replace("R", "").trim(); + try { + return Integer.parseInt(s); + } catch (Exception e) { + return -100; + } + } + + /** + * 检查有没有人CQ我,或我关注的呼号在CQ + * + * @param messages 消息列表 + * @return false=没有符合的消息,TRUE=有符合的消息 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + private boolean checkCQMeOrFollowCQMessage(ArrayList messages) { + //此message是刚刚解码出的消息 + //检查CQ我,且是我呼叫的目标 + for (int i = messages.size() - 1; i >= 0; i--) {//此处是检查有没有CQ我。(TO:ME,且不能是73) + Ft8Message msg = messages.get(i); + if (msg.getSequence() == sequential) {//如果与发射时序相同,不理会 + continue; + } + if (msg.band != GeneralVariables.band) {//如果消息不在相同的波段内,不呼叫 + continue; + } + if (GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//如果是在过滤范围内的呼叫,不理会 + continue; + } + + if ((msg.getCallsignTo().equals(GeneralVariables.myCallsign) + && !GeneralVariables.checkFun5(msg.extraInfo))) {//不能是73 + //设置发射之前,确定消息的序号,避免从头开始 + setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz + , msg.getSequence(), msg.snr) + , GeneralVariables.checkFunOrder(msg) + 1 + , msg.extraInfo); + return true; + } + } + + + //如果不自动呼叫我关注的消息,就退出 + if (!GeneralVariables.autoCallFollow) { + return false; + } + + if (toCallsign == null) { + return false; + } + //当已经有目标呼号的时候,不对关注的呼号做反应 + if (toCallsign.haveTargetCallsign()) { + return false; + } + + //我关注的呼号次之,!!!到关注的消息列表中找 + //此处是检查关注的呼号在CQ。(TO:CQ,且不能本次通联能成功的呼号) + for (int i = GeneralVariables.transmitMessages.size() - 1; i >= 0; i--) { + Ft8Message msg = GeneralVariables.transmitMessages.get(i); + if (msg.getSequence() == sequential) {//如果与发射时序相同,不理会 + continue; + } + if (msg.band != GeneralVariables.band) {//如果消息不在相同的波段内,不呼叫 + continue; + } + + //处于CQ,FROM是我的关注呼号,并且不在通联成功的呼号列表中 + if ((msg.checkIsCQ()//在CQ + && ((GeneralVariables.autoCallFollow && GeneralVariables.autoFollowCQ)//自动呼叫CQ + || GeneralVariables.callsignInFollow(msg.getCallsignFrom()))//是我关注的 + && !GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())//之前没有联通成功过 + && !msg.callsignFrom.equals(GeneralVariables.myCallsign))) {//不是我自己 + + resetTargetReport(); + setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz + , msg.getSequence(), msg.snr), 1, msg.extraInfo); + + return true; + } + } + + return false; + + } + + + public void updateQSlRecordList(int order, TransmitCallsign toCall) { + if (toCall == null) return; + if (toCall.callsign.equals("CQ")) return; + + QSLRecord record = GeneralVariables.qslRecordList.getRecordByCallsign(toCall.callsign); + if (record == null) { + toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); + record = GeneralVariables.qslRecordList.addQSLRecord(new QSLRecord( + messageStartTime, + messageEndTime, + GeneralVariables.myCallsign, + GeneralVariables.getMyMaidenhead4Grid(), + toCallsign.callsign, + toMaidenheadGrid, + sentTargetReport != -100 ? sentTargetReport : sendReport, + receiveTargetReport != -100 ? receiveTargetReport : receivedReport,//如果给对方的信号报告是不是-100,就用发给对方的信号报告记录 + "FT8", + GeneralVariables.band, + Math.round(GeneralVariables.getBaseFrequency() + ))); + } + //根据消息序列更新内容 + switch (order) { + case 1://更新网格,和对方消息的SNR + record.setToMaidenGrid(toMaidenheadGrid); + record.setSendReport(sentTargetReport != -100 ? sentTargetReport : sendReport); + GeneralVariables.qslRecordList.deleteIfSaved(record); + break; + + case 2://更新对方返回的信号报告,和对方的信号报告 + case 3: + record.setSendReport(sentTargetReport != -100 ? sentTargetReport : sendReport); + record.setReceivedReport(receiveTargetReport != -100 ? receiveTargetReport : receivedReport); + GeneralVariables.qslRecordList.deleteIfSaved(record); + break; + + //当RR73或73的状态下,就保存日志。 + case 4: + case 5: + if (!record.saved) { + doComplete();//保存到数据库 + record.saved = true; + } + + break; + } + + } + + /** + * 从关注列表解码的消息中,此处是变化发射程序的入口 + * + * @param msgList 消息列表 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + public void parseMessageToFunction(ArrayList msgList) { + if (GeneralVariables.myCallsign.length() < 3) { + return; + } + if (msgList.size() == 0) return;//没有消息解析,返回 + + if (msgList.get(0).getSequence() == sequential) { + return; + } + ArrayList messages =new ArrayList<>(msgList);//防止线程冲突 + + + int newOrder = checkFunctionOrdFromMessages(messages);//检查消息中对方回复的消息序号,-1为没有收到 + if (newOrder != -1) {//如果有消息序号,说明有回应,复位错误计数器 + GeneralVariables.noReplyCount = 0; + } + + //更新一下通联的列表检查是不是在通联列表中,如果没有记录下来,就保存 + updateQSlRecordList(newOrder, toCallsign); + + + // 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) + // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 + // 或我是RR73(4),且对方开始呼叫别人了,解决RR73卡死的问题 + if (newOrder == 5 + || (functionOrder == 5 && newOrder == -1)// 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) + || (functionOrder == 4 && + (GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit * 2) + && (GeneralVariables.noReplyLimit > 0)) // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 + || (functionOrder ==4 && checkTargetCallMe(messages)>1) + ) { // 或我是RR73(4),且对方开始呼叫别人了 + //doComplete();//做保存的动作 + //进入到CQ状态 + resetToCQ(); + + //加入检查消息中有没有呼号我的,或关注的呼号在CQ + checkCQMeOrFollowCQMessage(messages); + setCurrentFunctionOrder(functionOrder);//设置当前消息 + mutableFunctionOrder.postValue(functionOrder); + return; + } + + + if (newOrder != -1) {//说明收到消息,且未完成通联 + //原来是newOrder == 1,但有的时候,对方直接给信号报告,也就是消息2. + if (newOrder == 1 || newOrder == 2) {//说明是别人第一次回复我 + resetTargetReport();//把信号报告复位一下 + generateFun(); + } + + functionOrder = newOrder + 1;//执行下一个序号的消息 + mutableFunctions.postValue(functionList); + mutableFunctionOrder.postValue(functionOrder); + setCurrentFunctionOrder(functionOrder);//设置当前消息 + return; + } + + + //到此位置,我还没有在6号消息状态,检查看有没有人呼叫我 + // 2022-09-22如果这时有人呼叫我,或自动跟踪状态,我就设置新的发射消息列表 + if (checkCQMeOrFollowCQMessage(messages)) { + return; + } + + + //到此位置,说明没有收到回复的消息 + //之明如果我是在CQ,那么newOrder必然是-1 + if (functionOrder == 6) {//我处于CQ状态 + checkCQMeOrFollowCQMessage(messages); + return; + } + + + //到此位置,说明没有回应,错误次数要加1,弱信号检测不记无回应 + if (!messages.get(0).isWeakSignal) { + GeneralVariables.noReplyCount++; + } + //如果超出无反应限定值,复位到CQ状态 + if ((GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit) && (GeneralVariables.noReplyLimit > 0)) { + //检查关注消息列表,如果没有新的CQ,就进入到CQ状态,如果有,就转入到呼叫新的目标。 + if (!getNewTargetCallsign(messages)) {//检查关注列表中的CQ消息,如果有新的目标,返回TRUE; + functionOrder = 6; + toCallsign.callsign = "CQ"; + } + generateFun(); + setCurrentFunctionOrder(functionOrder);//设置当前消息 + mutableToCallsign.postValue(toCallsign); + mutableFunctionOrder.postValue(functionOrder); + + } + + } + + /** + * 检查关注列表中,有没有正在CQ的消息,且不是我现在的目标呼号 + * + * @param messages 关注的消息列表 + * @return 目标呼号,没有返回NULL + */ + public boolean getNewTargetCallsign(ArrayList messages) { + if (toCallsign == null) return false; + for (int i = messages.size() - 1; i >= 0; i--) { + Ft8Message ft8Message = messages.get(i); + if (ft8Message.band != GeneralVariables.band) {//如果消息不在相同的波段内,不理会 + continue; + } + //不是CQ,不理会 + if (!ft8Message.checkIsCQ()) { + continue; + } + //不是当前的目标呼号,且之前没有通联成功过 + if ((!ft8Message.getCallsignFrom().equals(toCallsign.callsign) + && (!GeneralVariables.checkQSLCallsign(ft8Message.getCallsignFrom())))) //之前没有联通成功过 + { + functionOrder = 1; + toCallsign.callsign = ft8Message.getCallsignFrom(); + return true; + } + + + } + return false; + } + + public boolean isSynFrequency() { + return GeneralVariables.synFrequency; + } + + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + if (!this.activated) {//强制关闭发射 + setTransmitting(false); + } + mutableIsActivated.postValue(activated); + } + + public boolean isTransmitting() { + return isTransmitting; + } + + public void setTransmitting(boolean transmitting) { + if (GeneralVariables.myCallsign.length() < 3 && transmitting) { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); + return; + } + + if (!transmitting) {//停止发射 + if (audioTrack != null) { + if (audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED) { + audioTrack.pause(); + } + if (onDoTransmitted != null) {//通知一下,已经不发射了 + onDoTransmitted.onAfterTransmit(getFunctionCommand(functionOrder), functionOrder); + } + } + } + + mutableIsTransmitting.postValue(transmitting); + isTransmitting = transmitting; + } + + /** + * 复位发射程序到6,时序也会改变 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + public void restTransmitting() { + if (GeneralVariables.myCallsign.length() < 3) { + return; + } + //要判断我的呼号类型,才能确定i3n3 !!! + int i3 = GenerateFT8.checkI3ByCallsign(GeneralVariables.myCallsign); + setTransmit(new TransmitCallsign(i3, 0, "CQ", UtcTimer.getNowSequential()) + , 6, ""); + + } + + /** + * 把给对方的信号记录复位成-100; + */ + public void resetTargetReport() { + receiveTargetReport = -100; + sentTargetReport = -100; + } + + /** + * 复位发射程序到6,不会改变时序 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + public void resetToCQ() { + resetTargetReport(); + if (toCallsign == null) { + //要判断我的呼号类型,才能确定i3n3 !!! + int i3 = GenerateFT8.checkI3ByCallsign(GeneralVariables.myCallsign); + setTransmit(new TransmitCallsign(i3, 0, "CQ", (UtcTimer.getNowSequential() + 1) % 2) + , 6, ""); + } else { + functionOrder = 6; + toCallsign.callsign = "CQ"; + mutableToCallsign.postValue(toCallsign);//设定呼叫的目标 + generateFun(); + } + } + + /** + * 设置发射时间延迟,这个延迟时间,也是给上一个周期解码的一个时间 + * + * @param sec 毫秒 + */ + public void setTimer_sec(int sec) { + utcTimer.setTime_sec(sec); + } + + public boolean isTransmitFreeText() { + return transmitFreeText; + } + + public void setFreeText(String freeText) { + this.freeText = freeText; + } + + public void setTransmitFreeText(boolean transmitFreeText) { + this.transmitFreeText = transmitFreeText; + if (transmitFreeText) { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.trans_free_text_mode)); + } else { + ToastMessage.show((GeneralVariables.getStringFromResource(R.string.trans_standard_messge_mode))); + } + } + + + private static class DoTransmitRunnable implements Runnable { + FT8TransmitSignal transmitSignal; + + public DoTransmitRunnable(FT8TransmitSignal transmitSignal) { + this.transmitSignal = transmitSignal; + } + + @SuppressLint("DefaultLocale") + @Override + public void run() { + //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来 + if (transmitSignal.functionOrder == 1 || transmitSignal.functionOrder == 2) {//当消息处于1或2时,说明开始了通联 + transmitSignal.messageStartTime = UtcTimer.getSystemTime(); + } + if (transmitSignal.messageStartTime == 0) {//如果起始时间没有,就取现在的 + transmitSignal.messageStartTime = UtcTimer.getSystemTime(); + } + + //用于显示将要发射的消息内容 + Ft8Message msg; + if (transmitSignal.transmitFreeText) { + msg = new Ft8Message("CQ", GeneralVariables.myCallsign, transmitSignal.freeText); + msg.i3 = 0; + msg.n3 = 0; + } else { + msg = transmitSignal.getFunctionCommand(transmitSignal.functionOrder); + } + msg.modifier = GeneralVariables.toModifier; + + if (transmitSignal.onDoTransmitted != null) { + //此处用于处理PTT等事件 + transmitSignal.onDoTransmitted.onBeforeTransmit(msg, transmitSignal.functionOrder); + } + + transmitSignal.isTransmitting = true; + transmitSignal.mutableIsTransmitting.postValue(true); + + + transmitSignal.mutableTransmittingMessage.postValue(String.format(" (%.0fHz) %s" + , GeneralVariables.getBaseFrequency() + , msg.getMessageText())); + //生成信号 +// float[] buffer=GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency()); +// if (buffer==null) { +// return; +// } + + //电台动作可能有要有个延迟时间,所以时间并不一定完全准确 + try {//给电台一个100毫秒的响应时间 + Thread.sleep(GeneralVariables.pttDelay);//给PTT指令后,电台一个响应时间,默认100毫秒 + } catch (InterruptedException e) { + e.printStackTrace(); + } + +// if (transmitSignal.onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 +// transmitSignal.onDoTransmitted.onAfterGenerate(buffer); +// } + //播放音频 + //transmitSignal.playFT8Signal(buffer); + transmitSignal.playFT8Signal(msg); + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java new file mode 100644 index 0000000..5b0aeb7 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java @@ -0,0 +1,74 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * FT8通联的6步 + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.Ft8Message; + +public class FunctionOfTransmit { + private int functionOrder;//消息的序号 + private String functionMessage;//消息内容 + private boolean completed;//是否完成 + private boolean isCurrentOrder;//是不是当前要发射的消息 + private Ft8Message ft8Message; + +// /** +// * 老的发送消息方法 +// * @param functionOrder 消息序号 +// * @param functionMessage 消息内容 +// * @param completed 是否结束 +// */ +// @Deprecated +// public FunctionOfTransmit(int functionOrder, String functionMessage, boolean completed) { +// this.functionOrder = functionOrder; +// this.functionMessage = functionMessage; +// this.completed = completed; +// } + + /** + * 新版发送消息方法 + * @param functionOrder 消息序号 + * @param message FT8消息 + * @param completed 是否结束 + */ + public FunctionOfTransmit(int functionOrder, Ft8Message message, boolean completed) { + this.functionOrder = functionOrder; + ft8Message=message; + this.completed = completed; + this.functionMessage = message.getMessageText(); + } + + public int getFunctionOrder() { + return functionOrder; + } + + public void setFunctionOrder(int functionOrder) { + this.functionOrder = functionOrder; + } + + public String getFunctionMessage() { + return functionMessage; + } + + public void setFunctionMessage(String functionMessage) { + this.functionMessage = functionMessage; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public boolean isCurrentOrder() { + return isCurrentOrder; + } + + public void setCurrentOrder(int currentOrder) { + isCurrentOrder = currentOrder==functionOrder; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java new file mode 100644 index 0000000..675252b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java @@ -0,0 +1,259 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 生成FT8音频信号的类。音频数据是32位的浮点数组。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ft8signal.FT8Package; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +public class GenerateFT8 { + private static final String TAG = "GenerateFT8"; + private static final int FTX_LDPC_K = 91; + public static final int FTX_LDPC_K_BYTES = (FTX_LDPC_K + 7) / 8; + private static final int FT8_NN = 79; + private static final float FT8_SYMBOL_PERIOD = 0.160f; + private static final float FT8_SYMBOL_BT = 2.0f; + private static final float FT8_SLOT_TIME = 15.0f; + private static final int Ft8num_samples = 15 * 12000; + private static final float M_PI = 3.14159265358979323846f; + + public static final int num_tones = FT8_NN;//符号数量:FT8是79个,FT4是105个。 + public static final float symbol_period = FT8_SYMBOL_PERIOD;//FT8_SYMBOL_PERIOD=0.160f + private static final float symbol_bt = FT8_SYMBOL_BT;//FT8_SYMBOL_BT=2.0f + private static final float slot_time = FT8_SLOT_TIME;//FT8_SLOT_TIME=15f + //public static int sample_rate = 48000;//采样率 + //public static int sample_rate = 12000;//采样率 + + + static { + System.loadLibrary("ft8cn"); + } + + + public static int checkI3ByCallsign(String callsign) { + String substring = callsign.substring(callsign.length() - 2); + if (substring.equals("/P")) { + if (callsign.length() <= 8) { + return 2;//i3=2消息 + } else { + return 4;//说明时非标准呼号 + } + } + if (substring.equals("/R")) { + if (callsign.length() <= 8) { + return 1;//i3=2消息 + } else { + return 4;//说明时非标准呼号 + } + } + if (callsign.contains("/")) {//除了/P /R以外,其余的都是非标准呼号 + return 4; + } + if (callsign.length() > 6) {//呼号大于6位,也是非标准呼号 + return 4; + } + if (callsign.length() == 0) {//没有呼号,就是自由文本 + return 0; + } + return 1; + } + + public static String byteToBinString(byte[] data) { + if (data == null) { + return ""; + } + StringBuilder string = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + string.append(String.format(",%8s", Integer.toBinaryString(data[i] & 0xff)).replace(" ", "0")); + } + return string.toString(); + } + + public static String byteToHexString(byte[] data) { + StringBuilder string = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + string.append(String.format(",%02X", data[i])); + } + return string.toString(); + } + + + /** + * 检查是不是标准呼号 + * + * @param callsign 呼号 + * @return 是不是 + */ + public static boolean checkIsStandardCallsign(String callsign) { + String temp; + if (callsign.endsWith("/P") || callsign.endsWith("/R")){ + temp=callsign.substring(0,callsign.length()-2); + }else { + temp=callsign; + } + // Log.e(TAG, "checkIsStandardCallsign: 呼号:"+temp.matches("[a-zA-Z0-9]?[a-zA-Z][0-9][a-zA-Z][a-zA-Z0-9]?[a-zA-Z]") ); + //return temp.matches("[A-Z0-9]?[A-Z][0-9][A-Z][A-Z0-9]?[A-Z]?"); + + return temp.matches("[A-Z0-9]?[A-Z0-9][0-9][A-Z][A-Z0-9]?[A-Z]?"); + + //FT8的认定:标准业余呼号由一个或两个字符的前缀组成,其中至少一个必须是字母,后跟一个十进制数字和最多三个字母的后缀。 + } + + /** + * 检查是不是信号报告 + * + * @param extraInfo 扩展消息 + * @return 是不是 + */ + private static boolean checkIsReport(String extraInfo) { + if (extraInfo.equals("73") || extraInfo.equals("RRR") + || extraInfo.equals("RR73")||extraInfo.equals("")) { + return false; + } + return !extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]"); + } + + public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate){ + return generateFt8(msg,frequency,sample_rate,true); + } + + /** + * 生成FT8信号 + * @param msg 消息 + * @param frequency 频率 + * @param sample_rate 采样率 + * @param hasModifier 是否有修饰符 + * @return + */ + public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate,boolean hasModifier) { + if (msg.callsignFrom.length()<3){ + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); + return null; + } + // 首先,将文本数据打包为二进制消息,共12个字节 + byte[] packed = new byte[FTX_LDPC_K_BYTES]; + //把"<>"去掉 + msg.callsignTo = msg.callsignTo.replace("<", "").replace(">", ""); + msg.callsignFrom = msg.callsignFrom.replace("<", "").replace(">", ""); + if (hasModifier) { + msg.modifier = GeneralVariables.toModifier;//修饰符 + }else { + msg.modifier=""; + } + //msg.callsignTo="CQ AzCz"; + + //判定用非标准呼号i3=4的条件: + //1.FROMCALL为非标准呼号 ,且 符合2或3 + //2.扩展消息时 网格、RR73,RRR,73 + //3.CQ,QRZ,DE + + + + if (msg.i3 != 0) {//目前只支持i3=1,i3=2,i3=4,i3=0 && n3=0 + if (!checkIsStandardCallsign(msg.callsignFrom) + && (!checkIsReport(msg.extraInfo) || msg.checkIsCQ())) { + msg.i3 = 4; + } else if (msg.callsignFrom.endsWith("/P")||(msg.callsignTo.endsWith("/P"))) { + msg.i3 = 2; + } else { + msg.i3 = 1; + } + } + + if (msg.i3 == 1 || msg.i3 == 2) { + packed = FT8Package.generatePack77_i1(msg); + } else if (msg.i3 == 4) {//说明是非标准呼号 + packed = FT8Package.generatePack77_i4(msg); + } else { + packFreeTextTo77(msg.getMessageText(), packed); + } + + return generateFt8ByA91(packed,frequency,sample_rate); + + /* + // 其次,将二进制消息编码为FSK音调序列,79个字节 + byte[] tones = new byte[num_tones]; // 79音调(符号)数组 + //此处是88个字节(91+7)/8,可以使用a91生成音频 + ft8_encode(packed, tones); + + // 第三,将FSK音调转换为音频信号b + + + int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 + + //float[] signal = new float[Ft8num_samples]; + float[] signal = new float[num_samples]; + + //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 + //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 + for (int i = 0; i < num_samples; i++)//把数据全部静音。 + { + signal[i] = 0; + } + + // 用79个字节符号,生成FT8音频 + synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); + for (int i = 0; i < num_samples; i++)//把数据全部静音。 + { + if (signal[i]>1.0||signal[i]<-1.0){ + Log.e(TAG, "generateFt8: "+signal[i] ); + } + } + return signal; + */ + } + + public static float[] generateFt8ByA91(byte[] a91, float frequency,int sample_rate){ + byte[] tones = new byte[num_tones]; // 79音调(符号)数组 + //此处是12个字节(91+7)/8,可以使用a91生成音频 + ft8_encode(a91, tones); + + // 第三,将FSK音调转换为音频信号b + + + int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 + + + + //int num_silence = (int) ((slot_time * sample_rate - num_samples) / 2); // 两端填充静音到15秒(15*12000-num_samples)/2(1.18秒的样本数) + //int num_total_samples = num_silence + num_samples + num_silence; // 填充信号中的样本数2.36秒+12.64秒=15秒的样本数 + + //float[] signal = new float[Ft8num_samples]; + float[] signal = new float[num_samples]; + + //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 + //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 + for (int i = 0; i < num_samples; i++)//把数据全部静音。 + { + signal[i] = 0; + } + + // 用79个字节符号,生成FT8音频 + synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); +// for (int i = 0; i < num_samples; i++)//把数据全部静音。 +// { +// if (signal[i]>1.0||signal[i]<-1.0){ +// Log.e(TAG, "generateFt8: "+signal[i] ); +// } +// } + return signal; + } + + + private static native int packFreeTextTo77(String msg, byte[] c77); + + private static native int pack77(String msg, byte[] c77); + + private static native void ft8_encode(byte[] payload, byte[] tones); + + private static native void synth_gfsk(byte[] symbols, int n_sym, float f0, + float symbol_bt, float symbol_period, + int signal_rate, float[] signal, int offset); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java new file mode 100644 index 0000000..c519e83 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java @@ -0,0 +1,14 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 发射的回调 + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.Ft8Message; + +public interface OnDoTransmitted { + void onBeforeTransmit(Ft8Message message,int functionOder); + void onAfterTransmit(Ft8Message message, int functionOder); + void onTransmitByWifi(Ft8Message message); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java new file mode 100644 index 0000000..e72c664 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java @@ -0,0 +1,12 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 发射结束后的回调 + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.log.QSLRecord; + +public interface OnTransmitSuccess { + void doAfterTransmit(QSLRecord qslRecord); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java new file mode 100644 index 0000000..257a3a9 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java @@ -0,0 +1,100 @@ +package com.bg7yoz.ft8cn.ft8transmit; + +/** + * 记录QSO的类,用于保存数据库。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class QSLRecord { + private long startTime;//起始时间 + private long endTime;//结束时间 + + private String myCallsign;//我的呼号 + private String myMaidenGrid;//我的网格 + private String toCallsign;//对方的呼号 + private String toMaidenGrid;//对方的网格 + private int sendReport;//对方收到我的报告(也就是我发送的信号强度) + private int receivedReport;//我收到对方的报告(也就是SNR) + private String mode="FT8"; + + private long bandFreq;//发射的波段 + private int frequency;//发射的频率 + + + public QSLRecord(long startTime, long endTime, String myCallsign, String myMaidenGrid + , String toCallsign, String toMaidenGrid, int sendReport, int receivedReport + , String mode, long bandFreq, int frequency) { + this.startTime = startTime; + this.endTime = endTime; + this.myCallsign = myCallsign; + this.myMaidenGrid = myMaidenGrid; + this.toCallsign = toCallsign; + this.toMaidenGrid = toMaidenGrid; + this.sendReport = sendReport; + this.receivedReport = receivedReport; + this.mode = mode; + this.bandFreq = bandFreq; + this.frequency = frequency; + } + + @Override + public String toString() { + return "QSLRecord{" + + "startTime=" + startTime + + ", endTime=" + endTime + + ", myCallsign='" + myCallsign + '\'' + + ", myMaidenGrid='" + myMaidenGrid + '\'' + + ", toCallsign='" + toCallsign + '\'' + + ", toMaidenGrid='" + toMaidenGrid + '\'' + + ", sendReport=" + sendReport + + ", receivedReport=" + receivedReport + + ", mode='" + mode + '\'' + + ", bandFreq=" + bandFreq + + ", frequency=" + frequency + + '}'; + } + + public long getEndTime() { + return endTime; + } + + public String getToCallsign() { + return toCallsign; + } + + public String getToMaidenGrid() { + return toMaidenGrid; + } + + public String getMode() { + return mode; + } + + public long getBandFreq() { + return bandFreq; + } + + public int getFrequency() { + return frequency; + } + + public long getStartTime() { + return startTime; + } + + public String getMyCallsign() { + return myCallsign; + } + + public String getMyMaidenGrid() { + return myMaidenGrid; + } + + public int getSendReport() { + return sendReport; + } + + public int getReceivedReport() { + return receivedReport; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java new file mode 100644 index 0000000..538e059 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java @@ -0,0 +1,100 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 通联记录的列表 + * @author BGY70Z + * @date 2023-03-20 + */ + +import com.bg7yoz.ft8cn.log.QSLRecord; + +import java.util.ArrayList; + +public class QslRecordList extends ArrayList { + + /** + * 根据呼号查是否有通联记录 + * @param callsign 呼号 + * @return 记录,没有则为空 + */ + public QSLRecord getRecordByCallsign(String callsign){ + for (int i = this.size()-1; i >=0 ; i--) { + if (this.get(i).getToCallsign().equals(callsign)){ + return this.get(i); + } + } + return null; + } + + /** + * 按照呼号查找,是否有通联记录,且保存过。如果没有记录,视作没保存过。 + * @param callsign 呼号 + * @return 是否保存过 + */ + public boolean getSavedRecByCallsign(String callsign){ + QSLRecord record=getRecordByCallsign(callsign); + if (record==null){ + return false; + }else { + return record.saved; + } + } + + /** + * 添加通联过的记录,如果已经存在,就更新记录 + * @param record 通联记录 + * @return 通联记录 + */ + public QSLRecord addQSLRecord(QSLRecord record){ + if (record.getToCallsign().equals("CQ")) return null; + //清除已经保存过的通联记录 + //for (int i = this.size()-1; i >=0 ; i--) { + // if (this.get(i).getToCallsign().equals(record.getToCallsign())){ + // if (this.get(i).saved){ + // this.remove(i); + // } + // } + //} + //找一下看有没有已经在列表中,但还没有保存的记录 + QSLRecord oldRecord= getRecordByCallsign(record.getToCallsign()); + if (oldRecord==null){ + this.add(record); + return record; + }else { + oldRecord.update(record); + } + return oldRecord; + } + + /** + * 删除已经保存过的呼号 + * @param record + */ + public void deleteIfSaved(QSLRecord record){ + //清除已经保存过的通联记录 + for (int i = this.size()-1; i >=0 ; i--) { + if (this.get(i).getToCallsign().equals(record.getToCallsign())){ + if (this.get(i).saved){ + this.remove(i); + } + } + } + } + + public String toHTML(){ + StringBuilder html=new StringBuilder(); + for (int i = 0; i < this.size(); i++) { + if (i%2==0) { + html.append(""); + html.append(String.format("%s", this.get(i).toHtmlString())); + html.append("
\n\n"); + }else { + html.append(">"); + html.append(String.format("%s", this.get(i).toHtmlString())); + html.append("
\n\n"); + } + + } + return html.toString(); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java new file mode 100644 index 0000000..fa8f803 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java @@ -0,0 +1,59 @@ +package com.bg7yoz.ft8cn.ft8transmit; +/** + * 呼叫过程所记录的呼号信息 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; + +public class TransmitCallsign { + private static final String TAG="TransmitCallsign"; + public String callsign; + public float frequency; + public int sequential; + public int snr; + public int i3; + public int n3; + public String dxcc; + public int cqZone; + public int itu; + + public TransmitCallsign(int i3,int n3,String callsign, int sequential) { + this.callsign = callsign; + this.sequential = sequential; + this.i3=i3; + this.n3=n3; + } + + public TransmitCallsign(int i3,int n3,String callsign, float frequency + , int sequential, int snr) { + this.callsign = callsign; + this.frequency = frequency; + this.sequential = sequential; + this.snr = snr; + this.i3=i3; + this.n3=n3; + + } + + /** + * 当目标呼号为空,或CQ,说明没有目标呼号 + * @return 是否有目标呼号 + */ + public boolean haveTargetCallsign(){ + if (callsign==null){ + return false; + } + return !callsign.equals("CQ"); + } + + @SuppressLint("DefaultLocale") + public String getSnr(){ + if (snr>0){ + return String.format("+%d",snr); + }else { + return String.format("%d",snr); + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java new file mode 100644 index 0000000..98da410 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java @@ -0,0 +1,118 @@ +package com.bg7yoz.ft8cn.grid_tracker; +/** + * 网格追踪中每个连线的窗口界面。包含各类型分区图标。与我有关的通联,文字是红色的。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.graphics.Paint; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.OverlayWithIW; +import org.osmdroid.views.overlay.infowindow.InfoWindow; + +public class GridInfoWindow extends InfoWindow { + public static final int UNDEFINED_RES_ID = 0; + + private final TextView titleView; + private final TextView descriptionView; + private final TextView subDescriptionView; + + + @SuppressLint("UseCompatLoadingForDrawables") + public GridInfoWindow(int layoutResId, MapView mapView, Ft8Message msg) { + super(layoutResId, mapView); + //setResIds(mapView.getContext()); + titleView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_title); + descriptionView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_description); + subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_info_bubble_subdescription); + ImageView fromDxccImage = (ImageView) this.mView.findViewById(R.id.track_from_dxcc_image); + ImageView fromItuImage = (ImageView) this.mView.findViewById(R.id.track_from_itu_image); + ImageView fromCqImage = (ImageView) this.mView.findViewById(R.id.track_from_cq_image); + ImageView toDxccImage = (ImageView) this.mView.findViewById(R.id.track_to_dxcc_image); + ImageView toItuImage = (ImageView) this.mView.findViewById(R.id.track_to_itu_image); + ImageView toCqImage = (ImageView) this.mView.findViewById(R.id.track_to_cq_image); + ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerGridInfoConstraintLayout); + + if (!msg.fromDxcc) fromDxccImage.setVisibility(View.GONE); + if (!msg.fromItu) fromItuImage.setVisibility(View.GONE); + if (!msg.fromCq) fromCqImage.setVisibility(View.GONE); + if (!msg.toDxcc) toDxccImage.setVisibility(View.GONE); + if (!msg.toItu) toItuImage.setVisibility(View.GONE); + if (!msg.toCq) toCqImage.setVisibility(View.GONE); + + + //查是不是在本波段内通联成功过的呼号 + if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//如果在数据库中,划线 + titleView.setPaintFlags( + titleView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else {//如果不在数据库中,去掉划线 + titleView.setPaintFlags( + titleView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); + } + boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom()); + + //是否有与我呼号有关的消息 + if (msg.inMyCall()) { + layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); + titleView.setTextColor(mapView.getResources().getColor( + R.color.message_in_my_call_text_color)); + } else if (otherBandIsQso) { + //设置在别的波段通联过的消息颜色 + titleView.setTextColor(mapView.getResources().getColor( + R.color.fromcall_is_qso_text_color)); + } else { + titleView.setTextColor(mapView.getResources().getColor( + R.color.message_text_color)); + } + + + this.mView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == 1) { + GridInfoWindow.this.close(); + } + return true; + } + }); + } + + + @Override + public void onOpen(Object item) { + OverlayWithIW overlay = (OverlayWithIW) item; + String title = overlay.getTitle(); + if (title == null) { + title = ""; + } + + if (this.mView == null) { + Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); + } else { + titleView.setText(title); + String snippet = overlay.getSnippet(); + //Spanned snippetHtml = Html.fromHtml(snippet); + descriptionView.setText(snippet); + String subDesc = overlay.getSubDescription(); + subDescriptionView.setText(subDesc); + + } + } + + @Override + public void onClose() { + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java new file mode 100644 index 0000000..1b352be --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java @@ -0,0 +1,158 @@ +package com.bg7yoz.ft8cn.grid_tracker; +/** + * 网格追踪中Marker(网格)的消息窗口。包括各分区的图标,点击呼叫的按钮。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.graphics.Paint; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.OverlayWithIW; +import org.osmdroid.views.overlay.infowindow.InfoWindow; + +public class GridMarkerInfoWindow extends InfoWindow { + public static final int UNDEFINED_RES_ID = 0; + + private final TextView titleView; + private final TextView descriptionView; + private final TextView subDescriptionView; + private final MainViewModel mainViewModel; + private Ft8Message msg; + + + @SuppressLint("UseCompatLoadingForDrawables") + public GridMarkerInfoWindow(MainViewModel mainViewModel,int layoutResId, MapView mapView, Ft8Message msg) { + super(layoutResId, mapView); + this.mainViewModel=mainViewModel; + this.msg=msg; + //setResIds(mapView.getContext()); + titleView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_title); + descriptionView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_description); + subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_marker_info_bubble_subdescription); + ImageView fromDxccImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_dxcc_image); + ImageView fromItuImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_itu_image); + ImageView fromCqImage = (ImageView) this.mView.findViewById(R.id.track_marker_from_cq_image); + if (!msg.fromDxcc) fromDxccImage.setVisibility(View.GONE); + if (!msg.fromItu) fromItuImage.setVisibility(View.GONE); + if (!msg.fromCq) fromCqImage.setVisibility(View.GONE); + + ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerMarkerConstraintLayout); + if (msg.fromCq||msg.fromItu||msg.fromDxcc){//如果是没有通联过的区域,把颜色改成红色 + layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); + ToastMessage.show(String.format(GeneralVariables.getStringFromResource( + (R.string.tracker_new_zone_found)),msg.getMessageText())); + } + + + + //查是不是在本波段内通联成功过的呼号 + if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//如果在数据库中,划线 + titleView.setPaintFlags( + titleView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else {//如果不在数据库中,去掉划线 + titleView.setPaintFlags( + titleView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); + } + boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom()); + + //是否有与我呼号有关的消息 + if (msg.inMyCall()) { + layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style)); + titleView.setTextColor(mapView.getResources().getColor( + R.color.message_in_my_call_text_color)); + } else if (otherBandIsQso) { + //设置在别的波段通联过的消息颜色 + titleView.setTextColor(mapView.getResources().getColor( + R.color.fromcall_is_qso_text_color)); + } else { + titleView.setTextColor(mapView.getResources().getColor( + R.color.message_text_color)); + } + + + + + + + ImageButton imageButton=(ImageButton) this.mView.findViewById(R.id.callThisImageButton); + if (GeneralVariables.myCallsign.equals(msg.getCallsignFrom())){ + imageButton.setVisibility(View.GONE); + } + + imageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + doCallNow(); + } + }); + this.mView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == 1) { + GridMarkerInfoWindow.this.close(); + } + return true; + } + }); + } + /** + * 马上对发起者呼叫 + * + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + private void doCallNow() { + mainViewModel.addFollowCallsign(msg.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(msg);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(msg.getFromCallTransmitCallsign() + , 1, msg.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + + + @Override + public void onOpen(Object item) { + OverlayWithIW overlay = (OverlayWithIW) item; + String title = overlay.getTitle(); + if (title == null) { + title = ""; + } + + if (this.mView == null) { + Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); + } else { + titleView.setText(title); + String snippet = overlay.getSnippet(); + //Spanned snippetHtml = Html.fromHtml(snippet); + descriptionView.setText(snippet); + String subDesc = overlay.getSubDescription(); + subDescriptionView.setText(subDesc); + + } + } + + @Override + public void onClose() { + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java new file mode 100644 index 0000000..75b694f --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java @@ -0,0 +1,951 @@ +package com.bg7yoz.ft8cn.grid_tracker; +/** + * OsmMapView中画通联线、画网格等操作。地图是sqlite模式,采用离线方式(nightUSGS4Layer)。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import static java.lang.Math.PI; +import static java.lang.Math.asin; +import static java.lang.Math.atan; +import static java.lang.Math.cos; +import static java.lang.Math.floor; +import static java.lang.Math.sin; +import static java.lang.Math.tan; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.log.QSLRecordStr; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.google.android.gms.maps.model.LatLng; + +import org.osmdroid.tileprovider.IRegisterReceiver; +import org.osmdroid.tileprovider.modules.IArchiveFile; +import org.osmdroid.tileprovider.modules.OfflineTileProvider; +import org.osmdroid.tileprovider.tilesource.FileBasedTileSource; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.tileprovider.util.SimpleRegisterReceiver; +import org.osmdroid.util.BoundingBox; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.views.CustomZoomButtonsDisplay; +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.Marker; +import org.osmdroid.views.overlay.Polygon; +import org.osmdroid.views.overlay.Polyline; +import org.osmdroid.views.overlay.milestones.MilestoneLineDisplayer; +import org.osmdroid.views.overlay.milestones.MilestoneLister; +import org.osmdroid.views.overlay.milestones.MilestoneManager; +import org.osmdroid.views.overlay.milestones.MilestoneMeterDistanceSliceLister; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class GridOsmMapView { + private static final String TAG = "GridOsmMapView"; + + public enum GridMode {//网格的模式 + QSX, QSO, QSL + } + + public enum ShowTipsMode { + ALL, NEW, NONE + } + + private final MainViewModel mainViewModel; + //public static int COLOR_QSX = 0x7f0000ff;//红色50%,未通联过 + //public static int COLOR_QSO = 0x7fffff00;//黄色50%,通联过 + //public static int COLOR_QSL = 0x7fff0000;//红色50%,确认过 + private boolean showCQ = true; + private boolean showQSX = false; + + public final MapView gridMapView; + private final Context context; + // public ItemizedIconOverlay markerOverlay; + // private final ArrayList markerItems = new ArrayList<>(); + private final ArrayList gridLines = new ArrayList<>(); + private GridPolyLine selectedLine = null; + private static final int TIME_OUT = 3; + private int selectLineTimeOut = TIME_OUT;//被选择的画线,停留的周期数 + private final ArrayList gridPolygons = new ArrayList<>(); + private final ArrayList gridMarkers = new ArrayList<>(); + + private ShowTipsMode showTipsMode = ShowTipsMode.NEW; + + public GridOsmMapView(Context context, MapView gridMapView, MainViewModel mainViewModel) { + this.gridMapView = gridMapView; + this.context = context; + this.mainViewModel = mainViewModel; + } + + + public void initMap(String grid, boolean offset) { + mapViewOtherData(gridMapView);//设置内部源 + gridMapView.setMultiTouchControls(true); + gridMapView.setBuiltInZoomControls(true);//显示缩放按钮 + gridMapView.getZoomController().getDisplay().setPositions(true + , CustomZoomButtonsDisplay.HorizontalPosition.RIGHT + , CustomZoomButtonsDisplay.VerticalPosition.BOTTOM); + gridMapView.setTilesScaledToDpi(true); + + gridMapView.setMaxZoomLevel(6.0); + gridMapView.setMinZoomLevel(1.0); + gridMapView.getController().setZoom(1.6); + + gridMapView.setUseDataConnection(true); + gridMapView.setMultiTouchControls(true); + gridMapView.getOverlayManager().getTilesOverlay().setEnabled(true); + gridMapView.setSelected(true); + setGrayLine(); + + //addMarkerOverlay();//添加Marker图层 + + + //[A-Ra-r]{2}[0-9]{2}[A-Xa-x]{2},六位梅登海德正则 + // [A-Ra-r]{2}[0-9]{2},四位梅登海德正则 + LatLng latLng = MaidenheadGrid.gridToLatLng(grid);//做一下判断是不是网格 + if (latLng != null) { + if (offset) { + gridMapView.getController().setCenter(new GeoPoint(latLng.latitude + , latLng.longitude - 90f)); + } else { + gridMapView.getController().setCenter(new GeoPoint(latLng.latitude + , latLng.longitude)); + } + } + } + + /** + * 缩放到线路的范围之内 + * + * @param line 线 + */ + public void zoomToLineBound(GridPolyLine line) { + BoundingBox boundingBox = new BoundingBox(); + selectedLine = line; + selectLineTimeOut = TIME_OUT; + line.getOutlinePaint().setColor(gridMapView.getResources().getColor( + R.color.tracker_select_line_color)); + line.getOutlinePaint().setStrokeWidth(6); + //mOutlinePaint = getStrokePaint(0xffFF1E27, 3); + + GeoPoint eastNorthPoint = new GeoPoint(line.getActualPoints().get(0).getLatitude() + , line.getActualPoints().get(0).getLongitude()); + GeoPoint westSouthPoint = new GeoPoint(line.getActualPoints().get(1).getLatitude() + , line.getActualPoints().get(1).getLongitude()); + + if (Math.abs(westSouthPoint.getLongitude() - eastNorthPoint.getLongitude()) > 180) { + if (eastNorthPoint.getLongitude() > westSouthPoint.getLongitude()) { + double temp = westSouthPoint.getLongitude(); + westSouthPoint.setLongitude(eastNorthPoint.getLongitude()); + eastNorthPoint.setLongitude(temp); + + } + } else { + if (eastNorthPoint.getLongitude() < westSouthPoint.getLongitude()) { + double temp = westSouthPoint.getLongitude(); + westSouthPoint.setLongitude(eastNorthPoint.getLongitude()); + eastNorthPoint.setLongitude(temp); + + } + } + if (eastNorthPoint.getLatitude() < westSouthPoint.getLatitude()) { + double temp = westSouthPoint.getLatitude(); + westSouthPoint.setLatitude(eastNorthPoint.getLatitude()); + eastNorthPoint.setLatitude(temp); + } + + boundingBox.set(eastNorthPoint.getLatitude(), eastNorthPoint.getLongitude() + , westSouthPoint.getLatitude(), westSouthPoint.getLongitude()); + + gridMapView.zoomToBoundingBox(boundingBox, true, 100); + } + + + /** + * 显示CQ的位置 + * + * @param marker CQ的标记 + * @param offset 是否偏移 + */ + public void gotoCqGrid(GridMarker marker, boolean offset) { + GeoPoint geoPoint = new GeoPoint(marker.getPosition()); + if (offset) { + geoPoint.setLongitude(geoPoint.getLongitude() - 40f); + } + gridMapView.getController().animateTo(geoPoint, 2.5, 500L); + } + + + public synchronized GridMarker addGridMarker(String grid, Ft8Message msg) { + //todo 对于4.0的CQ消息,是没有网格信息的,可以以国家的地理位置代替 + if (LatLng2GeoPoint(MaidenheadGrid.gridToLatLng(grid)) == null) return null; + GridMarker marker = new GridMarker(context, mainViewModel, gridMapView, grid, msg); + gridMarkers.add(marker); + return marker; + } + + /** + * 清除标记marker + */ + public synchronized void clearMarkers() { + for (GridMarker marker : gridMarkers) { + marker.closeInfoWindow(); + gridMapView.getOverlays().remove(marker); + } + gridMarkers.clear(); + gridMapView.invalidate(); + } + + public GridPolyLine getSelectedLine() { + return selectedLine; + } + + public void clearSelectedLines() { + if (selectedLine != null) { + selectedLine.closeInfoWindow(); + gridMapView.getOverlays().remove(selectedLine); + selectedLine = null; + + } + } + + /** + * 清除线条 + */ + public synchronized void clearLines() { + + boolean isOpening = false; + + if (selectedLine != null) { + selectLineTimeOut--; + isOpening = selectedLine.isInfoWindowOpen(); + selectedLine.closeInfoWindow(); + gridMapView.getOverlays().remove(selectedLine); + } + for (GridPolyLine line : gridLines) { + line.closeInfoWindow(); + gridMapView.getOverlays().remove(line); + } + gridLines.clear(); + if (selectedLine != null && selectLineTimeOut > 0) { + gridMapView.getOverlays().add(selectedLine); + if (isOpening) selectedLine.showInfoWindow(); + } + gridMapView.invalidate(); + } + + + /** + * 清除网格瓦片 + */ + public synchronized void clearGridPolygon() { + for (GridPolygon polygon : gridPolygons) { + gridMapView.getOverlays().remove(polygon); + } + gridPolygons.clear(); + gridMapView.invalidate(); + } + + /** + * 清除全部图层 + */ + public void clearAll() { + clearMarkers(); + clearLines(); + clearGridPolygon(); + } + + /** + * 按照网格,查找网格图层,如果没有返回null + * + * @param grid 网格 + * @return 图层 + */ + public GridPolygon getGridPolygon(String grid) { + synchronized (gridPolygons) { + for (GridPolygon polygon : gridPolygons) { + if (polygon.grid.equals(grid)) return polygon; + } + } + return null; + } + + /** + * 标记、更新新发生消息的网格 + * + * @param grid 网格 + * @param msg 消息内容 + * @param subDetail 细节 + * @return 网格对象 + */ + public GridPolygon upgradeGridInfo(String grid, String msg, String subDetail) { + GridPolygon gridPolygon = getGridPolygon(grid); + if (gridPolygon == null) { + gridPolygon = addGridPolygon(grid, GridMode.QSX); + } + gridPolygon.setSnippet(msg); + gridPolygon.setSubDescription(subDetail); + //gridPolygon.showInfoWindow(); + return gridPolygon; + } + + /** + * 标记、更新新发生消息的网格 + * + * @param recordStr 历史记录 + * @return 网格对象 + */ + public GridPolygon upgradeGridInfo(QSLRecordStr recordStr) { + GridPolygon gridPolygon = getGridPolygon(recordStr.getGridsquare()); + if (gridPolygon == null) { + if (recordStr.isQSL) { + gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSL); + } else { + gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSO); + } + } + + gridPolygon.setSnippet(String.format(String.format("%s %s", + String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) + , recordStr.getFreq()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) + , recordStr.getBand())))); + + gridPolygon.setSubDescription(String.format("%s\n%s\n%s %s\n%s %s", + String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) + , recordStr.getTime_on()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) + , recordStr.getTime_off()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) + , recordStr.getRst_rcvd()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) + , recordStr.getRst_sent()), + + String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) + , recordStr.getMode()), + recordStr.getComment() + )); + gridPolygon.setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容 + gridPolygon.setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, gridMapView)); + return gridPolygon; + } + + /** + * 更新地图 + */ + public void mapUpdate(){ + gridMapView.invalidate(); + } + + /** + * 升级网格状态,如果没有,说明是新的,就添加网格。返回false。如果有,返回true。 + * + * @param grid 网格 + * @param gridMode 模式 + * @return 发现 + */ + public boolean upgradeGridMode(String grid, GridMode gridMode) { + GridPolygon polygon = getGridPolygon(grid); + if (polygon != null) { + polygon.upgradeGridMode(gridMode); + return true; + } else { + addGridPolygon(grid, gridMode); + return false; + } + } + + /** + * 添加网格图层 + * + * @param grid 网格 + * @param gridMode 网格类型 + * @return 返回一个网格图层对象 + */ + public synchronized GridPolygon addGridPolygon(String grid, GridMode gridMode) { + if (gridMapView == null) return null; + if (gridMapView.getRepository()==null) return null; + try {//当日志量过多时,会出现闪退的问题,在此处做一个异常捕获,防止闪退 + + GridPolygon polygon = new GridPolygon(context, gridMapView, grid, gridMode); + gridPolygons.add(polygon); + gridMapView.getOverlays().add(polygon); + return polygon; + + } catch (Exception e) { + //throw new RuntimeException(e); + } + return null; + } + + /** + * 查找有没有符合的CQ Marker + * + * @param message 消息 + * @return Marker + */ + public GridMarker getMarker(Ft8Message message) { + for (GridMarker marker : gridMarkers) { + if (marker.msg == message) { + return marker; + } + } + return null; + } + + /** + * 查找有没有符合消息的线 + * + * @param message 消息 + * @return 线 + */ + public GridPolyLine getLine(Ft8Message message) { + for (GridPolyLine line : gridLines) { + if (line.msg == message) { + return line; + } + } + return null; + } + + /** + * 在两个网格之间画线。 + * + * @param message 消息 + * @param db 数据库 + */ + public synchronized GridPolyLine drawLine(Ft8Message message, DatabaseOpr db) { + LatLng fromLatLng = MaidenheadGrid.gridToLatLng(message.getMaidenheadGrid(db)); + LatLng toLatLng = MaidenheadGrid.gridToLatLng(message.getToMaidenheadGrid(db)); + if (fromLatLng == null) { + fromLatLng = message.fromLatLng; + } + + if (toLatLng == null) { + toLatLng = message.toLatLng; + } + if (fromLatLng == null || toLatLng == null) { + return null; + } + final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, message); + gridLines.add(line); + return line; + } + + public synchronized GridPolyLine drawLine(QSLRecordStr recordStr) { + LatLng fromLatLng = MaidenheadGrid.gridToLatLng(recordStr.getGridsquare()); + LatLng toLatLng = MaidenheadGrid.gridToLatLng(recordStr.getMy_gridsquare()); + if (fromLatLng == null) { + //todo 把呼号转为国家的经纬度 + return null; + } + + if (toLatLng == null) { + //todo 把呼号转为国家的经纬度 + return null; + } + final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, recordStr); + return line; + } + + /** + * 设定地图的离线来源 + * + * @param mapView osmMap + */ + public void mapViewOtherData(MapView mapView) { + //可以根据时间不同,显示不同的地图 + String strFilepath = getAssetsCacheFile(context, context.getString(R.string.map_name)); + File exitFile = new File(strFilepath); + if (!exitFile.exists()) { + mapView.setTileSource(TileSourceFactory.USGS_SAT); + } else { + OfflineTileProvider tileProvider = new OfflineTileProvider( + (IRegisterReceiver) new SimpleRegisterReceiver(context), new File[]{exitFile}); + mapView.setTileProvider(tileProvider); + String source = ""; + IArchiveFile[] archives = tileProvider.getArchives(); + if (archives.length > 0) { + Set tileSources = archives[0].getTileSources(); + if (!tileSources.isEmpty()) { + source = tileSources.iterator().next(); + mapView.setTileSource(FileBasedTileSource.getSource(source)); + } else { + mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); + } + } else + mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); + mapView.invalidate(); + + } + } + + /** + * 获取Assets目录,这里面保存着地图文件 + * + * @param context context + * @param fileName 地图文件名,sqlite格式 + * @return 包含全路径的文件名 + */ + public String getAssetsCacheFile(Context context, String fileName) { + File cacheFile = new File(context.getCacheDir(), fileName); + try { + InputStream inputStream = context.getAssets().open(fileName); + try { + FileOutputStream outputStream = new FileOutputStream(cacheFile); + try { + byte[] buf = new byte[1024]; + int len; + while ((len = inputStream.read(buf)) > 0) { + outputStream.write(buf, 0, len); + } + } finally { + outputStream.close(); + } + } finally { + inputStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return cacheFile.getAbsolutePath(); + } + + public static GeoPoint LatLng2GeoPoint(LatLng latLng) { + if (latLng == null) return null; + return new GeoPoint(latLng.latitude, latLng.longitude); + } + + public static ArrayList LatLngs2GeoPoints(LatLng[] latLngs) { + ArrayList geoPoints = new ArrayList<>(); + if (latLngs != null) { + for (int i = 0; i < latLngs.length; i++) { + geoPoints.add(LatLng2GeoPoint(latLngs[i])); + } + } + return geoPoints; + } + + public static class GridPolyLine extends Polyline { + //public String fromGrid; + //public String toGrid; + public Ft8Message msg; + public QSLRecordStr recorder; + //public boolean marked = false; + + @SuppressLint("DefaultLocale") + public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, QSLRecordStr recordStr) { + super(mapView); + this.recorder = recordStr; + setSnippet(String.format(String.format("%s %s", + String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) + , recordStr.getFreq()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) + , recordStr.getBand())))); + + setSubDescription(String.format("%s\n%s\n%s %s\n%s %s", + String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) + , recordStr.getTime_on()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) + , recordStr.getTime_off()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) + , recordStr.getRst_rcvd()), + String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) + , recordStr.getRst_sent()), + + String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) + , recordStr.getMode()), + recordStr.getComment() + )); + setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容 + this.mOutlinePaint = getStrokePaint( + mapView.getResources().getColor( + R.color.tracker_history_line_color), 3); + List pts = new ArrayList<>(); + pts.add(GridOsmMapView.LatLng2GeoPoint(fromLatLng)); + pts.add(GridOsmMapView.LatLng2GeoPoint(toLatLng)); + + + setPoints(pts); + setGeodesic(true); + setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, mapView)); + mapView.getOverlayManager().add(this); + } + + @SuppressLint("DefaultLocale") + public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, Ft8Message msg) { + super(mapView); + this.msg = msg; + + setSnippet(String.format("%s<--%s", msg.toWhere, msg.fromWhere));//表示距离 + setSubDescription(String.format("%dBm , %.1f ms , %s" + , msg.snr, msg.time_sec + , MaidenheadGrid.getDistLatLngStr(fromLatLng, toLatLng))); + setTitle(msg.getMessageText());//显示消息内容 + if (msg.inMyCall()) { + this.mOutlinePaint = getStrokePaint( + mapView.getResources().getColor( + R.color.tracker_in_my_line_color), 3); + } else { + this.mOutlinePaint = getStrokePaint(mapView.getResources().getColor( + R.color.tracker_line_color), 3); + } + + List pts = new ArrayList<>(); + pts.add(GridOsmMapView.LatLng2GeoPoint(fromLatLng)); + pts.add(GridOsmMapView.LatLng2GeoPoint(toLatLng)); + + + setPoints(pts); + setGeodesic(true); + setInfoWindow(new GridInfoWindow(R.layout.tracker_grid_info_win, mapView, msg)); + mapView.getOverlayManager().add(this); + //showInfoWindow(); + + final float lineLen = (float) getDistance(); + final float pointLen = lineLen / 10f > 200000 ? 200000f : lineLen / 10f; + + final List managers = new ArrayList<>(); + + final MilestoneMeterDistanceSliceLister slicerForPath = new MilestoneMeterDistanceSliceLister(); + managers.add(getAnimatedPathManager(slicerForPath)); + + setMilestoneManagers(managers); + + + //设置方向动画 + final ValueAnimator percentageCompletion = ValueAnimator.ofFloat(0, 1); // 10 kilometers + + percentageCompletion.setRepeatCount(ValueAnimator.INFINITE); + percentageCompletion.setDuration(1000); // 1 seconds + percentageCompletion.setStartDelay(0); // 1 second + + percentageCompletion.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + double dist = ((float) animation.getAnimatedValue()) * lineLen; + double distStart = dist - pointLen; + if (distStart < 0) distStart = 0; + slicerForPath.setMeterDistanceSlice(distStart, dist); + mapView.invalidate(); + } + }); + percentageCompletion.start(); + } + + /** + * 线条的动画点,设置:绿色,10f宽度 + */ + private MilestoneManager getAnimatedPathManager(final MilestoneLister pMilestoneLister) { + final Paint slicePaint = getStrokePaint(Color.GREEN, 15f); + return new MilestoneManager(pMilestoneLister, new MilestoneLineDisplayer(slicePaint)); + } + + private Paint getStrokePaint(final int pColor, final float pWidth) { + Paint paint = new Paint(); + paint.setStrokeWidth(pWidth); + paint.setStyle(Paint.Style.STROKE); + paint.setAntiAlias(true); + paint.setColor(pColor); + //paint.setStrokeCap(Paint.Cap.ROUND); + paint.setPathEffect(new DashPathEffect(new float[]{20, 10}, 0)); + return paint; + } + + public void showNewInfo() { + if (msg != null) { + if ((msg.fromDxcc || msg.fromItu || msg.fromCq) + && !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { + showInfoWindow(); + } + } + if (recorder != null) { + showInfoWindow(); + } + + } + } + + public static class GridPolygon extends Polygon { + public String grid; + public GridMode gridMode; + private final Context context; + //private BasicInfoWindow infoWindow; + //public String details; + + public GridPolygon(Context context, MapView mapView, String grid, GridMode gridMode) { + super(mapView); + this.grid = grid; + this.gridMode = gridMode; + this.context = context; + + setTitle(grid); + setStrokeWidth(3f); + setStrokeColor(this.context.getColor(R.color.osm_grid_out_line_color)); + + updateGridMode(); + + ArrayList pts = LatLngs2GeoPoints(MaidenheadGrid.gridToPolygon(grid)); + setPoints(pts); + + setVisible(true); + + } + + public synchronized void updateGridMode() { + synchronized (this) {//防止闪退 + switch (gridMode) { + case QSL: + this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qsl_color)); + //setFillColor(this.context.getColor(R.color.tracker_sample_qsl_color)); + break; + case QSO: + this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qso_color)); + //setFillColor(this.context.getColor(R.color.tracker_sample_qso_color)); + break; + case QSX: + this.mFillPaint.setColor(this.context.getColor(R.color.tracker_sample_qsx_color)); + //setFillColor(this.context.getColor(R.color.tracker_sample_qsx_color)); + break; + } + } + } + + public synchronized void upgradeGridMode(GridMode mode) { + if (mode.ordinal() > gridMode.ordinal()) { + gridMode = mode; + updateGridMode(); + } + } + } + + public static class GridMarker extends Marker { + public String grid; + private final Context context; + private final Ft8Message msg; + + @SuppressLint({"UseCompatLoadingForDrawables", "DefaultLocale"}) + public GridMarker(Context context, MainViewModel mainViewModel, MapView mapView + , String grid, Ft8Message msg) { + super(mapView); + this.grid = grid; + this.context = context; + this.msg = msg; + + this.setPosition(LatLng2GeoPoint(MaidenheadGrid.gridToLatLng(grid))); + this.setAnchor(ANCHOR_CENTER, ANCHOR_BOTTOM); + this.setInfoWindow(new GridMarkerInfoWindow(mainViewModel + , R.layout.tracker_cq_marker_info_win, mapView, msg)); + setSnippet(String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec)); + setSubDescription(String.format("%s , %s" + , MaidenheadGrid.getDistStr(grid, GeneralVariables.getMyMaidenheadGrid()) + , msg.fromWhere));//表示距离 + setTitle(msg.getMessageText());//显示消息内容 + + + @SuppressLint("UseCompatLoadingForDrawables") + Drawable d; + if (GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { + d = context.getDrawable(R.drawable.ic_baseline_cq_qso_24).mutate(); + d.setColorFilter(context.getColor(R.color.tracker_cq_marker_is_qso_color) + , PorterDuff.Mode.SRC_ATOP); + + } else { + d = context.getDrawable(R.drawable.ic_baseline_cq_24).mutate(); + } + if (GeneralVariables.checkQSLCallsign_OtherBand(msg.callsignFrom)) { + d.setColorFilter(context.getColor(R.color.tracker_cq_marker_other_is_qso_color) + , PorterDuff.Mode.SRC_ATOP); + } + setIcon(d); + + //this.showInfoWindow(); + mapView.getOverlays().add(this); + } + + public void showNewInfo() { + if ((msg.fromDxcc || msg.fromItu || msg.fromCq || (msg.checkIsCQ())) + && !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) { + showInfoWindow(); + } + } + } + + + /** + * 显示提示,根据显示模式来显示。 + */ + public void showInfoWindows() { + setShowTipsMode(showTipsMode); + } + + /** + * 显示全部提示 + */ + public void showAllInfoWindows() { + if (showQSX) { + for (GridPolyLine line : gridLines) { + line.showInfoWindow(); + } + } + if (showCQ) { + for (GridMarker marker : gridMarkers) { + marker.showInfoWindow(); + } + } + } + + /** + * 只显示新的提示 + */ + public void showNewInfoWindows() { + if (showQSX) { + for (GridPolyLine line : gridLines) { + line.showNewInfo(); + } + } + if (showCQ) { + for (GridMarker marker : gridMarkers) { + marker.showNewInfo(); + } + } + } + + /** + * 关闭全部提示窗口 + */ + public void hideInfoWindows() { + for (GridPolygon polygon : gridPolygons + ) { + polygon.closeInfoWindow(); + } + for (GridPolyLine line : gridLines) { + line.closeInfoWindow(); + } + for (GridMarker marker : gridMarkers) { + marker.closeInfoWindow(); + } + gridMapView.invalidate(); + } + + + public void setShowCQ(boolean showCQ) { + this.showCQ = showCQ; + showInfoWindows(); + } + + public void setShowQSX(boolean showQSX) { + this.showQSX = showQSX; + showInfoWindows(); + } + + public void setShowTipsMode(ShowTipsMode showTipsMode) { + this.showTipsMode = showTipsMode; + hideInfoWindows(); + switch (this.showTipsMode) { + case ALL: + showAllInfoWindows(); + break; + case NEW: + showNewInfoWindows(); + break; + case NONE: + break; + } + } + + + private static double[] computeDayNightTerminator(long t) { + // The nice thing about the java time standard is that converting it + // to a julian date is trivial - unlike the gyrations the original + // matlab code had to go through to convert the y/n/d/h/m/s parameters + final double julianDate1970 = t / (double) (1000 * 60 * 60 * 24); + // convert from the unix epoch to the astronomical epoch + // (noon on January 1, 4713 BC, GMT/UT) (the .5 is noon versus midnight) + final double juliandate = julianDate1970 + 2440587.500000; + final double K = PI / 180; + // here be dragons! + final double T = (juliandate - 2451545.0) / 36525; + double L = 280.46645 + 36000.76983 * T + 0.0003032 * T * T; + L = L % 360; + if (L < 0) + L = L + 360; + double M = 357.52910 + 35999.05030 * T - 0.0001559 * T * T - + 0.00000048 * T * T * T; + M = M % 360; + if (M < 0) + M = M + 360; + final double C = (1.914600 - 0.004817 * T - 0.000014 * T * T) * sin(K * M) + + (0.019993 - 0.000101 * T) * sin(K * 2 * M) + + 0.000290 * sin(K * 3 * M); + final double theta = L + C; + final double LS = L; + final double LM = 218.3165 + 481267.8813 * T; + final double eps0 = 23.0 + 26.0 / 60.0 + 21.448 / 3600.0 - + (46.8150 * T + + 0.00059 * T * T - 0.001813 * T * T * T) / 3600; + final double omega = 125.04452 - 1934.136261 * T + 0.0020708 * T * T + + T * T * + T / 450000; + final double deltaEps = + (9.20 * cos(K * omega) + 0.57 * cos(K * 2 * LS) + + 0.10 * cos(K * 2 * LM) - 0.09 * cos(K * 2 * omega)) / 3600; + final double eps = eps0 + deltaEps + 0.00256 * + cos(K * (125.04 - 1934.136 * T)); + final double lambda = theta - 0.00569 - 0.00478 * sin(K * (125.04 - + 1934.136 * + T)); + final double delta = asin(sin(K * eps) * sin(K * lambda)); + final double dec = delta / K; + final double tau = (juliandate - floor(juliandate)) * 360; + double[] coords = new double[361]; + for (int i = 0; i < 361; i++) + coords[i] = atan(cos((i - 180 + tau) * K) / tan(dec * K)) / K + 90; + return coords; + } + + /** + * 根据当前时间画灰线 + */ + public void setGrayLine() { + double[] lats = computeDayNightTerminator(System.currentTimeMillis()); + LatLng[] grayLine = new LatLng[lats.length * 3]; + for (int i = 0; i < lats.length; i++) { + grayLine[i] = new LatLng((lats[i] - 90), i); + grayLine[lats.length + i] = new LatLng((lats[i] - 90), i); + grayLine[lats.length * 2 + i] = new LatLng((lats[i] - 90), i); + } + + Polyline line = new Polyline(gridMapView); + line.setWidth(15f); + line.setColor(context.getColor(R.color.tracker_gray_line_color)); + + List pts = new ArrayList<>(); + for (int i = 0; i < grayLine.length; i++) { + pts.add(GridOsmMapView.LatLng2GeoPoint(grayLine[i])); + } + line.setInfoWindow(null); + line.setPoints(pts); + gridMapView.getOverlays().add(line); + + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java new file mode 100644 index 0000000..3c9209b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java @@ -0,0 +1,89 @@ +package com.bg7yoz.ft8cn.grid_tracker; +/** + * 通联日志的提示窗口。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.graphics.Paint; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import org.osmdroid.views.MapView; +import org.osmdroid.views.overlay.OverlayWithIW; +import org.osmdroid.views.overlay.infowindow.InfoWindow; + +public class GridRecordInfoWindow extends InfoWindow { + public static final int UNDEFINED_RES_ID = 0; + // static int mTitleId = 0; +// static int mDescriptionId = 0; +// static int mSubDescriptionId = 0; +// static int fromDxccImageId = 0; +// static int fromItuImageId = 0; +// static int fromCqImageId = 0; +// static int toDxccImageId = 0; +// static int toItuImageId = 0; +// static int toCqImageId = 0; + private final TextView titleView; + private final TextView descriptionView; + private final TextView subDescriptionView; + + + @SuppressLint("UseCompatLoadingForDrawables") + public GridRecordInfoWindow(int layoutResId, MapView mapView) { + super(layoutResId, mapView); + //setResIds(mapView.getContext()); + titleView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_title); + descriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_description); + subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_subdescription); + + ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerGridRecInfoConstraintLayout); + + + this.mView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == 1) { + GridRecordInfoWindow.this.close(); + } + return true; + } + }); + } + + + @Override + public void onOpen(Object item) { + OverlayWithIW overlay = (OverlayWithIW) item; + String title = overlay.getTitle(); + if (title == null) { + title = ""; + } + + if (this.mView == null) { + Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!"); + } else { + titleView.setText(title); + String snippet = overlay.getSnippet(); + //Spanned snippetHtml = Html.fromHtml(snippet); + descriptionView.setText(snippet); + String subDesc = overlay.getSubDescription(); + subDescriptionView.setText(subDesc); + + } + } + + @Override + public void onClose() { + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java new file mode 100644 index 0000000..da2b4bc --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java @@ -0,0 +1,895 @@ +package com.bg7yoz.ft8cn.grid_tracker; +/** + * 网格追踪的主窗口。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AnimationUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.database.OnAfterQueryConfig; +import com.bg7yoz.ft8cn.databinding.ActivityGridTrackerMainBinding; +import com.bg7yoz.ft8cn.floatview.FloatView; +import com.bg7yoz.ft8cn.floatview.FloatViewButton; +import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; +import com.bg7yoz.ft8cn.log.OnQueryQSLRecordCallsign; +import com.bg7yoz.ft8cn.log.QSLRecordStr; +import com.bg7yoz.ft8cn.timer.UtcTimer; +import com.bg7yoz.ft8cn.ui.CallingListAdapter; +import com.bg7yoz.ft8cn.ui.FreqDialog; +import com.bg7yoz.ft8cn.ui.SetVolumeDialog; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class GridTrackerMainActivity extends AppCompatActivity { + private static final String TAG = "GridTrackerMainActivity"; + private static final String DataConfigShowMode = "tracker_show_mode"; + private static final String DataConfigShowQsx = "tracker_show_qsx"; + private static final String DataConfigShowCQ = "tracker_show_cq"; + + private MainViewModel mainViewModel; + private ActivityGridTrackerMainBinding binding; + private FloatView floatView; + private FloatViewButton transButton; + private GridOsmMapView gridOsmMapView; + + private RecyclerView callMessagesRecyclerView; + private CallingListAdapter callingListAdapter; + private boolean messageListIsClose = false; + private boolean configBarIsClose = false; + private QSLRecordStr qlsRecorder = null;//用于历史显示消息 + private MutableLiveData> qslRecordList = new MutableLiveData<>(); + + + @SuppressLint("NotifyDataSetChanged") + protected void doAfterCreate() { + //设置消息列表 + callingListAdapter.notifyDataSetChanged(); + callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); + + setTipsRadioGroupClickerListener();//显示模式Group radio动作 + setShowTipsSwitchClickerListener();//显示提示开关动作 + readConfig(); + + //读取调用本activity的参数,如果不为空,说明要画参数中的消息 + //画在日志界面中被选择的消息 + Intent intentGet = getIntent(); + qlsRecorder = (QSLRecordStr) intentGet.getSerializableExtra("qslList"); + if (qlsRecorder != null) { + GridOsmMapView.GridPolyLine line = drawMessage(qlsRecorder);//在地图上画每一个消息 + if (line != null) { + line.showInfoWindow(); + } + } + //画日志界面查询出的全部消息 + String queryKey = intentGet.getStringExtra("qslAll"); + int queryFilter = intentGet.getIntExtra("queryFilter", 0); + if (queryKey != null) { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tracker_query_qso_info)); + mainViewModel.databaseOpr.getQSLRecordByCallsign(true, 0, queryKey, queryFilter + , new OnQueryQSLRecordCallsign() { + @Override + public void afterQuery(ArrayList records) { + qslRecordList.postValue(records); + } + }); + } + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + //禁止休眠 + getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + , WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + //设置深色模式 + getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES); + + //全屏 + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN + , WindowManager.LayoutParams.FLAG_FULLSCREEN); + + + mainViewModel = MainViewModel.getInstance(this); + binding = ActivityGridTrackerMainBinding.inflate(getLayoutInflater()); + + gridOsmMapView = new GridOsmMapView(getBaseContext(), binding.osmMap, mainViewModel); + + + callMessagesRecyclerView = binding.callMessagesRecyclerView; + callingListAdapter = new CallingListAdapter(this, mainViewModel + , mainViewModel.ft8Messages, CallingListAdapter.ShowMode.TRACKER); + callMessagesRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + callMessagesRecyclerView.setAdapter(callingListAdapter); + + + callingListAdapter.setOnItemClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int position = (int) view.getTag(); + if (position == -1) { + return; + } + if (position > mainViewModel.ft8Messages.size() - 1) { + return; + } + Ft8Message msg = mainViewModel.ft8Messages.get(position); + + if (msg.checkIsCQ()) { + GridOsmMapView.GridMarker marker = gridOsmMapView.getMarker(msg); + if (marker == null) marker = gridOsmMapView.addGridMarker( + msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg); + if (marker != null) { + gridOsmMapView.hideInfoWindows(); + gridOsmMapView.gotoCqGrid(marker, true); + marker.showInfoWindow(); + } + } else { + gridOsmMapView.hideInfoWindows(); + gridOsmMapView.clearSelectedLines(); + GridOsmMapView.GridPolyLine line; + line = gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr); + if (line != null) { + gridOsmMapView.zoomToLineBound(line); + line.showInfoWindow(); + closeMessages(); + } + } + } + }); + //设置消息列表滑动,用于快速呼叫 + initRecyclerViewAction(); + + //观察解码数量 + mainViewModel.mutable_Decoded_Counter.observe(this, new Observer() { + @SuppressLint({"DefaultLocale", "NotifyDataSetChanged"}) + @Override + public void onChanged(Integer integer) { + } + }); + mainViewModel.mutableIsDecoding.observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + gridOsmMapView.clearLines(); + gridOsmMapView.clearMarkers(); + } + } + }); + mainViewModel.mutableFt8MessageList.observe(this, new Observer>() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onChanged(ArrayList messages) { + if (mainViewModel.currentMessages == null) return; + ArrayList tempMsg = new ArrayList<>(mainViewModel.currentMessages); + callingListAdapter.notifyDataSetChanged(); + if (callMessagesRecyclerView.computeVerticalScrollRange() + - callMessagesRecyclerView.computeVerticalScrollExtent() + - callMessagesRecyclerView.computeVerticalScrollOffset() < 500) { + callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); + } + + binding.gridMessageTextView.setText(String.format("%s %s" + , String.format(GeneralVariables.getStringFromResource( + R.string.tracker_decoded_new) + , mainViewModel.currentDecodeCount), String.format( + getString(R.string.decoding_takes_milliseconds) + , mainViewModel.ft8SignalListener.decodeTimeSec.getValue()))); + + //画电台之间的连线 + //对CQ的电台打点 + for (Ft8Message msg : tempMsg) { + drawMessage(msg);//在地图上画每一个消息 + } + gridOsmMapView.showInfoWindows(); + //} + } + }); + + + //观察DEBUG信息 + GeneralVariables.mutableDebugMessage.observe(this, new Observer() { + @Override + public void onChanged(String s) { + if (s.length() > 1) { + binding.trackerDebugLayout.setVisibility(View.VISIBLE); + } else { + binding.trackerDebugLayout.setVisibility(View.GONE); + } + binding.debugMessageTextView.setText(s); + } + }); + //设置发射消息框的动画 + binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this + , R.anim.view_blink)); + //观察发射的状态 + mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, + new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + binding.transmittingLayout.setVisibility(View.VISIBLE); + } else { + binding.transmittingLayout.setVisibility(View.GONE); + } + } + }); + + //观察发射内容的变化 + mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this, + new Observer() { + @Override + public void onChanged(String s) { + binding.transmittingMessageTextView.setText(s); + } + }); + + + //观察时钟的变化,显示进度条 + mainViewModel.timerSec.observe(this, new Observer() { + @Override + public void onChanged(Long aLong) { + if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential() + && mainViewModel.ft8TransmitSignal.isActivated()) { + binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color)); + } else { + binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color)); + } + binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15)); + } + }); + + //添加浮动按钮 + InitFloatView(); + + + //gridOsmMapView.initMap(GeneralVariables.getMyMaidenhead4Grid(), true); + + //把呼号与网格对应关系中的网格提取出来 + for (Map.Entry entry : GeneralVariables.callsignAndGrids.entrySet()) { + gridOsmMapView.upgradeGridMode(entry.getValue(), GridOsmMapView.GridMode.QSX); + } + + //观察呼号与网格的对应关系表的变化,如果有新增的,就添加 + GeneralVariables.mutableNewGrid.observe(this, new Observer() { + @Override + public void onChanged(String s) { + if (!gridOsmMapView.upgradeGridMode(s, GridOsmMapView.GridMode.QSX)) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource( + R.string.grid_tracker_new_grid), s)); + } + } + }); + + //获取曾经通联过的网格 + mainViewModel.databaseOpr.getQsoGridQuery(new DatabaseOpr.OnGetQsoGrids() { + @Override + public void onAfterQuery(HashMap grids) { + for (Map.Entry entry : grids.entrySet()) { + gridOsmMapView.upgradeGridMode(entry.getKey() + , entry.getValue() ? GridOsmMapView.GridMode.QSL : GridOsmMapView.GridMode.QSO); + } + } + }); + + //关闭消息按钮 + binding.closeMessageImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + closeMessages(); + } + }); + //打开消息按钮 + binding.openMessagesImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openMessage(); + } + }); + gridOsmMapView.initMap(GeneralVariables.getMyMaidenhead4Grid(), true); + + qslRecordList.observe(this, new Observer>() { + @Override + public void onChanged(ArrayList qslRecordStrs) { + for (QSLRecordStr record : qslRecordStrs) { + drawMessage(record);//在地图上画每一个消息 + } + gridOsmMapView.mapUpdate(); + } + }); + + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + closeMessages(); + closeConfigBar(); + doAfterCreate(); + } + }, 1000); + + setContentView(binding.getRoot()); + } + + + /** + * 在地图上画消息,包括收发消息和CQ消息 + * + * @param msg 消息 + */ + @SuppressLint("DefaultLocale") + private void drawMessage(Ft8Message msg) { + gridOsmMapView.upgradeGridInfo( + msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg.getMessageText() + , String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec)); + gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr); + if (msg.checkIsCQ()) { + gridOsmMapView.addGridMarker( + msg.getMaidenheadGrid(mainViewModel.databaseOpr) + , msg); + } + } + + private GridOsmMapView.GridPolyLine drawMessage(QSLRecordStr recordStr) { + gridOsmMapView.gridMapView.post(new Runnable() { + @Override + public void run() { + gridOsmMapView.upgradeGridInfo(recordStr); + gridOsmMapView.drawLine(recordStr); + } + }); + + return null; + } + + private void setShowTipsSwitchClickerListener() { + + binding.trackerShowQsxSwitch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + gridOsmMapView.hideInfoWindows(); + if (binding.trackerShowQsxSwitch.isChecked()) { + binding.trackerShowQsxSwitch.setText(GeneralVariables.getStringFromResource( + R.string.tracker_show_qsx_tips)); + } else { + binding.trackerShowQsxSwitch.setText(GeneralVariables.getStringFromResource( + R.string.tracker_hide_qsx_tips)); + } + gridOsmMapView.setShowQSX(binding.trackerShowQsxSwitch.isChecked()); + writeConfig(DataConfigShowQsx, binding.trackerShowQsxSwitch.isChecked() ? "1" : "0"); + } + }); + + binding.trackerShowCqSwitch.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + gridOsmMapView.hideInfoWindows(); + if (binding.trackerShowCqSwitch.isChecked()) { + binding.trackerShowCqSwitch.setText(GeneralVariables.getStringFromResource( + R.string.tracker_show_cq_tips)); + } else { + binding.trackerShowCqSwitch.setText(GeneralVariables.getStringFromResource( + R.string.tracker_hide_cq_tips)); + } + gridOsmMapView.setShowCQ(binding.trackerShowCqSwitch.isChecked()); + writeConfig(DataConfigShowCQ, binding.trackerShowCqSwitch.isChecked() ? "1" : "0"); + + } + }); + } + + /** + * 设置显示模式动作 + */ + private void setTipsRadioGroupClickerListener() { + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (binding.tipsAllRadioButton.isChecked()) { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.ALL); + writeConfig(DataConfigShowMode, "0"); + } + if (binding.tipsNewRadioButton.isChecked()) { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NEW); + writeConfig(DataConfigShowMode, "1"); + } + if (binding.tipsNoneRadioButton.isChecked()) { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NONE); + binding.trackerShowQsxSwitch.setVisibility(View.GONE); + binding.trackerShowCqSwitch.setVisibility(View.GONE); + writeConfig(DataConfigShowMode, "2"); + } else { + binding.trackerShowQsxSwitch.setVisibility(View.VISIBLE); + binding.trackerShowCqSwitch.setVisibility(View.VISIBLE); + } + } + }; + binding.tipsAllRadioButton.setOnClickListener(listener); + binding.tipsNewRadioButton.setOnClickListener(listener); + binding.tipsNoneRadioButton.setOnClickListener(listener); + + View.OnClickListener barListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + closeConfigBar(); + } + }; + binding.closeTIpsImageView.setOnClickListener(barListener); + binding.trackerInfoModeTextView.setOnClickListener(barListener); + + } + + private void openConfigBar() { + if (!configBarIsClose) return; + configBarIsClose = false; + ObjectAnimator openConfigAnimator = ObjectAnimator.ofFloat(binding.trackerConfigLayout + , "translationY", 0); + //openConfigAnimator.setDuration(500); + openConfigAnimator.setFloatValues(binding.trackerConfigLayout.getHeight() + 10, 0); + openConfigAnimator.start(); + } + + private void closeConfigBar() { + if (configBarIsClose) return; + configBarIsClose = true; + ObjectAnimator openConfigAnimator = ObjectAnimator.ofFloat(binding.trackerConfigLayout + , "translationY", 0); + //openConfigAnimator.setDuration(500); + openConfigAnimator.setFloatValues(0, binding.trackerConfigLayout.getHeight() + 100); + openConfigAnimator.start(); + } + + /** + * 关闭消息栏 + */ + private void openMessage() { + if (!messageListIsClose) return; + messageListIsClose = false; + ObjectAnimator openMessageAnimator = ObjectAnimator.ofFloat(binding.callingListConstraintLayout + , "translationX", 0); + //openMessageAnimator.setDuration(500); + openMessageAnimator.setFloatValues(-binding.callingListConstraintLayout.getWidth() - 10, 0); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(openMessageAnimator); + animatorSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + binding.openMessagesImageButton.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + binding.closeMessageImageButton.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + + animatorSet.start(); + } + + /** + * 动画关闭消息栏 + */ + private void closeMessages() { + if (messageListIsClose) return; + messageListIsClose = true; + ObjectAnimator closeMessageAnimator = ObjectAnimator.ofFloat(binding.callingListConstraintLayout + , "translationX", 0); + //closeMessageAnimator.setDuration(500); + closeMessageAnimator.setFloatValues(0, -binding.callingListConstraintLayout.getWidth() - 10); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(closeMessageAnimator); + animatorSet.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + binding.closeMessageImageButton.setVisibility(View.GONE); + } + + @Override + public void onAnimationEnd(Animator animator) { + binding.openMessagesImageButton.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + + animatorSet.start(); + } + + + /** + * 添加浮动按钮 + */ + + private void InitFloatView() { + floatView = new FloatView(this, 32); + + binding.trackConstraint.addView(floatView); + floatView.setButtonMargin(0); + floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT); + + floatView.setButtonBackgroundResourceId(R.drawable.float_button_style); + + transButton = floatView.addButton(R.id.grid_tracker_trans, "grid_tracker_trans" + , R.drawable.ic_baseline_cancel_schedule_send_off + , new View.OnClickListener() { + @Override + public void onClick(View view) { + //如果 + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.restTransmitting(); + } + mainViewModel.ft8TransmitSignal.setActivated(!mainViewModel.ft8TransmitSignal.isActivated()); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + }); + + + //动态添加按钮,建议使用静态的ID,静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置 + + floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + new FreqDialog(binding.trackConstraint.getContext(), mainViewModel).show(); + } + }); + + floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + new SetVolumeDialog(binding.trackConstraint.getContext(), mainViewModel).show(); + } + }); + floatView.addButton(R.id.grid_tracker_config, "grid_tracker_config" + , R.drawable.ic_baseline_tracker_settings_24 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + if (configBarIsClose) { + openConfigBar(); + } else { + closeConfigBar(); + } + } + }); + + + //显示当前目标呼号 + mainViewModel.ft8TransmitSignal.mutableToCallsign.observe(this, new Observer() { + @Override + public void onChanged(TransmitCallsign transmitCallsign) { + binding.trackerTargetTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.target_callsign) + , transmitCallsign.callsign)); + } + }); + + //观察发射状态按钮的变化 + Observer transmittingObserver = new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (mainViewModel.ft8TransmitSignal.isActivated()) { + binding.trackerTargetTextView.setVisibility(View.VISIBLE); + } else { + binding.trackerTargetTextView.setVisibility(View.GONE); + } + + if (mainViewModel.ft8TransmitSignal.isTransmitting()) { + transButton.setImageResource(R.drawable.ic_baseline_send_red_48); + transButton.setAnimation(AnimationUtils.loadAnimation(getBaseContext(), R.anim.view_blink)); + } else { + //录音对象也要处于启动状态才可以有发射的状态 + if (mainViewModel.ft8TransmitSignal.isActivated() && mainViewModel.hamRecorder.isRunning()) { + transButton.setImageResource(R.drawable.ic_baseline_send_white_48); + } else { + transButton.setImageResource(R.drawable.ic_baseline_cancel_schedule_send_off); + } + transButton.setAnimation(null); + } + + } + }; + //显示发射状态 + mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this, transmittingObserver); + mainViewModel.ft8TransmitSignal.mutableIsActivated.observe(this, transmittingObserver); + + floatView.initLocation(); + } + + /** + * 把配置信息写到数据库 + * + * @param KeyName 关键词 + * @param Value 值 + */ + private void writeConfig(String KeyName, String Value) { + mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); + } + + private void readConfig() { + OnAfterQueryConfig queryConfig = new OnAfterQueryConfig() { + @Override + public void doOnBeforeQueryConfig(String KeyName) { + + } + + @Override + public void doOnAfterQueryConfig(String KeyName, String Value) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (KeyName.equalsIgnoreCase(DataConfigShowMode)) { + if (Value.equals("1")) { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NEW); + binding.tipsNewRadioButton.setChecked(true); + binding.tipsNewRadioButton.callOnClick(); + } else if (Value.equals("2")) { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.NONE); + binding.tipsNoneRadioButton.setChecked(true); + binding.tipsNoneRadioButton.callOnClick(); + } else { + gridOsmMapView.setShowTipsMode(GridOsmMapView.ShowTipsMode.ALL); + binding.tipsAllRadioButton.setChecked(true); + binding.tipsAllRadioButton.callOnClick(); + } + //tipsAllRadioButton + } + if (KeyName.equalsIgnoreCase(DataConfigShowQsx)) { + gridOsmMapView.setShowQSX(Value.equals("1")); + binding.trackerShowQsxSwitch.setChecked(Value.equals("1")); + binding.trackerShowQsxSwitch.callOnClick(); + } + if (KeyName.equalsIgnoreCase(DataConfigShowCQ)) { + gridOsmMapView.setShowCQ(Value.equals("1")); + binding.trackerShowCqSwitch.setChecked(Value.equals("1")); + binding.trackerShowCqSwitch.callOnClick(); + } + } + }); + } + }; + mainViewModel.databaseOpr.getConfigByKey(DataConfigShowMode, queryConfig); + mainViewModel.databaseOpr.getConfigByKey(DataConfigShowQsx, queryConfig); + mainViewModel.databaseOpr.getConfigByKey(DataConfigShowCQ, queryConfig); + } + + /** + * 马上对发起者呼叫 + * + * @param message 消息 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + private void doCallNow(Ft8Message message) { + mainViewModel.addFollowCallsign(message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() + , 1, message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + + + /** + * 设置列表滑动动作 + */ + private void initRecyclerViewAction() { + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG + , ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder + , @NonNull RecyclerView.ViewHolder target) { + return false; + } + + //@RequiresApi(api = Build.VERSION_CODES.N) + @SuppressLint("NotifyDataSetChanged") + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + if (direction == ItemTouchHelper.START) { + Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); + if (message != null) { + //呼叫的目标不能是自己 + if (!message.getCallsignFrom().equals("<...>") + && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !(message.i3 == 0 && message.n3 == 0)) { + doCallNow(message); + } + } + callingListAdapter.notifyDataSetChanged(); + //callingListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); + } + if (direction == ItemTouchHelper.END) {//删除 + callingListAdapter.deleteMessage(viewHolder.getAdapterPosition()); + callingListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); + } + } + + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); + //制作呼叫背景的图标显示 + final Drawable callIcon = ContextCompat.getDrawable(getBaseContext(), R.drawable.ic_baseline_send_red_48); + final Drawable delIcon = ContextCompat.getDrawable(getBaseContext(), R.drawable.log_item_delete_icon); + final Drawable background = new ColorDrawable(Color.LTGRAY); + + if (message == null) { + return; + } + if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 + return; + } + Drawable icon; + if (dX > 0) { + icon = delIcon; + } else { + icon = callIcon; + } + View itemView = viewHolder.itemView; + int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + int iconLeft, iconRight, iconTop, iconBottom; + int backTop, backBottom, backLeft, backRight; + backTop = itemView.getTop(); + backBottom = itemView.getBottom(); + iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + iconBottom = iconTop + icon.getIntrinsicHeight(); + if (dX > 0) { + backLeft = itemView.getLeft(); + backRight = itemView.getLeft() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconLeft = itemView.getLeft() + iconMargin; + iconRight = iconLeft + icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else if (dX < 0) { + backRight = itemView.getRight(); + backLeft = itemView.getRight() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconRight = itemView.getRight() - iconMargin; + iconLeft = iconRight - icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else { + background.setBounds(0, 0, 0, 0); + icon.setBounds(0, 0, 0, 0); + } + background.draw(c); + icon.draw(c); + + } + }).attachToRecyclerView(binding.callMessagesRecyclerView); + } + + + /** + * 菜单选项 + * + * @param item 菜单 + * @return 是否选择 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); + int position = (int) item.getActionView().getTag(); + Ft8Message ft8Message = callingListAdapter.getMessageByPosition(position); + if (ft8Message == null) return super.onContextItemSelected(item); + + GeneralVariables.resetLaunchSupervision();//复位自动监管 + switch (item.getItemId()) { + case 1://时序与发送者相反!!! + Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + } + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() + , 1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + break; + + case 3: + Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); + doCallNow(ft8Message); + break; + + case 4://回复 + Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() + , -1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + break; + + + } + + return super.onContextItemSelected(item); + } + + + @Override + protected void onResume() { + super.onResume(); + binding.osmMap.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + binding.osmMap.onPause(); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java new file mode 100644 index 0000000..9d4a309 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java @@ -0,0 +1,219 @@ +package com.bg7yoz.ft8cn.html; +/** + * Http服务内容的出框架。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.database.Cursor; + +import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +public class HtmlContext { + private static final String HTML_HEAD = " FT8CN\n" + + " \n" + + " "+ + "\n" + + "\n" + + "\n"; + private static final String HTML_TITLE = "
" + + "Welcome to FT8CN "+ BuildConfig.VERSION_NAME+"
" + +GeneralVariables.getStringFromResource(R.string.html_return) + +"
\n"; + private static final String HTML_FOOTER = "" + + "
BG7YOZ
" + + ""+GeneralVariables.getStringFromResource(R.string.html_return)+"
\n"; + + private static String HTML_BODY(String context) { + return "" + HTML_TITLE + "
"+context+"\n
" + HTML_FOOTER + ""; + } + + public static String HTML_STRING(String context) { + return HTML_HEAD + HTML_BODY(context); + } + + + public static String DEFAULT_HTML() { + return HTML_STRING("" + + "" + + "" + + "" + + "" + + "" + + "" + + + "" + + "" + + "" + + //"" + + + "" + + "" + + + "" + + + "" + + "" + + + "
" + + GeneralVariables.getStringFromResource(R.string.html_track_operation_information) +"
" + +GeneralVariables.getStringFromResource(R.string.html_track_callsign_hash_table) + +"
" + +GeneralVariables.getStringFromResource(R.string.html_trace_parsed_messages)+"
" + +GeneralVariables.getStringFromResource(R.string.html_trace_callsign_and_grid_correspondence_table) + +"
" + +GeneralVariables.getStringFromResource(R.string.html_query_swl_message) + +"
" + +GeneralVariables.getStringFromResource(R.string.html_query_qso_swl) + +"
" + +GeneralVariables.getStringFromResource(R.string.html_query_configuration_information) + +"
" + +GeneralVariables.getStringFromResource(R.string.html_query_all_table)+"
" + +GeneralVariables.getStringFromResource(R.string.html_manage_tracking_callsign)+"
" + // +GeneralVariables.getStringFromResource(R.string.html_manage_communication_callsigns)+"
" + +GeneralVariables.getStringFromResource(R.string.html_show_communication_callsigns)+"
" + +GeneralVariables.getStringFromResource(R.string.html_callsign_qth)+"
"//查询日志 + +GeneralVariables.getStringFromResource(R.string.html_query_logs)+"
" + +GeneralVariables.getStringFromResource(R.string.html_export_log) + +""+GeneralVariables.getStringFromResource(R.string.html_to_the_third_party)+"
" + +GeneralVariables.getStringFromResource(R.string.html_import_log) + +""+GeneralVariables.getStringFromResource(R.string.html_from_jtdx_lotw)+"
"); + } + + /** + * 生成表格的内容 + * @param sb html + * @param s 内容 + * @return html + */ + public static StringBuilder tableCell(StringBuilder sb, String... s){ + for (String c: s) { + sb.append(String.format("%s",c)); + } + sb.append("\n"); + return sb; + } + + /** + * 生成表格标题 + * @param sb html + * @param s 标题 + * @return html + */ + public static StringBuilder tableCellHeader(StringBuilder sb, String... s){ + for (String c: s) { + sb.append(String.format("%s",c)); + } + sb.append("\n"); + return sb; + } + @SuppressLint("DefaultLocale") + public static StringBuilder tableKeyRow(StringBuilder sb , Boolean darkMode, String key, int value){ + sb.append(String.format("%s%d\n" + ,darkMode ? "class=\"bbb\"" : "" + ,key,value)); + return sb; + } + + public static StringBuilder tableKeyRow(StringBuilder sb ,Boolean darkMode,String key,String value){ + sb.append(String.format("%s%s\n" + ,darkMode ? "class=\"bbb\"" : "" + ,key,value)); + return sb; + } + + public static StringBuilder tableRowBegin(StringBuilder sb){ + return tableRowBegin(sb,false,false); + } + public static StringBuilder tableRowBegin(StringBuilder sb,Boolean alignCenter,boolean darkMode){ + sb.append(String.format("" + ,alignCenter?"align=center":"" + ,darkMode ? "class=\"bbb\"":"")); + return sb; + } + public static StringBuilder tableRowEnd(StringBuilder sb){ + sb.append(""); + return sb; + } + + public static StringBuilder tableBegin(StringBuilder sb){ + return tableBegin(sb,false,1,true); + } + public static StringBuilder tableBegin(StringBuilder sb,boolean hasBorder,boolean fullWidth){ + return tableBegin(sb,hasBorder,1,fullWidth); + } + @SuppressLint("DefaultLocale") + public static StringBuilder tableBegin(StringBuilder sb, boolean hasBorder, int cellpadding,boolean fullWidth){ + sb.append(String.format("" + ,hasBorder ? "border=\"1\"" :"" + ,cellpadding + ,fullWidth ? "width=\"100%\"":"")); + return sb; + } + + public static StringBuilder tableEnd(StringBuilder sb){ + sb.append("
"); + return sb; + } + public static String ListTableContext(Cursor cursor,boolean fullWidth){ + return ListTableContext(cursor,false,0,fullWidth); + } + public static String ListTableContext(Cursor cursor,boolean hasBorder,int cellpadding ,boolean fullWidth) { + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result,hasBorder,cellpadding,fullWidth).append("\n"); + + //写字段名 + HtmlContext.tableRowBegin(result); + for (int i = 0; i < cursor.getColumnCount(); i++) { + HtmlContext.tableCellHeader(result,cursor.getColumnName(i)); + } + HtmlContext.tableRowEnd(result).append("\n"); + int order=0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result,false,order % 2 !=0); + for (int i = 0; i < cursor.getColumnCount(); i++) { + HtmlContext.tableCell(result,(cursor.getString(i)!=null) ? cursor.getString(i) :""); + } + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); + return result.toString(); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java new file mode 100644 index 0000000..409b5df --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/ImportTaskList.java @@ -0,0 +1,134 @@ +package com.bg7yoz.ft8cn.html; + +import android.annotation.SuppressLint; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.util.HashMap; + +public class ImportTaskList extends HashMap { + + /** + * 获取上传的任务,以session为key + * + * @param session session + * @return 任务的HTML + */ + public String getTaskHTML(int session) { + ImportTask task = this.get(session); + if (task == null) { + return GeneralVariables.getStringFromResource(R.string.null_task_html); + } + return task.getHtml(); + } + public void cancelTask(int session){ + ImportTask task = this.get(session); + if (task != null) { + task.setStatus(ImportState.CANCELED); + } + } + + /** + * 检查任务是不是在运行。 + * + * @param session 任务ID + * @return false 没有任务或任务结束 + */ + public boolean checkTaskIsRunning(int session) { + ImportTask task = this.get(session); + if (task == null) { + return false; + } else { + return task.status == ImportState.STARTING || task.status == ImportState.IMPORTING; + } + } + + /** + * 添加任务到列表,要确保线程安全 + * + * @param session session + * @param task 任务 + */ + public synchronized ImportTask addTask(int session, ImportTask task) { + this.put(session, task); + return task; + } + + public ImportTask addTask(int session) { + return addTask(session, new ImportTask(session)); + } + + + enum ImportState { + STARTING, IMPORTING, FINISHED, CANCELED + } + + public static class ImportTask { + + + int session;//session,用于记录上传会话,是一个hash + public int count = 0;//解析出总的数据量 + public int importedCount = 0;//导入的数量 + public int readErrorCount = 0;//读取数据错误数量 + public int processCount = 0; + public int updateCount = 0;//更新的数量 + public int invalidCount = 0;//无效的QSL + public int newCount = 0;//新导入的数量 + public ImportState status = ImportState.STARTING;//状态:0:开始,1:运行,2:结束,3:取消 + String message = "";//任务消息描述 + String errorMsg = ""; + + @SuppressLint("DefaultLocale") + public String getHtml() { + String htmlHeader = "\n"; + String htmlEnder = "
\n"; + String progress = String.format("%s %.1f%%(%d/%d)\n", GeneralVariables.getStringFromResource(R.string.import_progress_html) + , count == 0 ? 0 : processCount * 100f / count, processCount, count); + String cell = "%s\n"; + String errorHtml = status == ImportState.FINISHED || status == ImportState.CANCELED ? errorMsg : ""; + String doCancelButton = status == ImportState.FINISHED || status == ImportState.CANCELED ? "" + : String.format("

" + , session,GeneralVariables.getStringFromResource(R.string.import_cancel_button)); + return htmlHeader + + String.format(cell, progress) + + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_read_error_count_html), readErrorCount)) + + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_new_count_html), newCount)) + + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_update_count_html), updateCount)) + + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_invalid_count_html), invalidCount)) + + String.format(cell, String.format(GeneralVariables.getStringFromResource(R.string.import_readed_html), importedCount)) + + String.format(cell, message) + + String.format(cell, errorHtml) + + htmlEnder + + doCancelButton; + } + + public ImportTask(int session) { + this.session = session; + } + + public void setStatus(ImportState status) { + this.status = status; + setStateMSG(status); + } + + private void setStateMSG(ImportState state) { + switch (state) { + case IMPORTING: + this.message = String.format("%s" + ,GeneralVariables.getStringFromResource(R.string.log_importing_html)); + break; + case FINISHED: + this.message = String.format("%s" + ,GeneralVariables.getStringFromResource(R.string.log_import_finished_html)); + break; + case CANCELED: + this.message = String.format("%s" + ,GeneralVariables.getStringFromResource(R.string.import_canceled_html)); + break; + } + } + + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java new file mode 100644 index 0000000..860dbb2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java @@ -0,0 +1,1976 @@ +package com.bg7yoz.ft8cn.html; +/** + * Http服务的具体内容。数据库访问不需要异步方式。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import static com.bg7yoz.ft8cn.html.HtmlContext.HTML_STRING; + +import android.annotation.SuppressLint; +import android.database.Cursor; +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.connector.CableSerialPort; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.database.AfterInsertQSLData; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.database.RigNameList; +import com.bg7yoz.ft8cn.log.LogFileImport; +import com.bg7yoz.ft8cn.log.QSLRecord; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import fi.iki.elonen.NanoHTTPD; + +@SuppressWarnings("ConstantConditions") +public class LogHttpServer extends NanoHTTPD { + private final MainViewModel mainViewModel; + public static int DEFAULT_PORT = 7050; + private static final String TAG = "LOG HTTP"; + + private ImportTaskList importTaskList = new ImportTaskList();//导如日志的任务列表 + + + public LogHttpServer(MainViewModel viewModel, int port) { + super(port); + this.mainViewModel = viewModel; + + } + + @Override + public Response serve(IHTTPSession session) { + String[] uriList = session.getUri().split("/"); + String uri = ""; + String msg; + Log.i(TAG, "serve uri: " + session.getUri()); + + if (uriList.length >= 2) { + uri = uriList[1]; + } + + if (uri.equalsIgnoreCase("CONFIG")) {//查配置信息 + msg = HTML_STRING(getConfig()); + } else if (uri.equalsIgnoreCase("showQSLCallsigns")) {//显示通联过的呼号,包括最后的时间 + msg = HTML_STRING(showQslCallsigns(session)); + } else if (uri.equalsIgnoreCase("DEBUG")) {//查通联过的呼号 + msg = HTML_STRING(showDebug()); + } else if (uri.equalsIgnoreCase("SHOWHASH")) {//查通呼号的哈希表 + msg = HTML_STRING(showCallsignHash()); + } else if (uri.equalsIgnoreCase("NEWMESSAGE")) {//查本周期通联消息表 + msg = HTML_STRING(getNewMessages()); + } else if (uri.equalsIgnoreCase("MESSAGE")) {//查保存的SWL通联消息表 + return getMessages(session); + } else if (uri.equalsIgnoreCase("QSOSWLMSG")) {//查SWL QSO通联消息表 + return getSWLQsoMessages(session); + } else if (uri.equalsIgnoreCase("QSOLogs")) {//查QSO日志 + return getQsoLogs(session); + } else if (uri.equalsIgnoreCase("CALLSIGNGRID")) {//查呼号与网格的对应关系 + msg = HTML_STRING(showCallGridList()); + } else if (uri.equalsIgnoreCase("GETCALLSIGNQTH")) { + msg = HTML_STRING(getCallsignQTH(session)); + } else if (uri.equalsIgnoreCase("ALLTABLE")) {//查所有的表 + msg = HTML_STRING(getAllTableName()); + } else if (uri.equalsIgnoreCase("FOLLOWCALLSIGNS")) {//查关注的呼号 + msg = HTML_STRING(getFollowCallsigns()); + } else if (uri.equalsIgnoreCase("DELFOLLOW")) {//删除关注的呼号 + if (uriList.length >= 3) { + deleteFollowCallSign(uriList[2].replace("_", "/")); + } + msg = HTML_STRING(getFollowCallsigns()); + } else if (uri.equalsIgnoreCase("DELQSL")) { + if (uriList.length >= 3) { + deleteQSLByMonth(uriList[2].replace("_", "/")); + } + msg = HTML_STRING(showQSLTable()); + } else if (uri.equalsIgnoreCase("QSLCALLSIGNS")) {//查通联过的呼号 + msg = HTML_STRING(getQSLCallsigns()); + } else if (uri.equalsIgnoreCase("QSLTABLE")) { + msg = HTML_STRING(showQSLTable()); + } else if (uri.equalsIgnoreCase("IMPORTLOG")) { + msg = HTML_STRING(showImportLog()); + } else if (uri.equalsIgnoreCase("GETIMPORTTASK")) {//这个是用户实时获取导入状态的URI + msg = HTML_STRING(makeGetImportTaskHTML(session)); + } else if (uri.equalsIgnoreCase("CANCELTASK")) {//这个是用户取消导入的URI + msg = HTML_STRING(doCancelImport(session)); + } else if (uri.equalsIgnoreCase("IMPORTLOGDATA")) { + msg = HTML_STRING(doImportLogFile(session)); + } else if (uri.equalsIgnoreCase("SHOWALLQSL")) { + msg = HTML_STRING(showAllQSL()); + } else if (uri.equalsIgnoreCase("SHOWQSL")) { + msg = HTML_STRING(showQSLByMonth(uriList[2])); + } else if (uri.equalsIgnoreCase("DELQSLCALLSIGN")) {//删除通联过的呼号 + if (uriList.length >= 3) { + deleteQSLCallSign(uriList[2].replace("_", "/")); + } + msg = HTML_STRING(getQSLCallsigns()); + } else { + msg = HtmlContext.DEFAULT_HTML(); + } + //return newFixedLengthResponse(msg); + + try { + Response response; + if (uri.equalsIgnoreCase("DOWNALLQSL")) {//下载日志 + msg = downAllQSl(); + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); + response.addHeader("Content-Disposition", "attachment;filename=All_log.adi"); + } else if (uri.equalsIgnoreCase("DOWNQSL")) { + if (uriList.length >= 3) { + msg = downQSLByMonth(uriList[2], true); + } else { + msg = HtmlContext.DEFAULT_HTML(); + } + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); + response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); + + } else if (uri.equalsIgnoreCase("DOWNQSLNOQSL")) { + if (uriList.length >= 3) { + msg = downQSLByMonth(uriList[2], false); + } else { + msg = HtmlContext.DEFAULT_HTML(); + } + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", msg); + response.addHeader("Content-Disposition", String.format("attachment;filename=log%s.adi", uriList[2])); + + } else { + response = newFixedLengthResponse(msg); + } + return response;// + } catch (Exception exception) { + return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, exception.getMessage()); + } + } + + + @SuppressLint("DefaultLocale") + private String doImportLogFile(IHTTPSession session) { + //判断是不是POST日志文件 + if (session.getMethod().equals(Method.POST) + || session.getMethod().equals(Method.PUT)) { + Map files = new HashMap<>(); + //Map header = session.getHeaders(); + try { + session.parseBody(files); + + Log.e(TAG, "doImportLogFile: information:" + files.toString()); + String param = files.get("file1");//这个是post或put文件的key + + ImportTaskList.ImportTask task = importTaskList.addTask(param.hashCode());//生成一个新的任务 + + LogFileImport logFileImport = new LogFileImport(task, param); + + + //把提交的数据放到一个独立的线程运行,防止WEB页面停留太久 + new Thread(new Runnable() { + @Override + public void run() { + doImportADI(task, logFileImport); + } + }).start(); + + //重定向,跳转到实时导入信息界面 + return String.format("\n" + , param.hashCode()); + + } catch (IOException | ResponseException e) { + e.printStackTrace(); + return String.format(GeneralVariables.getStringFromResource(R.string.html_import_failed) + , e.getMessage()); + } + } + return GeneralVariables.getStringFromResource(R.string.html_illegal_command); + } + + + private String makeGetImportTaskHTML(IHTTPSession session) { + String script = ""; + script = "\n\n"; + Map pars = session.getParms(); + if (pars.get("session") != null) { + String s = Objects.requireNonNull(pars.get("session")); + int id = Integer.parseInt(s); + if (!importTaskList.checkTaskIsRunning(id)) {//如果任务停止,就没有必要刷新了 + script = ""; + } + return script + importTaskList.getTaskHTML(id); + } + + return script; + } + + @SuppressLint("DefaultLocale") + private String doCancelImport(IHTTPSession session) { + Map pars = session.getParms(); + Log.e(TAG, "doCancelImport: " + pars.toString()); + if (pars.get("session") != null) { + String s = Objects.requireNonNull(pars.get("session")); + int id = Integer.parseInt(s); + importTaskList.cancelTask(id); + return String.format("\n" + , id); + } + return ""; + } + + @SuppressLint("DefaultLocale") + private void doImportADI(ImportTaskList.ImportTask task, LogFileImport logFileImport) { + task.setStatus(ImportTaskList.ImportState.IMPORTING); + ArrayList> recordList = logFileImport.getLogRecords();//以正则表达式:[<][Ee][Oo][Rr][>]分行 + task.importedCount = 0; + task.count = recordList.size();//总行数 + for (HashMap record : recordList) { + if (task.status == ImportTaskList.ImportState.CANCELED) break;//检查是不是取消导入 + + QSLRecord qslRecord = new QSLRecord(record); + task.processCount++; + if (mainViewModel.databaseOpr.doInsertQSLData(qslRecord, new AfterInsertQSLData() { + @Override + public void doAfterInsert(boolean isInvalid, boolean isNewQSL) { + if (isInvalid) { + task.invalidCount++; + return; + } + if (isNewQSL) { + task.newCount++; + } else { + task.updateCount++; + } + } + })) { + task.importedCount++; + } + } + + + //此处是显示错误的数据 + StringBuilder temp = new StringBuilder(); + if (logFileImport.getErrorCount() > 0) { + temp.append(""); + temp.append(String.format("\n", logFileImport.getErrorCount())); + for (int key : logFileImport.getErrorLines().keySet()) { + temp.append(String.format("\n" + , key, logFileImport.getErrorLines().get(key))); + } + + temp.append("
%d malformed logs
%d
%s
"); + } + + task.errorMsg = temp.toString(); + if (task.status!= ImportTaskList.ImportState.CANCELED) { + task.setStatus(ImportTaskList.ImportState.FINISHED); + } + mainViewModel.databaseOpr.getQslDxccToMap();//更新一下已经通联的分区 + } + + + /** + * 获取配置信息 + * + * @return config表内容 + */ + private String getConfig() { + Cursor cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select KeyName,Value from config", null); + return HtmlContext.ListTableContext(cursor, true, 4, false); + } + + /** + * 获取通联过的呼号,包括:呼号、最后时间、频段,波长、网格 + * + * @return config表内容 + */ + private String showQslCallsigns(IHTTPSession session) { + String callsign = ""; + //读取查询的参数 + Map pars = session.getParms(); + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + String where = String.format("%%%s%%", callsign); + + String html = String.format("

%s" + + "

\n" + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_message_query)); + + Cursor cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select q.[call] as callsign ,q.gridsquare,q.band||\"(\"||q.freq||\")\" as band \n" + + ",q.qso_date||\"-\"||q.time_on as last_time from QSLTable q \n" + + "inner join QSLTable q2 ON q.id =q2.id \n" + + "where q.[call] like ?\n" + + "group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.time_on,q.band\n" + + "HAVING q.qso_date||q.time_on =MAX(q2.qso_date||q2.time_on) \n", new String[]{where}); + return html + HtmlContext.ListTableContext(cursor, true, 3, false); + + } + + /** + * 获取全部的表名 + * + * @return html + */ + private String getAllTableName() { + Cursor cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select * from sqlite_master where type='table'", null); + return HtmlContext.ListTableContext(cursor, true, 4, true); + } + + @SuppressLint({"Range", "DefaultLocale"}) + private String getCallsignQTH(IHTTPSession session) { + String callsign = ""; + String grid = ""; + //读取查询的参数 + Map pars = session.getParms(); + + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("grid") != null) { + grid = Objects.requireNonNull(pars.get("grid")); + } + String whereCallsign = String.format("%%%s%%", callsign.toUpperCase()); + String whereGrid = String.format("%%%s%%", grid.toUpperCase()); + Cursor cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select callsign ,grid,updateTime from CallsignQTH where (callsign like ?) and (grid like ?)" + , new String[]{whereCallsign, whereGrid}); + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result, true, 2, false).append("\n"); + + result.append(String.format("
%s
\n" + + "%s\n" + + "


\n" + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , grid + , GeneralVariables.getStringFromResource(R.string.html_message_query))); + //写字段名 + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_distance) + , GeneralVariables.getStringFromResource(R.string.html_update_time) + ).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + + @SuppressLint("SimpleDateFormat") SimpleDateFormat formatTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0); + Date date = new Date(cursor.getLong(cursor.getColumnIndex("updateTime"))); + + HtmlContext.tableCell(result + , cursor.getString(cursor.getColumnIndex("callsign")) + , cursor.getString(cursor.getColumnIndex("grid")) + , MaidenheadGrid.getDistStr(GeneralVariables.getMyMaidenhead4Grid() + , cursor.getString(cursor.getColumnIndex("grid"))) + , formatTime.format(date) + ).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + HtmlContext.tableEnd(result).append("
\n"); + result.append(String.format("%d", order)); + cursor.close(); + return result.toString(); + } + + + /** + * 获取关注的呼号 + * + * @return HTML + */ + private String getFollowCallsigns() { + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from followCallsigns", null); + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result, true, 3, false).append("\n"); + + //写字段名 + HtmlContext.tableRowBegin(result).append("\n"); + for (int i = 0; i < cursor.getColumnCount(); i++) { + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); + } + HtmlContext.tableRowEnd(result).append("\n"); + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); + for (int i = 0; i < cursor.getColumnCount(); i++) { + HtmlContext.tableCell(result + , cursor.getString(i) + , String.format("%s" + , cursor.getString(i).replace("/", "_") + , GeneralVariables.getStringFromResource(R.string.html_delete)) + ).append("\n"); + } + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); + return result.toString(); + } + + /** + * 删除关注的呼号 + * + * @param callsign 关注的呼号 + */ + private void deleteFollowCallSign(String callsign) { + mainViewModel.databaseOpr.getDb().execSQL("delete from followCallsigns where callsign=?", new String[]{callsign}); + } + + private void deleteQSLByMonth(String month) { + mainViewModel.databaseOpr.getDb().execSQL("delete from QSLTable where SUBSTR(qso_date,1,6)=? \n" + , new String[]{month}); + } + + + /** + * 查询通联过的呼号 + * + * @return HTML + */ + @SuppressLint("Range") + private String getQSLCallsigns() { + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select * from QslCallsigns order by ID desc", null); + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + + //写字段名 + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time) + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time) + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_qsl_band) + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.html_qsl_manual_confirmation) + , GeneralVariables.getStringFromResource(R.string.html_qsl_lotw_confirmation) + , GeneralVariables.getStringFromResource(R.string.html_qsl_data_source) + , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0); + HtmlContext.tableCell(result + , cursor.getString(cursor.getColumnIndex("startTime")) + , cursor.getString(cursor.getColumnIndex("finishTime")) + , cursor.getString(cursor.getColumnIndex("callsign")) + , cursor.getString(cursor.getColumnIndex("mode")) + , cursor.getString(cursor.getColumnIndex("grid")) + , cursor.getString(cursor.getColumnIndex("band")) + , cursor.getString(cursor.getColumnIndex("band_i")) + "Hz" + , (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) + ? "" : "×" + , (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) + ? "" : "×" + , (cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1) + ? GeneralVariables.getStringFromResource(R.string.html_qsl_import_data_from_external) + : GeneralVariables.getStringFromResource(R.string.html_qsl_native_data) + , String.format("%s" + , cursor.getString(cursor.getColumnIndex("ID")) + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); + return result.toString(); + } + + private void deleteQSLCallSign(String callsign) { + mainViewModel.databaseOpr.getDb().execSQL("delete from QslCallsigns where id=?", new String[]{callsign}); + } + + /** + * 显示呼号与网格的对应关系 + * + * @return html + */ + @SuppressLint("DefaultLocale") + private String showCallGridList() { + StringBuilder result = new StringBuilder(); + result.append(""); + result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_callsign_grid_total) + , GeneralVariables.callsignAndGrids.size())); + HtmlContext.tableBegin(result, true, 1, false); + HtmlContext.tableRowBegin(result); + result.append(String.format("%s", GeneralVariables.getStringFromResource(R.string.html_callsign))); + result.append(String.format("%s", GeneralVariables.getStringFromResource(R.string.html_qsl_grid))); + HtmlContext.tableRowEnd(result).append("\n"); + + result.append(GeneralVariables.getCallsignAndGridToHTML()); + HtmlContext.tableEnd(result).append("
\n"); + + return result.toString(); + } + + /** + * 显示调试信息 + * + * @return html + */ + @SuppressLint("DefaultLocale") + private String showDebug() { + StringBuilder result = new StringBuilder(); + result.append(""); + + HtmlContext.tableBegin(result, true, 5, false).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_variable) + , GeneralVariables.getStringFromResource(R.string.html_value)).append("\n"); + + HtmlContext.tableKeyRow(result, false, "UTC" + , UtcTimer.getTimeStr(mainViewModel.timerSec.getValue())); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_my_callsign) + , GeneralVariables.myCallsign); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_my_grid) + , GeneralVariables.getMyMaidenheadGrid()); + + HtmlContext.tableKeyRow(result, true//消息最大缓存条数 + , GeneralVariables.getStringFromResource(R.string.html_max_message_cache) + , String.format("%d", GeneralVariables.MESSAGE_COUNT)); + + HtmlContext.tableKeyRow(result, false//音量大小 + , GeneralVariables.getStringFromResource(R.string.signal_strength) + , String.format("%.0f%%\n", GeneralVariables.volumePercent * 100f)); + + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_audio_bits) + , GeneralVariables.audioOutput32Bit ? + GeneralVariables.getStringFromResource(R.string.audio32_bit) + : GeneralVariables.getStringFromResource(R.string.audio16_bit)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_audio_rate) + , String.format("%dHz", GeneralVariables.audioSampleRate)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_decodes_in_this_cycle) + , String.format("%d", mainViewModel.currentDecodeCount)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.decode_mode_text) + , GeneralVariables.deepDecodeMode + ? GeneralVariables.getStringFromResource(R.string.deep_mode) + : GeneralVariables.getStringFromResource(R.string.fast_mode)); + + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_total_number_of_decodes) + , String.format("%d", mainViewModel.ft8Messages.size())); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_in_recording_state) + , Boolean.TRUE.equals(mainViewModel.mutableIsRecording.getValue()) + ? GeneralVariables.getStringFromResource(R.string.html_recording) + : GeneralVariables.getStringFromResource(R.string.html_no_recording)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_time_consuming_for_this_decoding) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_average_delay_time_of_this_cycle) + , String.format(GeneralVariables.getStringFromResource(R.string.html_seconds) + , mainViewModel.mutableTimerOffset.getValue())); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_sound_frequency) + , String.format("%.0fHz", GeneralVariables.getBaseFrequency())); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_transmission_delay_time) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.transmitDelay)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_launch_supervision) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.launchSupervision)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_automatic_program_run_time) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.launchSupervisionCount())); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_no_reply_limit) + , GeneralVariables.noReplyLimit); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_no_reply_count) + , GeneralVariables.noReplyCount); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.follow_cq) + , String.valueOf(GeneralVariables.autoFollowCQ)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.auto_call_follow) + , String.valueOf(GeneralVariables.autoCallFollow)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_target_callsign) + , (mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue() != null) + ? mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue().callsign : ""); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_sequential) + , mainViewModel.ft8TransmitSignal.sequential); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.synFrequency) + , String.valueOf(GeneralVariables.synFrequency)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.tran_delay) + , GeneralVariables.transmitDelay); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.ptt_delay) + , GeneralVariables.pttDelay); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.rig_name) + , RigNameList.getInstance( + GeneralVariables.getMainContext()).getRigNameByIndex(GeneralVariables.modelNo).modelName); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_mark_message) + , String.format("%s", mainViewModel.markMessage + ? GeneralVariables.getStringFromResource(R.string.html_marking_message) + : GeneralVariables.getStringFromResource(R.string.html_do_not_mark_message))); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_operation_mode) + , ControlMode.getControlModeStr(GeneralVariables.controlMode)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_civ_address) + , String.format("0x%2X", GeneralVariables.civAddress)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_baud_rate) + , GeneralVariables.baudRate); + + result.append(""); + result.append(""); + result.append(GeneralVariables.getStringFromResource(R.string.html_available_serial_ports)); + result.append("\n"); + result.append(""); + if (mainViewModel.mutableSerialPorts != null) { + if (mainViewModel.mutableSerialPorts.getValue().size() == 0) { + result.append("-"); + } + } + for (CableSerialPort.SerialPort serialPort : Objects.requireNonNull(mainViewModel.mutableSerialPorts.getValue())) { + result.append(serialPort.information()).append("
\n"); + } + result.append(""); + result.append("\n"); + + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_instruction_set) + , (mainViewModel.baseRig != null) ? mainViewModel.baseRig.getName() : "-"); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_connect_mode) + , (GeneralVariables.controlMode == ControlMode.VOX) ? "-" + : ConnectMode.getModeStr(GeneralVariables.connectMode)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_baud_rate) + , GeneralVariables.baudRate); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band) + , GeneralVariables.getBandString()); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_radio_frequency) + , (mainViewModel.baseRig != null) + ? BaseRigOperation.getFrequencyStr(mainViewModel.baseRig.getFreq()) : "-"); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_flex_max_rf_power) + , String.format("%d W", GeneralVariables.flexMaxRfPower)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_atu_tune_power) + , String.format("%d W", GeneralVariables.flexMaxTunePower)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.be_excluded_callsigns) + , GeneralVariables.getExcludeCallsigns()); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.config_save_swl) + , String.valueOf(GeneralVariables.saveSWLMessage)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.config_save_swl_qso) + , String.valueOf(GeneralVariables.saveSWL_QSO)); + + HtmlContext.tableEnd(result).append("
\n"); + + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + result.append(String.format("%s\n" + , String.format(GeneralVariables.getStringFromResource(R.string.html_successful_callsign) + , GeneralVariables.getBandString()))); + + result.append(""); + for (int i = 0; i < GeneralVariables.QSL_Callsign_list.size(); i++) { + result.append(GeneralVariables.QSL_Callsign_list.get(i)); + result.append(", "); + if (((i + 1) % 10) == 0) { + result.append("\n"); + } + } + result.append("\n"); + HtmlContext.tableEnd(result).append("
\n"); + + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + result.append(String.format("%s\n" + , GeneralVariables.getStringFromResource(R.string.html_tracking_callsign))); + + result.append(""); + for (int i = 0; i < GeneralVariables.followCallsign.size(); i++) { + result.append(GeneralVariables.followCallsign.get(i)); + result.append(", "); + if (((i + 1) % 10) == 0) { + result.append("\n"); + } + } + result.append("\n"); + HtmlContext.tableEnd(result).append("\n"); + + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + result.append(String.format("%s\n" + , GeneralVariables.getStringFromResource(R.string.html_tracking_qso_information))); + + result.append(""); + result.append(GeneralVariables.qslRecordList.toHTML()); + result.append("\n"); + HtmlContext.tableEnd(result).append("\n"); + + return result.toString(); + } + + /** + * 显示呼号的HASH + * + * @return html + */ + private String showCallsignHash() { + StringBuilder result = new StringBuilder(); + result.append(""); + HtmlContext.tableBegin(result, true, 3, false).append("\n"); + HtmlContext.tableRowBegin(result); + //表头 + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_hash_value)); + + HtmlContext.tableRowEnd(result).append("\n"); + + + int order = 0; + for (Map.Entry entry : Ft8Message.hashList.entrySet()) { + HtmlContext.tableRowBegin(result, false, (order / 3) % 2 != 0); + + HtmlContext.tableCell(result, entry.getValue()); + HtmlContext.tableCell(result, String.format(" 0x%x ", entry.getKey())); + HtmlContext.tableRowEnd(result).append("\n"); + + order++; + } + HtmlContext.tableEnd(result).append("\n"); + + return result.toString(); + } + + @SuppressLint("Range") + private Response exportSWLMessage(String exportFile, String callsign, String start_date, String end_date) { + Response response; + StringBuilder fileName = new StringBuilder(); + fileName.append("message"); + if (callsign.length() > 0) { + fileName.append("_"); + fileName.append(callsign.replace("/", "_") + .replace("\\", "_") + .replace(":", "_") + .replace("?", "_") + .replace("*", "_") + .replace("|", "_") + .replace("\"", "_") + .replace("'", "_") + .replace("<", "_") + .replace(".", "_") + .replace(">", "_")); + } + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); + } + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); + } + fileName.append(".").append(exportFile); + + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!start_date.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)>=\"%s\") " + , start_date.replace("-", ""))); + } + if (!end_date.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)<=\"%s\") " + , end_date.replace("-", ""))); + } + String whereStr = String.format("%%%s%%", callsign); + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + + dateSql + + " order by ID " + , new String[]{whereStr, whereStr}); + + StringBuilder result = new StringBuilder(); + + String formatStr; + if (exportFile.equalsIgnoreCase("CSV")) { + formatStr = "%s,%.3f,Rx,%s,%d,%.1f,%d,%s\n"; + } else { + formatStr = "%s %12.3f Rx %s %6d %4.1f %4d %s\n"; + } + + while (cursor.moveToNext()) { + String utcTime = cursor.getString(cursor.getColumnIndex("UTC")); + int dB = cursor.getInt(cursor.getColumnIndex("SNR")); + float dt = cursor.getFloat(cursor.getColumnIndex("TIME_SEC")); + int freq = cursor.getInt(cursor.getColumnIndex("FREQ")); + String callTo = cursor.getString(cursor.getColumnIndex("CALL_TO")); + String protocol = cursor.getString(cursor.getColumnIndex("Protocol")); + String callFrom = cursor.getString(cursor.getColumnIndex("CALL_FROM")); + String extra = cursor.getString(cursor.getColumnIndex("EXTRAL")); + long band = cursor.getLong(cursor.getColumnIndex("BAND")); + + result.append(String.format(formatStr + , utcTime, (band / 1000f / 1000f), protocol, dB, dt, freq, String.format("%s %s %s", callTo, callFrom, extra))); + } + cursor.close(); + + + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", result.toString()); + response.addHeader("Content-Disposition" + , String.format("attachment;filename=%s", fileName)); + + return response; + } + + /** + * 查SWL消息表 + * + * @return html + */ + @SuppressLint({"DefaultLocale", "Range"}) + private Response getMessages(IHTTPSession session) { + int pageSize = 100; + String callsign = ""; + + + StringBuilder result = new StringBuilder(); + String startDate = ""; + String endDate = ""; + String exportFile = ""; + + //读取查询的参数 + Map pars = session.getParms(); + int pageIndex = 1; + if (pars.get("page") != null) { + pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); + } + if (pars.get("pageSize") != null) { + pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); + } + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("start_date") != null) { + startDate = Objects.requireNonNull(pars.get("start_date")); + } + if (pars.get("end_date") != null) { + endDate = Objects.requireNonNull(pars.get("end_date")); + } + String whereStr = String.format("%%%s%%", callsign); + + if (pars.get("exportFile") != null) { + exportFile = Objects.requireNonNull(pars.get("exportFile")); + } + + //导出到文件中 + if (exportFile.equalsIgnoreCase("CSV") + || exportFile.equalsIgnoreCase("TXT")) { + return exportSWLMessage(exportFile, callsign, startDate, endDate); + } + + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append(""); + + + result.append(String.format("%s" + + " , %s
" + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_csv) + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_text))); + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!startDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)>=\"%s\") " + , startDate.replace("-", ""))); + } + if (!endDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(UTC,1,8)<=\"%s\") " + , endDate.replace("-", ""))); + } + //计算总的记录数 + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select count(*) as rc from SWLMessages " + + "where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?))" + dateSql + , new String[]{whereStr, whereStr}); + cursor.moveToFirst(); + int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); + if (pageIndex > pageCount) pageIndex = pageCount; + cursor.close(); + + //查询、每页消息数设定 + result.append(String.format("
%s , %s" + + "" +//页码及页大小 + "
\n%s " +//呼号 + "  
\n" + + "
\n%s " +//起始时间 + " \n%s 
" //结束时间 + + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) + , GeneralVariables.getStringFromResource(R.string.html_message_page_size) + , pageSize + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_message_query) + , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) + , startDate + , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) + , endDate)); + + + //定位页,第一页、上一页、下一页,最后一页 + result.append(String.format("|<" + + "  <<" + + "" + + ">>" + + "  >|
\n" + , 1, pageSize, callsign, startDate, endDate + , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate + , pageIndex + , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate + , pageCount, pageSize, callsign, startDate, endDate)); + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result).append("\n"); + + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + + dateSql + + " order by ID LIMIT(%d),%d " + , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); + + //result.append("\n"); + HtmlContext.tableBegin(result, false, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result, "No."); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_protocol)); + HtmlContext.tableCellHeader(result, "i3.n3", "UTC", "dB", "Δt"); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_qsl_freq)); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.message)); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + + + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0); + + int i3 = cursor.getInt(cursor.getColumnIndex("I3")); + int n3 = cursor.getInt(cursor.getColumnIndex("N3")); + String utcTime = cursor.getString(cursor.getColumnIndex("UTC")); + int dB = cursor.getInt(cursor.getColumnIndex("SNR")); + float dt = cursor.getFloat(cursor.getColumnIndex("TIME_SEC")); + int freq = cursor.getInt(cursor.getColumnIndex("FREQ")); + String protocol = cursor.getString(cursor.getColumnIndex("Protocol")); + String callTo = cursor.getString(cursor.getColumnIndex("CALL_TO")); + String callFrom = cursor.getString(cursor.getColumnIndex("CALL_FROM")); + String extra = cursor.getString(cursor.getColumnIndex("EXTRAL")); + long band = cursor.getLong(cursor.getColumnIndex("BAND")); + + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, protocol, Ft8Message.getCommandInfoByI3N3(i3, n3)); + HtmlContext.tableCell(result, utcTime); + HtmlContext.tableCell(result, String.format("%d", dB)); + HtmlContext.tableCell(result, String.format("%.1f", dt)); + HtmlContext.tableCell(result, String.format("%dHz", freq)); + HtmlContext.tableCell(result, String.format("" + + "%s  " + + "%s  %s", pageSize, callTo.replace("<", "") + .replace(">", "") + , callTo.replace("<", "<") + .replace(">", ">") + , pageSize, callFrom.replace("<", "") + .replace(">", "") + , callFrom.replace("<", "<") + .replace(">", ">"), extra)); + HtmlContext.tableCell(result, BaseRigOperation.getFrequencyStr(band)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + + order++; + } + cursor.close(); + HtmlContext.tableEnd(result).append("
\n"); + //result.append("

"); + + + return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); + //return result.toString(); + + } + + + /** + * 把swo的QSO日志导出到文件 + * + * @param exportFile 文件名 + * @param callsign 呼号 + * @param start_date 起始日期 + * @param end_date 结束日期 + * @return 数据 + */ + @SuppressLint("Range") + private Response exportSWLQSOMessage(String exportFile, String callsign, String start_date, String end_date) { + Response response; + StringBuilder fileName = new StringBuilder(); + fileName.append("swl_qso"); + if (callsign.length() > 0) { + fileName.append("_"); + fileName.append(callsign.replace("/", "_") + .replace("\\", "_") + .replace(":", "_") + .replace("?", "_") + .replace("*", "_") + .replace("|", "_") + .replace("\"", "_") + .replace("'", "_") + .replace("<", "_") + .replace(".", "_") + .replace(">", "_")); + } + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); + } + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); + } + fileName.append(".").append(exportFile); + + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!start_date.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " + , start_date.replace("-", ""))); + } + if (!end_date.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " + , end_date.replace("-", ""))); + } + String whereStr = String.format("%%%s%%", callsign); + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + dateSql + + " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); + + + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" + , downQSLTable(cursor, true)); + response.addHeader("Content-Disposition" + , String.format("attachment;filename=%s", fileName)); + + return response; + } + + + /** + * 查询SWL日志 + * + * @param session 会话 + * @return html + */ + @SuppressLint({"DefaultLocale", "Range"}) + private Response getSWLQsoMessages(IHTTPSession session) { + int pageSize = 100; + String callsign = ""; + + + StringBuilder result = new StringBuilder(); + String startDate = ""; + String endDate = ""; + String exportFile = ""; + + //读取查询的参数 + Map pars = session.getParms(); + int pageIndex = 1; + if (pars.get("page") != null) { + pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); + } + if (pars.get("pageSize") != null) { + pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); + } + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("start_date") != null) { + startDate = Objects.requireNonNull(pars.get("start_date")); + } + if (pars.get("end_date") != null) { + endDate = Objects.requireNonNull(pars.get("end_date")); + } + String whereStr = String.format("%%%s%%", callsign); + + if (pars.get("exportFile") != null) { + exportFile = Objects.requireNonNull(pars.get("exportFile")); + } + + //导出到文件中 + if (exportFile.equalsIgnoreCase("ADI")) { + return exportSWLQSOMessage(exportFile, callsign, startDate, endDate); + } + + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + result.append(""); + + result.append(String.format("%s" + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_adi))); + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!startDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " + , startDate.replace("-", ""))); + } + if (!endDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " + , endDate.replace("-", ""))); + } + //计算总的记录数 + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select count(*) as rc from SWLQSOTable " + + "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql + , new String[]{whereStr, whereStr}); + cursor.moveToFirst(); + int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); + if (pageIndex > pageCount) pageIndex = pageCount; + cursor.close(); + + //查询、每页消息数设定 + result.append(String.format("
%s , %s" + + "" +//页码及页大小 + "
\n%s " +//呼号 + "  
\n" + + "
\n%s " +//起始时间 + " \n%s 
" //结束时间 + + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) + , GeneralVariables.getStringFromResource(R.string.html_message_page_size) + , pageSize + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_message_query) + , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) + , startDate + , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) + , endDate)); + + + //定位页,第一页、上一页、下一页,最后一页 + result.append(String.format("|<" + + "  <<" + + "" + + ">>" + + "  >|
\n" + + , 1, pageSize, callsign, startDate, endDate + , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate + , pageIndex + , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate + , pageCount, pageSize, callsign, startDate, endDate)); + + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result).append("\n"); + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + dateSql + + " order by qso_date desc,time_on desc LIMIT(%d),%d " + , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); + + HtmlContext.tableBegin(result, false, true).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result, "No." + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"call" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode)//"mode" + , GeneralVariables.getStringFromResource(R.string.html_rst_sent)//"rst_sent" + , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd)//"rst_rcvd" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" + , GeneralVariables.getStringFromResource(R.string.html_qsl_band)//"band" + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq)//"freq" + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") + .append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); + + String call = cursor.getString(cursor.getColumnIndex("call")); + String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); + String mode = cursor.getString(cursor.getColumnIndex("mode")); + String rst_sent = cursor.getString(cursor.getColumnIndex("rst_sent")); + String rst_rcvd = cursor.getString(cursor.getColumnIndex("rst_rcvd")); + String qso_date = cursor.getString(cursor.getColumnIndex("qso_date")); + String time_on = cursor.getString(cursor.getColumnIndex("time_on")); + String qso_date_off = cursor.getString(cursor.getColumnIndex("qso_date_off")); + String time_off = cursor.getString(cursor.getColumnIndex("time_off")); + String band = cursor.getString(cursor.getColumnIndex("band")); + String freq = cursor.getString(cursor.getColumnIndex("freq")); + String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); + String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); + String comment = cursor.getString(cursor.getColumnIndex("comment")); + + + //生成数据表的一行 + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, String.format("%s" + , pageSize, call.replace("<", "") + .replace(">", "") + , call.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare); + HtmlContext.tableCell(result, mode, rst_sent, rst_rcvd, qso_date, time_on, qso_date_off, time_off); + HtmlContext.tableCell(result, band, freq); + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , station_callsign.replace("<", "") + .replace(">", "") + , station_callsign.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare + , comment).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + cursor.close(); + HtmlContext.tableEnd(result).append("
\n"); + + return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); + } + + + /** + * 把QSO日志导出到文件 + * + * @param exportFile 文件名 + * @param callsign 呼号 + * @param start_date 起始日期 + * @param end_date 结束日期 + * @return 数据 + */ + @SuppressLint("Range") + private Response exportQSOLogs(String exportFile, String callsign, String start_date, String end_date, String extWhere) { + Response response; + StringBuilder fileName = new StringBuilder(); + fileName.append("qso_log"); + if (callsign.length() > 0) { + fileName.append("_"); + fileName.append(callsign.replace("/", "_") + .replace("\\", "_") + .replace(":", "_") + .replace("?", "_") + .replace("*", "_") + .replace("|", "_") + .replace("\"", "_") + .replace("'", "_") + .replace("<", "_") + .replace(".", "_") + .replace(">", "_")); + } + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); + } + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); + } + fileName.append(".").append(exportFile); + + + Cursor cursor; + String whereStr = String.format("%%%s%%", callsign); + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + extWhere + + " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); + + + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" + , downQSLTable(cursor, false)); + response.addHeader("Content-Disposition" + , String.format("attachment;filename=%s", fileName)); + + return response; + } + + /** + * 查询QSO日志 + * + * @param session 会话 + * @return html + */ + @SuppressLint({"DefaultLocale", "Range"}) + private Response getQsoLogs(IHTTPSession session) { + int pageSize = 100; + String callsign = ""; + + + StringBuilder result = new StringBuilder(); + String startDate = ""; + String endDate = ""; + String exportFile = ""; + String qIsQSL = ""; + String qIsImported = ""; + + //读取查询的参数 + Map pars = session.getParms(); + int pageIndex = 1; + if (pars.get("page") != null) { + pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); + } + if (pars.get("pageSize") != null) { + pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); + } + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("start_date") != null) { + startDate = Objects.requireNonNull(pars.get("start_date")); + } + if (pars.get("end_date") != null) { + endDate = Objects.requireNonNull(pars.get("end_date")); + } + String whereStr = String.format("%%%s%%", callsign); + + if (pars.get("exportFile") != null) { + exportFile = Objects.requireNonNull(pars.get("exportFile")); + } + if (pars.get("QSL") != null) { + qIsQSL = Objects.requireNonNull(pars.get("QSL")); + } + if (pars.get("Imported") != null) { + qIsImported = Objects.requireNonNull(pars.get("Imported")); + } + + result.append("
\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append(""); + result.append(String.format("%s\n" + , callsign, startDate, endDate, qIsQSL, qIsImported + , GeneralVariables.getStringFromResource(R.string.html_export_adi))); + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!startDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " + , startDate.replace("-", ""))); + } + if (!endDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " + , endDate.replace("-", ""))); + } + if (!qIsQSL.equals("")) { + dateSql.append(String.format(" AND ((isQSl = %s) %s (isLotW_QSL = %s))" + , qIsQSL, qIsQSL.equals("1") ? "OR" : "AND", qIsQSL)); + } + if (!qIsImported.equals("")) { + dateSql.append(String.format(" AND (isLotW_import = %s)", qIsImported)); + } + + + //导出到文件中 + if (exportFile.equalsIgnoreCase("ADI")) { + return exportQSOLogs(exportFile, callsign, startDate, endDate, dateSql.toString()); + } + + //计算总的记录数 + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select count(*) as rc from QSLTable " + + "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql + , new String[]{whereStr, whereStr}); + cursor.moveToFirst(); + int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); + if (pageIndex > pageCount) pageIndex = pageCount; + cursor.close(); + + //查询、每页消息数设定 + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableRowBegin(result).append("\n"); + + result.append(String.format("%s , %s" + + "" +//页码及页大小 + "  
\n" + + "
\n%s " +//呼号 + "\n%s " +//起始时间 + "\n%s \n" +//结束时间 + " \n" + + " \n" + + "
\n" + + + "
\n" + + " QSL" + + "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
" + + "
\n" + + "
\n" + + "
\n" + + " " + GeneralVariables.getStringFromResource(R.string.html_qso_source) + "" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
" + + "
" + + "
" + + "\n" + + " "); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result); + result.append(""); + //"
" + + + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) + , GeneralVariables.getStringFromResource(R.string.html_message_page_size) + , pageSize + , GeneralVariables.getStringFromResource(R.string.html_message_query) + + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) + , startDate + , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) + , endDate + + , qIsQSL.equals("") ? "checked=\"true\"" : "" + , qIsQSL.equals("0") ? "checked=\"true\"" : "" + , qIsQSL.equals("1") ? "checked=\"true\"" : "" + + + , qIsImported.equals("") ? "checked=\"true\"" : "" + , qIsImported.equals("0") ? "checked=\"true\"" : "" + , qIsImported.equals("1") ? "checked=\"true\"" : "" + )); + + + //定位页,第一页、上一页、下一页,最后一页 + result.append(String.format("|<" + + "  <<" + + "" + + ">>\n" + + "  >|\n" + + , 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageIndex + , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageCount, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported)); + result.append("
\n" + + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + dateSql + + " order by qso_date desc,time_on desc LIMIT(%d),%d " + , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); + + HtmlContext.tableBegin(result, false, true).append("\n"); + + //表头 + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result, "No.", "QSL" + , GeneralVariables.getStringFromResource(R.string.html_qso_source) + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) + , GeneralVariables.getStringFromResource(R.string.html_rst_sent) + , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd) + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" + , GeneralVariables.getStringFromResource(R.string.html_qsl_band) + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") + .append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + + //表内容 + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); + + String call = cursor.getString(cursor.getColumnIndex("call")); + boolean isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; + boolean isLotW_Import = cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1; + boolean isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; + String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); + String mode = cursor.getString(cursor.getColumnIndex("mode")); + String rst_sent = cursor.getString(cursor.getColumnIndex("rst_sent")); + String rst_rcvd = cursor.getString(cursor.getColumnIndex("rst_rcvd")); + String qso_date = cursor.getString(cursor.getColumnIndex("qso_date")); + String time_on = cursor.getString(cursor.getColumnIndex("time_on")); + String qso_date_off = cursor.getString(cursor.getColumnIndex("qso_date_off")); + String time_off = cursor.getString(cursor.getColumnIndex("time_off")); + String band = cursor.getString(cursor.getColumnIndex("band")); + String freq = cursor.getString(cursor.getColumnIndex("freq")); + String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); + String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); + String comment = cursor.getString(cursor.getColumnIndex("comment")); + + + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, (isQSL || isLotW_QSL) ? "" : ""); + HtmlContext.tableCell(result, isLotW_Import ? + String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_qso_external)) + : String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_qso_raw)));//是否是导入的 + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , call.replace("<", "") + .replace(">", "") + , call.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare, mode, rst_sent, rst_rcvd + , qso_date, time_on, qso_date_off, time_off, band, freq); + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , station_callsign.replace("<", "") + .replace(">", "") + , station_callsign.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare + , comment).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + + order++; + } + cursor.close(); + HtmlContext.tableEnd(result).append("
\n"); + + return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); + } + + /** + * 获取全部通联日志 + * + * @return HTML + */ + private String showAllQSL() { + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select * from QSLTable order by ID DESC ", null); + return HtmlContext.ListTableContext(cursor, true); + } + + /** + * 按月获取日志 + * + * @param month 月份yyyymm + * @return HTML + */ + private String showQSLByMonth(String month) { + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select * from QSLTable WHERE SUBSTR(qso_date,1,?)=? \n" + + "order by ID DESC ", new String[]{String.valueOf(month.length()), month}); + return HtmlContext.ListTableContext(cursor, true); + } + + /** + * 查最新解码的消息 + * + * @return html + */ + private String getNewMessages() { + StringBuilder result = new StringBuilder(); + result.append(""); + HtmlContext.tableBegin(result, false, true).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result, "UTC", "dB", "Δt" + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.message) + , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)); + HtmlContext.tableRowEnd(result).append("\n"); + + int order = 0; + if (mainViewModel.currentMessages != null) { + for (Ft8Message message : mainViewModel.currentMessages) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0) + .append("\n").append(message.toHtml()); + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + } + HtmlContext.tableEnd(result).append("
\n"); + return result.toString(); + } + + /** + * 显示导入FT8CN日志文件的HTML + * + * @return HTML + */ + private String showImportLog() { + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result, false, true); + HtmlContext.tableCell(result, String.format("%s%s%s" + , GeneralVariables.getStringFromResource(R.string.html_please_select) + , GeneralVariables.getStringFromResource(R.string.html_adi_format) + , GeneralVariables.getStringFromResource(R.string.html_file_in_other_format))); + + HtmlContext.tableRowEnd(result).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + + result.append("
\n" + + " \n" + + " \n" + + "
"); + HtmlContext.tableRowEnd(result).append("\n"); + HtmlContext.tableEnd(result).append("\n"); + return result.toString(); + } + + @SuppressLint("Range") + private String showQSLTable() { + + StringBuilder result = new StringBuilder(); + HtmlContext.tableBegin(result, false, 1, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_time) + , GeneralVariables.getStringFromResource(R.string.html_total) + , GeneralVariables.getStringFromResource(R.string.html_operation) + , GeneralVariables.getStringFromResource(R.string.html_operation) + , GeneralVariables.getStringFromResource(R.string.html_operation) + ).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select count(*) as b from QSLTable" + , null); + cursor.moveToFirst(); + + result.append(String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_all_logs))); + result.append(String.format("%s", cursor.getString(cursor.getColumnIndex("b")))); + result.append(String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_download))); + result.append(""); + cursor.close(); + + cursor = mainViewModel.databaseOpr.getDb().rawQuery("select count(*) as b from QSLTable\n" + + "WHERE SUBSTR(qso_date,1,8)=?", new String[]{UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime())}); + cursor.moveToFirst(); + + HtmlContext.tableRowBegin(result, true, true).append("\n"); + HtmlContext.tableCell(result, String.format("%s" + , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) + , GeneralVariables.getStringFromResource(R.string.html_today_log))); + HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); + HtmlContext.tableCell(result, String.format("%s" + , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) + , GeneralVariables.getStringFromResource(R.string.html_download_all))); + HtmlContext.tableCell(result, String.format("%s" + , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) + , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); + HtmlContext.tableCell(result, String.format("%s" + , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + + cursor.close(); + + int order = 1; + cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select SUBSTR(qso_date,1,6) as a,count(*) as b from QSLTable\n" + + "group by SUBSTR(qso_date,1,6)", null); + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 == 0); + + HtmlContext.tableCell(result, String.format("%s" + , cursor.getString(cursor.getColumnIndex("a")) + , cursor.getString(cursor.getColumnIndex("a")))); + + HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); + HtmlContext.tableCell(result, String.format("%s" + , cursor.getString(cursor.getColumnIndex("a")) + , GeneralVariables.getStringFromResource(R.string.html_download_all))); + HtmlContext.tableCell(result, String.format("%s" + , cursor.getString(cursor.getColumnIndex("a")) + , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); + HtmlContext.tableCell(result, String.format("%s" + , cursor.getString(cursor.getColumnIndex("a")) + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + order++; + } + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); + return result.toString(); + } + + private String downQSLByMonth(String month, boolean downall) { + Cursor cursor; + if (downall) { + cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable \n" + + "WHERE (SUBSTR(qso_date,1,?)=?)" + , new String[]{String.valueOf(month.length()), month}); + } else { + cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable \n" + + "WHERE (SUBSTR(qso_date,1,?)=?)and(isLotW_QSL=0 and isQSL=0)" + , new String[]{String.valueOf(month.length()), month}); + + } + return downQSLTable(cursor, false); + } + + /** + * 下载全部日志 + * + * @return String + */ + private String downAllQSl() { + Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable", null); + return downQSLTable(cursor, false); + } + + /** + * 生成QSL记录文本 + * + * @return 日志内容 + */ + @SuppressLint({"Range", "DefaultLocale"}) + private String downQSLTable(Cursor cursor, boolean isSWL) { + StringBuilder logStr = new StringBuilder(); + + logStr.append("FT8CN ADIF Export\n"); + while (cursor.moveToNext()) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("call")).length() + , cursor.getString(cursor.getColumnIndex("call")))); + if (!isSWL) { + if (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) { + logStr.append("Y "); + } else { + logStr.append("N "); + } + if (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) { + logStr.append("Y "); + } else { + logStr.append("N "); + } + } else { + logStr.append("Y "); + } + if (cursor.getString(cursor.getColumnIndex("gridsquare")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("gridsquare")).length() + , cursor.getString(cursor.getColumnIndex("gridsquare")))); + } + + if (cursor.getString(cursor.getColumnIndex("mode")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("mode")).length() + , cursor.getString(cursor.getColumnIndex("mode")))); + } + + if (cursor.getString(cursor.getColumnIndex("rst_sent")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("rst_sent")).length() + , cursor.getString(cursor.getColumnIndex("rst_sent")))); + } + + if (cursor.getString(cursor.getColumnIndex("rst_rcvd")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("rst_rcvd")).length() + , cursor.getString(cursor.getColumnIndex("rst_rcvd")))); + } + + if (cursor.getString(cursor.getColumnIndex("qso_date")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("qso_date")).length() + , cursor.getString(cursor.getColumnIndex("qso_date")))); + } + + if (cursor.getString(cursor.getColumnIndex("time_on")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("time_on")).length() + , cursor.getString(cursor.getColumnIndex("time_on")))); + } + + if (cursor.getString(cursor.getColumnIndex("qso_date_off")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("qso_date_off")).length() + , cursor.getString(cursor.getColumnIndex("qso_date_off")))); + } + + if (cursor.getString(cursor.getColumnIndex("time_off")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("time_off")).length() + , cursor.getString(cursor.getColumnIndex("time_off")))); + } + + if (cursor.getString(cursor.getColumnIndex("band")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("band")).length() + , cursor.getString(cursor.getColumnIndex("band")))); + } + + if (cursor.getString(cursor.getColumnIndex("freq")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("freq")).length() + , cursor.getString(cursor.getColumnIndex("freq")))); + } + + if (cursor.getString(cursor.getColumnIndex("station_callsign")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("station_callsign")).length() + , cursor.getString(cursor.getColumnIndex("station_callsign")))); + } + + if (cursor.getString(cursor.getColumnIndex("my_gridsquare")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("my_gridsquare")).length() + , cursor.getString(cursor.getColumnIndex("my_gridsquare")))); + } + + String comment = cursor.getString(cursor.getColumnIndex("comment")); + + //Distance: 99 km + //在写库的时候,一定要加" km" + logStr.append(String.format("%s \n" + , comment.length() + , comment)); + } + + cursor.close(); + return logStr.toString(); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java new file mode 100644 index 0000000..c4d5070 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java @@ -0,0 +1,1033 @@ +package com.bg7yoz.ft8cn.icom; + +/** + * ICom各数据包的解包和封包。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class IComPacketTypes { + private static final String TAG = "IComPacketTypes"; + + public static final int TX_BUFFER_SIZE = 0xf0; + /** + * 各类型包的长度 + */ + public static final int CONTROL_SIZE = 0x10; + public static final int WATCHDOG_SIZE = 0x14; + public static final int PING_SIZE = 0x15; + public static final int OPENCLOSE_SIZE = 0x16; + public static final int RETRANSMIT_RANGE_SIZE = 0x18;//重新传输,范围 + public static final int TOKEN_SIZE = 0x40; + public static final int STATUS_SIZE = 0x50; + public static final int LOGIN_RESPONSE_SIZE = 0x60; + public static final int LOGIN_SIZE = 0x80; + public static final int CONNINFO_SIZE = 0x90; + public static final int CAPABILITIES_SIZE = 0x42;//功能包 + public static final int RADIO_CAP_SIZE = 0x66; + public static final int CAP_CAPABILITIES_SIZE = 0xA8;//0x42+0x66 + public static final int AUDIO_HEAD_SIZE=0x18;//音频数据包的头是0x10+0x08,后面再跟音频数据 + + + public static final short CMD_NULL = 0x00;//空指令 + public static final short CMD_RETRANSMIT = 0x01;//请求重新发送,seq是重新发送的序号 + public static final short CMD_ARE_YOU_THERE = 0x03;//询问是否在?seq值必须为0 + public static final short CMD_I_AM_HERE = 0x04;//回答是否在? + public static final short CMD_DISCONNECT = 0x05;//断开 + public static final short CMD_ARE_YOU_READY = 0x06;//询问电台是否准备好了,seq=1 + public static final short CMD_I_AM_READY = 0x06;//电台回复已经准备好 + public static final short CMD_PING = 0x07;//ping ,seq有自己的序列 + + public static final byte TOKEN_TYPE_DELETE = 0x01;//令牌删除包 + public static final byte TOKEN_TYPE_CONFIRM = 0x02;//令牌确认包 + public static final byte TOKEN_TYPE_DISCONNECT = 0x04;//断开CI-V和音频流 + public static final byte TOKEN_TYPE_RENEWAL = 0x05;//令牌续订 + + + public static final long PING_PERIOD_MS = 500;//ping时钟的周期 + public static final long ARE_YOU_THERE_PERIOD_MS = 500;//查找电台的时钟周期 + public static final long IDLE_PERIOD_MS = 100;//空包时钟的周期 + public static final long TOKEN_RENEWAL_PERIOD_MS = 60000;//令牌旭东的时钟周期 + public static final long PURGE_MILLISECONDS = 10000;//数据缓冲区的时间最长是10秒钟 + public static final long OPEN_CLOSE_PERIOD_MS = 500;//civ指令定时发送open指令,确保端口打开 + public static final long WATCH_DOG_PERIOD_MS = 500;//监视数据接收状况的看门狗 + public static final long WATCH_DOG_ALERT_MS = 2000;//触发数据接收状况报警的阈值 + public static final long METER_TIMER_PERIOD_MS = 500;//检查meter的时钟周期 + + public static final int AUDIO_SAMPLE_RATE = 12000;//音频的采样率 + + public static final short CODEC_ALL_SUPPORTED = 0x018b; + public static final short CODEC_ONLY_24K = 0x0100; + public static final short CODEC_ONLY_12K = 0x0080; + public static final short CODEC_ONLY_441K = 0x0040;//44.1k + public static final short CODEC_ONLY_2205K = 0x0020;//22.05k + public static final short CODEC_ONLY_11025K = 0x0010;//11.025k + public static final short CODEC_ONLY_48K = 0x0008; + public static final short CODEC_ONLY_32K = 0x0004; + public static final short CODEC_ONLY_16K = 0x0002; + public static final short CODEC_ONLY_8K = 0x0001; + + public static class IcomCodecType { + public static final byte ULAW_1CH_8BIT = 0x01; + public static final byte LPCM_1CH_8BIT = 0x02; + public static final byte LPCM_1CH_16BIT = 0x04;//FT8CN推荐值 + public static final byte PCM_2CH_8BIT = 0x08; + public static final byte LPCM_2CH_16BIT = 0x10; + public static final byte ULAW_2CH_8BIT = 0x20; + public static final byte OPUS_CH1 = 0x40; + public static final byte OPUS_CH2 = (byte) 0x80; + } + + + /** + * 控制指令数据包。用于简单通信和重传请求的不带内容的数据包,0x10 + */ + public static class ControlPacket { + /** + * 把控制数据包转换成数据流 + * + * @return 数据流 + */ + public static byte[] toBytes(short type, short seq, int sentId, int rcvdId) { + byte[] packet = new byte[CONTROL_SIZE]; + System.arraycopy(intToBigEndian(CONTROL_SIZE), 0, packet, 0, 4); + System.arraycopy(shortToBigEndian(type), 0, packet, 4, 2); + System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); + System.arraycopy(intToBigEndian(sentId), 0, packet, 8, 4); + System.arraycopy(intToBigEndian(rcvdId), 0, packet, 12, 4); + return packet; + } + + public static byte[] idlePacketData(short seq, int sendid, int rcvdId) { + return toBytes(CMD_NULL, seq, sendid, rcvdId); + } + + public static boolean isControlPacket(byte[] data) {//0x10 + return data.length == CONTROL_SIZE && readIntBigEndianData(data, 0x00) == CONTROL_SIZE; + } + + public static short getType(byte[] data) { + if (data.length < CONTROL_SIZE) return 0; + return readShortBigEndianData(data, 0x04); + } + + public static short getSeq(byte[] data) { + if (data.length < CONTROL_SIZE) return 0; + return readShortBigEndianData(data, 0x06); + } + + public static int getSentId(byte[] data) { + if (data.length < CONTROL_SIZE) return 0; + return readIntBigEndianData(data, 0x08); + } + + public static int getRcvdId(byte[] data) { + if (data.length < CONTROL_SIZE) return 0; + return readIntBigEndianData(data, 0x0c); + } + public static void setRcvdId(byte[] data,int rcvdId){ + System.arraycopy(intToBigEndian(rcvdId),0x00,data,0x0c,4); + } + } + + public static class AudioPacket{ + /** + * quint32 len; // 0x00 + * quint16 type; // 0x04 + * quint16 seq; // 0x06 + * quint32 sentid; // 0x08 + * quint32 rcvdid; // 0x0c + * + * + * //接收的时候,ident=0x8116 ,8106,8006 + * quint16 ident; // 0x10 发射的时候: 当datalen=0xa0时,ident=0x9781,否则ident=0x0080; + * quint16 sendseq; // 0x12 + * quint16 unused; // 0x14 + * quint16 datalen; // 0x16 + */ + public static boolean isAudioPacket(byte[] data){ + if (data.length 0) { + return count; + } else { + return count; + } + } + + /** + * 获取radioCap数据包,也许radioCap的数量不止一个,所以要用index来指定哪一个数据包 + * + * @param data 数据包 + * @param index 数据包的索引,以0为起点 + * @return radioCap数据包 + */ + public static byte[] getRadioCapPacket(byte[] data, int index) { + if (data.length < (CAPABILITIES_SIZE + RADIO_CAP_SIZE * (index + 1))) + return null;//如果小于最小长度,就返回空 + byte[] packet = new byte[0x66]; + System.arraycopy(data, CAPABILITIES_SIZE + RADIO_CAP_SIZE * index, packet, 0, RADIO_CAP_SIZE); + return packet; + } + } + + /** + * 电台参数数据包(0x66长度),它是在Capabilities(0x42长度)包的后面,如果是一个,总数据包的长度是0xA8, + */ + public static class RadioCapPacket { + /* + union { + struct { + quint8 unusede[7]; // 0x00 + quint16 commoncap; // 0x07 + quint8 unused; // 0x09 + quint8 macaddress[6]; // 0x0a + }; + quint8 guid[GUIDLEN]; // 0x0 + }; + char name[32]; // 0x10 + char audio[32]; // 0x30 + quint16 conntype; // 0x50 + char civ; // 0x52 + quint16 rxsample; // 0x53 + quint16 txsample; // 0x55 + quint8 enablea; // 0x57 + quint8 enableb; // 0x58 + quint8 enablec; // 0x59 + quint32 baudrate; // 0x5a + quint16 capf; // 0x5e + char unusedi; // 0x60 + quint16 capg; // 0x61 + char unusedj[3]; // 0x63 + */ + + /** + * 获取电台名称 + * + * @param data 0x66数据包 + * @return 名称 + */ + public static String getRigName(byte[] data) { + byte[] rigName = new byte[32]; + System.arraycopy(data, 0x10, rigName, 0, 32); + return new String(rigName).trim(); + } + + public static String getAudioName(byte[] data) { + byte[] audioName = new byte[32]; + System.arraycopy(data, 0x30, audioName, 0, 32); + return new String(audioName).trim(); + } + + public static byte getCivAddress(byte[] data) { + return data[0x52]; + } + + public static short getRxSupportSample(byte[] data) { + return readShortData(data, 0x53); + } + + public static short getTxSupportSample(byte[] data) { + return readShortData(data, 0x55); + } + + public static boolean getSupportTX(byte[] data) { + return data[0x57] == 0x01; + } + + } + + /** + * TOKEN(0x40)数据包。 + * 发送时,requestType=0x02是令牌确认,发送0x01是删除令牌。 + * 接收时,requestType=0x05,且requestReply=0x02&&type=0x01说明是电台令牌续订操作,这时要判断response的值。 + * response=0x00 00 00 00说明时续订成功,response=0xff ff ff ff说明时拒绝续订,此时应记录下RemoteId、 + * Token,tokRequest的值,关闭各端口,重新开始登录操作 + */ + public static class TokenPacket { + /* + public int len = TOKEN_SIZE; // 0x00 (int32) + public short type; // 0x04(int16) + public short seq; // 0x06(int16) + public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 + public int rcvdId; // 0x0c(int32) + public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 + public short payloadSize = TOKEN_SIZE - 0x10;// 0x12(int16) 负载长度,是包长度-包头16字节 + public byte requestReply; // 0x14(int8) + public byte requestType; // 0x15(int8) + public short innerSeq; // 0x16(int16) + public byte[] unusedB = new byte[2];// 0x18(char[2] + public short tokRequest; // 0x1a(int16) + public int token; // 0x1c(int32) + public short tokRequest; // 0x20 + public byte[] unusedG = new byte[5]; // 0x22 + public short commonCap;//0x27 + public byte unusedH;//0x29 + public byte[] macAddress=new byte[6];//0x2a + public byte[] guid=new byte[16];//0x20 + public int response;//0x30 + public byte[] unusedE=new byte[12];//0x34 + */ + + /** + * 生成Token数据包 + * + * @param seq 序列号 + * @param localSID 主机的ID + * @param remoteSID 电台的ID + * @param requestType 发送0x02是令牌确认,发送0x01是删除令牌。如果接收0x05,且requestReply=0x02&&type=0x01 + * 说明是电台令牌续订成功 + * @param authInnerSendSeq 内部序列号 + * @param tokRequest 主机的TOKEN + * @param token 电台的TOKEN + * @return 数组 + */ + public static byte[] getTokenPacketData( + short seq, int localSID, int remoteSID, byte requestType + , short authInnerSendSeq, short tokRequest, int token) { + byte[] packet = new byte[TOKEN_SIZE]; + System.arraycopy(intToBigEndian(TOKEN_SIZE), 0, packet, 0, 4); //len int32 0x00 + //System.arraycopy(shortToBigEndian((short) 0), 0, packet, 4, 2);//type int16 0x04 + System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); //seq int16 0x06 + System.arraycopy(intToBigEndian(localSID), 0, packet, 8, 4); //localId int32 0x08 + System.arraycopy(intToBigEndian(remoteSID), 0, packet, 12, 4); //remoteId int32 0x0c + //System.arraycopy({0x00,0x00}, 0, packet, 16, 2); //unusedA byte[2] 0x10 + System.arraycopy(shortToByte((short) (TOKEN_SIZE - 0x10)) + , 0, packet, 18, 2); //payloadSize int16 0x12 + packet[20] = 0x01; //requestReply byte 0x14 + packet[21] = requestType; //requestType byte 0x15 + System.arraycopy(shortToByte(authInnerSendSeq), 0, packet, 22, 2);//innerSeq int16 0x16 + //System.arraycopy(unusedB, 0, packet, 24, 2); //unusedB byte[2] 0x18 + System.arraycopy(shortToByte(tokRequest), 0, packet, 26, 2);//tokRequest int16 0x1a + System.arraycopy(intToByte(token), 0, packet, 28, 4); //token int32 0x1c + //后面其余的部分为用0填充 + return packet; + } + + public static byte[] getRenewalPacketData( + short seq, int localSID, int remoteSID + , short authInnerSendSeq, short tokRequest, int token + ) { + return getTokenPacketData(seq, localSID, remoteSID, TOKEN_TYPE_RENEWAL, authInnerSendSeq, tokRequest, token); + } + + /** + * 检查是不是令牌续订成功,条件:requestType=0x05,且requestReply=0x02&&type=0x01&&response=0x00 + * + * @param data 数据包 + * @return 是否续订成功 + */ + public static boolean TokenRenewalOK(byte[] data) { + byte requestType = data[0x15]; + byte requestReply = data[0x14]; + short type = readShortBigEndianData(data, 0x04); + int response = readIntData(data, 0x30); + return requestType == 0x05 && requestReply == 0x02 && type == 0x01 && response == 0x00; + } + + public static byte getRequestType(byte[] data) { + return data[21];//0x15 + } + + public static byte getRequestReply(byte[] data) { + return data[20];//0x14 + } + + public static int getResponse(byte[] data) { + return readIntData(data, 0x30); + } + + public static short getTokRequest(byte[] data) { + return readShortData(data, 26);//0x1a + } + + public static int getToken(byte[] data) { + return readIntData(data, 28);//0x1c + } + } + + + /** + * 登录回复包,0x60包,长度96; + */ + public static class LoginResponsePacket { + /* + public int len;// 0x00 (int32) + public short type; // 0x04(int16) + public short seq; // 0x06(int16) + public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 + public int rcvdId; // 0x0c(int32) + public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 + public short payloadSize;// 0x12(int16) 负载长度,是包长度-包头16字节 + public byte requestReply; // 0x14(int8) + public byte requestType; // 0x15(int8) + public short innerSeq; // 0x16(int16) + public byte[] unusedB = new byte[2];// 0x18(char[2] + public short tokRequest; // 0x1a(int16) + public int token; // 0x1c(int32) + public short authStartId; // 0x20 + public byte[] unusedD = new byte[14]; // 0x22 + public int error; // 0x30 + public byte[] unusedE = new byte[12]; // 0x34 + public byte[] connection = new byte[16]; // 0x40 + public byte[] unusedF = new byte[16]; // 0x50 + */ + + public static boolean authIsOK(byte[] data) { + return readIntData(data, 0x30) == 0x00; + } + + public static int errorNum(byte[] data) { + return readIntData(data, 0x30); + } + + public static int getToken(byte[] data) { + return readIntData(data, 0x1c); + } + + public static String getConnection(byte[] data) { + byte[] connection = new byte[16]; + System.arraycopy(data, 0x40, connection, 0, 16); + return new String(connection).trim(); + } + + } + + + /** + * 登录用数据包,0x80包,长度128 + */ + public static class LoginPacket { + /* + public int len = LOGIN_SIZE; // 0x00 (int32) + public short type; // 0x04(int16) + public short seq; // 0x06(int16) + public int sentId; // 0x08(int32) MyID,与本地IP地址、端口有关 + public int rcvdId; // 0x0c(int32) + public byte[] unusedA = new byte[2]; // 0x10 char[2]//可能是用于指令的序号 + public short payloadSize = LOGIN_SIZE - 0x10;// 0x12(int16) 负载长度,是包长度-包头16字节 + public byte requestReply; // 0x14(int8) + public byte requestType; // 0x15(int8) + public short innerSeq; // 0x16(int16) + public byte[] unusedB = new byte[2];// 0x18(char[2] + public short tokRequest; // 0x1a(int16) + public int token; // 0x1c(int32) + public byte[] unusedC = new byte[32]; // 0x20(char[32]) + private byte[] userName = new byte[16]; // 0x40(char[16]) + private byte[] password = new byte[16]; // 0x50(char[16]) + private final byte[] name = new byte[16];// 0x60(char[16]) + public byte[] unusedF = new byte[16]; // 0x70(char[16]) + */ + + + /** + * 生成Login(0x80)数据包 + * + * @param seq 序号 + * @param localSID 主机ID + * @param remoteSID 电台ID + * @param authInnerSendSeq 内部序号 + * @param tokRequest 我的TOKEN + * @param userName 用户名(明文) + * @param password 密码(明文) + * @param name 终端的名称 + * @return 数据包 + */ + public static byte[] loginPacketData(short seq, int localSID, int remoteSID, short authInnerSendSeq + , short tokRequest, int token, String userName, String password, String name) { + byte[] packet = new byte[LOGIN_SIZE]; + System.arraycopy(intToBigEndian(LOGIN_SIZE), 0, packet, 0, 4); //len int32 0x00 + System.arraycopy(shortToBigEndian((short) 0), 0, packet, 4, 2);//type int16 0x04 + System.arraycopy(shortToBigEndian(seq), 0, packet, 6, 2); //seq int 16 0x06 + System.arraycopy(intToBigEndian(localSID), 0, packet, 8, 4); //localId int32 0x08 + System.arraycopy(intToBigEndian(remoteSID), 0, packet, 12, 4); //remoteId int32 0x0c + //System.arraycopy({0x00,0x00}, 0, packet, 16, 2); //unusedA byte[2] 0x10 + System.arraycopy(shortToByte((short) (LOGIN_SIZE - 0x10)) + , 0, packet, 18, 2);//payloadSize,int16,大端模式,是包长度-包头16字节 0x12 + packet[20] = 0x01;//requestReply byte 0x14 + packet[21] = 0x00;//requestType byte 0x15 + System.arraycopy(shortToByte(authInnerSendSeq), 0, packet, 22, 2);//innerSeq int16 x016 + //System.arraycopy(unusedB, 0, packet, 24, 2); //unusedB byte[2] 0x18 + System.arraycopy(shortToByte(tokRequest), 0, packet, 26, 2);//tokRequest int16 0x1a,我的token + System.arraycopy(intToByte(token), 0, packet, 28, 4);//token int32 0x1c,电台的token + //System.arraycopy(unusedC, 0, packet, 32, 32); //unusedC byte[32] 0x0x20 + System.arraycopy(passCode(userName), 0, packet, 64, 16);//userName byte[16] 0x40,用户名密文 + System.arraycopy(passCode(password), 0, packet, 80, 16);//password byte[16] 0x50,密码密文 + System.arraycopy(stringToByte(name, 16), 0, packet, 96, 16);//name byte[16] 0x60,主机的名称 + //System.arraycopy(unusedF, 0, packet, 112, 16); //unusedF byte[16] 0x70 + return packet; + } + + } + + + /** + * 用户名、密码加密算法,最长只支持到16位。 + * + * @param pass 用户名或密码 + * @return 密文,最长16位 + */ + public static byte[] passCode(String pass) { + byte[] sequence =//字典 + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x47, 0x5d, 0x4c, 0x42, 0x66, 0x20, 0x23, 0x46, 0x4e, 0x57, 0x45, 0x3d, 0x67, 0x76, 0x60, 0x41, 0x62, 0x39, 0x59, 0x2d, 0x68, 0x7e, + 0x7c, 0x65, 0x7d, 0x49, 0x29, 0x72, 0x73, 0x78, 0x21, 0x6e, 0x5a, 0x5e, 0x4a, 0x3e, 0x71, 0x2c, 0x2a, 0x54, 0x3c, 0x3a, 0x63, 0x4f, + 0x43, 0x75, 0x27, 0x79, 0x5b, 0x35, 0x70, 0x48, 0x6b, 0x56, 0x6f, 0x34, 0x32, 0x6c, 0x30, 0x61, 0x6d, 0x7b, 0x2f, 0x4b, 0x64, 0x38, + 0x2b, 0x2e, 0x50, 0x40, 0x3f, 0x55, 0x33, 0x37, 0x25, 0x77, 0x24, 0x26, 0x74, 0x6a, 0x28, 0x53, 0x4d, 0x69, 0x22, 0x5c, 0x44, 0x31, + 0x36, 0x58, 0x3b, 0x7a, 0x51, 0x5f, 0x52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + + }; + + byte[] passBytes = pass.getBytes(); + byte[] enCodePass = new byte[16];//密文不能超过16字节 + + for (int i = 0; i < passBytes.length && i < 16; i++) { + int p = (passBytes[i] + i) & 0xff;//防止出现负数 + if (p > 126) { + p = 32 + p % 127; + } + enCodePass[i] = sequence[p]; + } + return enCodePass; + } + + /** + * 把字符串转指定长度的byte数组 + * + * @param s 字符串 + * @param len 长度 + * @return 数组 + */ + public static byte[] stringToByte(String s, int len) { + byte[] buf = new byte[len]; + byte[] temp = s.getBytes(); + for (int i = 0; i < temp.length; i++) { + if (i > len) break; + buf[i] = temp[i]; + } + return buf; + } + + /** + * 把int转为小端模式,(高位在结尾) + * + * @param n int + * @return 数组 + */ + public static byte[] intToBigEndian(int n) { + byte[] b = new byte[4]; + b[0] = (byte) (n & 0xff); + b[1] = (byte) (n >> 8 & 0xff); + b[2] = (byte) (n >> 16 & 0xff); + b[3] = (byte) (n >> 24 & 0xff); + return b; + } + + /** + * int32转换成4字节数组 + * + * @param n int32 + * @return 数组 + */ + public static byte[] intToByte(int n) { + byte[] b = new byte[4]; + b[3] = (byte) (n & 0xff); + b[2] = (byte) (n >> 8 & 0xff); + b[1] = (byte) (n >> 16 & 0xff); + b[0] = (byte) (n >> 24 & 0xff); + return b; + } + + /** + * 从流数据中读取小端模式的Int32 + * + * @param data 流数据 + * @param start 起始点 + * @return Int32 + */ + public static int readIntBigEndianData(byte[] data, int start) { + if (data.length - start < 4) return 0; + return (int) data[start] & 0xff + | ((int) data[start + 1] & 0xff) << 8 + | ((int) data[start + 2] & 0xff) << 16 + | ((int) data[start + 3] & 0xff) << 24; + } + + public static int readIntData(byte[] data, int start) { + if (data.length - start < 4) return 0; + return (int) data[start + 3] & 0xff + | ((int) data[start + 2] & 0xff) << 8 + | ((int) data[start + 1] & 0xff) << 16 + | ((int) data[start] & 0xff) << 24; + } + + /** + * 从流数据中读取小端模式的Short + * + * @param data 流数据 + * @param start 起始点 + * @return Int16 + */ + public static short readShortBigEndianData(byte[] data, int start) { + if (data.length - start < 2) return 0; + return (short) ((short) data[start] & 0xff + | ((short) data[start + 1] & 0xff) << 8); + } + + /** + * 把字节转换成short,不做小端转换!! + * + * @param data 字节数据 + * @return short + */ + public static short readShortData(byte[] data, int start) { + if (data.length - start < 2) return 0; + return (short) ((short) data[start + 1] & 0xff + | ((short) data[start] & 0xff) << 8); + } + + /** + * 把short转为小端模式,(高位在结尾) + * + * @param n short + * @return 数组 + */ + public static byte[] shortToBigEndian(short n) { + byte[] b = new byte[2]; + b[0] = (byte) (n & 0xff); + b[1] = (byte) (n >> 8 & 0xff); + return b; + } + + /** + * 把short转成byte数组 + * + * @param n short + * @return 数组 + */ + public static byte[] shortToByte(short n) { + byte[] b = new byte[2]; + b[1] = (byte) (n & 0xff); + b[0] = (byte) (n >> 8 & 0xff); + return b; + } + + /** + * 显示16进制内容 + * + * @param data 数组 + * @return 内容 + */ + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%02x ", data[i] & 0xff)); + } + return s.toString(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java new file mode 100644 index 0000000..27e3fdc --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java @@ -0,0 +1,152 @@ +package com.bg7yoz.ft8cn.icom; +/** + * WIFI模式下电台操作。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTrack; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.icom.IcomUdpBase.IcomUdpStyle; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.io.IOException; + +public class IComWifiRig { + public interface OnIComDataEvents{ + void onReceivedCivData(byte[] data); + void onReceivedWaveData(byte[] data); + } + private IcomControlUdp controlUdp; + private AudioTrack audioTrack = null; + private final String ip; + private final int port; + private final String userName; + private final String password; + public boolean opened=false; + public boolean isPttOn=false; + + private OnIComDataEvents onIComDataEvents; + + + + public IComWifiRig(String ip, int port, String userName, String password) { + this.ip = ip; + this.port = port; + this.userName = userName; + this.password = password; + } + + + public void start(){ + opened=true; + openAudio();//打开音频 + controlUdp=new IcomControlUdp(userName,password,ip,port); + //设置事件,这里可以处理电台状态,和接收电台送来的音频数据 + controlUdp.setOnStreamEvents(new IcomUdpBase.OnStreamEvents() { + @Override + public void OnReceivedIAmHere(byte[] data) { + + } + + @Override + public void OnReceivedCivData(byte[] data) { + if (onIComDataEvents!=null){ + onIComDataEvents.onReceivedCivData(data); + } + } + + @Override + public void OnReceivedAudioData(byte[] audioData) { + if (onIComDataEvents!=null){ + onIComDataEvents.onReceivedWaveData(audioData); + } + if (audioTrack!=null){ + // if (!isPttOn) {//如果ptt没有按下 + audioTrack.write(audioData, 0, audioData.length + , AudioTrack.WRITE_NON_BLOCKING); + // } + } + } + + @Override + public void OnUdpSendIOException(IcomUdpStyle style,IOException e) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource( + R.string.network_exception),IcomUdpBase.getUdpStyle(style),e.getMessage())); + close(); + } + + @Override + public void OnLoginResponse(boolean authIsOK) { + if (authIsOK){ + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.login_succeed)); + }else { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.loging_failed)); + controlUdp.closeAll(); + } + } + + }); + controlUdp.openStream();//打开端口 + controlUdp.startAreYouThereTimer();//开始连接电台 + } + + public void setPttOn(boolean on){//打开PTT + isPttOn=on; + controlUdp.civUdp.sendPttAction(on); + controlUdp.audioUdp.isPttOn=on; + } + public void sendCivData(byte[] data){ + controlUdp.civUdp.sendCivData(data); + } + + public void sendWaveData(float[] data){//发送音频数据到电台 + controlUdp.sendWaveData(data); + } + + /** + * 关闭各种连接,以及音频 + */ + public void close(){ + opened=false; + controlUdp.closeAll(); + closeAudio(); + //controlUdp.closeStream(); + } + /** + * 打开音频,流方式。当收到音频流的时候,播放数据 + */ + public void openAudio() { + if (audioTrack!=null) closeAudio(); + + AudioAttributes attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(); + AudioFormat myFormat = new AudioFormat.Builder().setSampleRate(IComPacketTypes.AUDIO_SAMPLE_RATE) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); + int mySession = 0; + audioTrack = new AudioTrack(attributes, myFormat + , IComPacketTypes.AUDIO_SAMPLE_RATE/4, AudioTrack.MODE_STREAM + , mySession); + audioTrack.play(); + } + /** + * 关闭音频 + */ + public void closeAudio() { + if (audioTrack != null) { + audioTrack.stop(); + audioTrack = null; + } + } + + public void setOnIComDataEvents(OnIComDataEvents onIComDataEvents) { + this.onIComDataEvents = onIComDataEvents; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java new file mode 100644 index 0000000..d0f1242 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java @@ -0,0 +1,103 @@ +package com.bg7yoz.ft8cn.icom; +/** + * 处理ICom的音频流。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; + +import java.net.DatagramPacket; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + +public class IcomAudioUdp extends IcomUdpBase { + private static final String TAG = "IcomAudioUdp"; + + public IcomAudioUdp() { + udpStyle = IcomUdpStyle.AudioUdp; + } + private final ExecutorService doTXThreadPool =Executors.newCachedThreadPool(); + private final DoTXAudioRunnable doTXAudioRunnable=new DoTXAudioRunnable(this); + + @Override + public void onDataReceived(DatagramPacket packet, byte[] data) { + super.onDataReceived(packet, data); + + if (!IComPacketTypes.AudioPacket.isAudioPacket(data)) return; + byte[] audioData = IComPacketTypes.AudioPacket.getAudioData(data); + if (onStreamEvents != null) { + onStreamEvents.OnReceivedAudioData(audioData); + } + } + + + public void sendTxAudioData(float[] audioData) { + if (audioData==null) return; + + short[] temp=new short[audioData.length]; + //要做一下浮点到16位int的转换 + for (int i = 0; i < audioData.length; i++) { + float x = audioData[i]; + if (x > 1.0) + x = 1.0f; + else if (x < -1.0) + x = -1.0f; + temp[i] = (short) (x * 32767.0); + } + doTXAudioRunnable.audioData=temp; + doTXThreadPool.execute(doTXAudioRunnable); + } + private static class DoTXAudioRunnable implements Runnable{ + IcomAudioUdp icomAudioUdp; + short[] audioData; + + public DoTXAudioRunnable(IcomAudioUdp icomAudioUdp) { + this.icomAudioUdp = icomAudioUdp; + } + + @Override + public void run() { + if (audioData==null) return; + + final int partialLen = IComPacketTypes.TX_BUFFER_SIZE * 2;//数据包的长度 + //要转换一下到BYTE,小端模式 + + //byte[] data = new byte[audioData.length * 2 + partialLen * 4];//多出一点空声音放在前后各20ms*2共80ms + //先播放,是给出空的声音,for i 循环,做了一个判断,是给前面的空声音,for j循环,做得判断,是让后面发送空声音 + byte[] audioPacket = new byte[partialLen]; + for (int i = 0; i < (audioData.length / IComPacketTypes.TX_BUFFER_SIZE) + 8; i++) {//多出6个周期,前面3个,后面3个多 + if (!icomAudioUdp.isPttOn) break; + long now = System.currentTimeMillis() - 1;//获取当前时间 + + icomAudioUdp.sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket + , (short) 0, icomAudioUdp.localId, icomAudioUdp.remoteId, icomAudioUdp.innerSeq)); + icomAudioUdp.innerSeq++; + + Arrays.fill(audioPacket,(byte)0x00); + if (i>=3) {//让前两个空数据发送出去 + for (int j = 0; j < IComPacketTypes.TX_BUFFER_SIZE; j++) { + if ((i-3) * IComPacketTypes.TX_BUFFER_SIZE + j < audioData.length) { + System.arraycopy(IComPacketTypes.shortToBigEndian((short) + (audioData[(i-3) * IComPacketTypes.TX_BUFFER_SIZE + j] + * GeneralVariables.volumePercent))//乘以信号量的比率 + , 0, audioPacket, j * 2, 2); + } + } + } + while (icomAudioUdp.isPttOn) { + if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期 + break; + } + } + } + Log.e(TAG, "run: 音频发送完毕!!" ); + Thread.currentThread().interrupt(); + } + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java new file mode 100644 index 0000000..75fd2df --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java @@ -0,0 +1,110 @@ +package com.bg7yoz.ft8cn.icom; +/** + * 处理ICom的CIV流。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import com.bg7yoz.ft8cn.rigs.IcomRigConstant; + +import java.net.DatagramPacket; +import java.util.Timer; +import java.util.TimerTask; + +public class IcomCivUdp extends IcomUdpBase{ + private static final String TAG="IcomCivUdp"; + public byte civAddress=(byte)0xA4; + public boolean supportTX=true; + public short civSeq=0; + + + private Timer openCivDataTimer; + + + public IcomCivUdp() { + udpStyle=IcomUdpStyle.CivUdp; + } + + @Override + public void onDataReceived(DatagramPacket packet,byte[] data) { + super.onDataReceived(packet,data); + + if (data.length == IComPacketTypes.CONTROL_SIZE) { + if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) { + Log.d(TAG, "onDataReceived: civ I am ready!!"); + sendOpenClose(true);//打开连接 + startIdleTimer();//打开发送空包时钟 + startCivDataTimer();//启动civ看门狗 + + } + } else { + if (IComPacketTypes.ControlPacket.getType(data) != IComPacketTypes.CMD_PING) { + Log.d(TAG, "onDataReceived: CIV :" + IComPacketTypes.byteToStr(data)); + checkCivData(data); + } + if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_RETRANSMIT) { + Log.d(TAG, "onDataReceived: type=0x01"+IComPacketTypes.byteToStr(data) ); + //lastReceived=System.currentTimeMillis();//更新一下最后接收数据的时间,让watchDog处理 + } + + } + } + + public void checkCivData(byte[] data){ + if (IComPacketTypes.CivPacket.checkIsCiv(data)){ + lastReceivedTime=System.currentTimeMillis(); + if (getOnStreamEvents()!=null){ + getOnStreamEvents().OnReceivedCivData(IComPacketTypes.CivPacket.getCivData(data)); + } + } + } + + public void sendOpenClose(boolean open){ + if (open) { + sendTrackedPacket(IComPacketTypes.OpenClosePacket.toBytes((short) 0 + , localId, remoteId, civSeq,(byte) 0x04));//打开连接 + }else { + sendTrackedPacket(IComPacketTypes.OpenClosePacket.toBytes((short) 0 + , localId, remoteId, civSeq,(byte) 0x00));//关闭连接 + } + civSeq++; + } + public void startCivDataTimer(){ + stopTimer(openCivDataTimer); + Log.d(TAG, String.format("openCivDataTimer: local port:%d,remote port %d", localPort, rigPort)); + openCivDataTimer = new Timer(); + openCivDataTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (System.currentTimeMillis()-lastReceivedTime>2000) { + sendOpenClose(true); + } + } + }, 100, IComPacketTypes.OPEN_CLOSE_PERIOD_MS); + } + + + public void sendPttAction(boolean pttOn){ + if (pttOn) { + sendCivData(IcomRigConstant.setPTTState(0xe0, civAddress, IcomRigConstant.PTT_ON)); + }else { + sendCivData(IcomRigConstant.setPTTState(0xe0, civAddress, IcomRigConstant.PTT_OFF)); + } + } + + public void sendCivData(byte[] data){ + sendTrackedPacket(IComPacketTypes.CivPacket.setCivData((short) 0,localId,remoteId,civSeq,data)); + civSeq++; + } + + @Override + public void close() { + super.close(); + sendOpenClose(false); + stopTimer(openCivDataTimer); + stopTimer(idleTimer); + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java new file mode 100644 index 0000000..6579afe --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java @@ -0,0 +1,244 @@ +package com.bg7yoz.ft8cn.icom; +/** + * ICom的控制流。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import java.net.DatagramPacket; +import java.util.Timer; +import java.util.TimerTask; + +public class IcomControlUdp extends IcomUdpBase { + private static final String TAG = "IcomControlUdp"; + public final String APP_NAME = "FT8CN"; + + //与采样率有关,每20ms发送的样本数12000/50=240=F0,实际字节数是(16bit),还要乘以2,也就是480字节 + + + public Timer tokenTimer;//续订令牌的时钟 + + public String userName; + public String password; + public String rigName = ""; + public String audioName = ""; + public byte[] rigMacAddress=new byte[6];//0xA8、0x90包中提供 + public String connectionMode = ""; + + public boolean gotAuthOK = false;//token认证通过了 + public boolean isAuthenticated = false;//登录成功 + public boolean rigIsBusy = false; + + public IcomCivUdp civUdp; + public IcomAudioUdp audioUdp; + + + public IcomControlUdp(String userName, String password, String remoteIp, int remotePort) { + udpStyle=IcomUdpStyle.ControlUdp; + this.userName = userName; + this.password = password; + + this.rigIp = remoteIp; + this.rigPort = remotePort; + + civUdp = new IcomCivUdp(); + audioUdp = new IcomAudioUdp(); + civUdp.rigIp = remoteIp; + audioUdp.rigIp = remoteIp; + civUdp.openStream(); + audioUdp.openStream(); + } + + @Override + public void onDataReceived(DatagramPacket packet,byte[] data) { + super.onDataReceived(packet,data); + switch (data.length) { + case IComPacketTypes.CONTROL_SIZE://在父类中已经实现0x04,0x01指令 + if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_HERE) { + rigIp=packet.getAddress().getHostAddress(); + //civUdp.rigIp=packet.getAddress().getHostAddress(); + //audioUdp.rigIp=packet.getAddress().getHostAddress(); + } //如果电台回复I'm ready,就发起login + if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) { + sendLoginPacket();//电台准备好了,申请登录 + startIdleTimer();//打开发送空包时钟 + } + break; + case IComPacketTypes.TOKEN_SIZE://处理令牌的续订之类的事情 + onReceiveTokenPacket(data); + break; + case IComPacketTypes.STATUS_SIZE://0x50电台回复我它的参数:CivPort,AudioPort等 + onReceiveStatusPacket(data); + break; + case IComPacketTypes.LOGIN_RESPONSE_SIZE://0x60电台回复登录的请求 + onReceiveLoginResponse(data); + break; + case IComPacketTypes.CONNINFO_SIZE://电台会回复2次0x90包,区别在于busy字段 + onReceiveConnInfoPacket(data); + break; + case IComPacketTypes.CAP_CAPABILITIES_SIZE://0xA8数据包,返回civ地址 + byte[] audioCap = IComPacketTypes.CapCapabilitiesPacket.getRadioCapPacket(data, 0); + if (audioCap!=null) { + civUdp.supportTX = IComPacketTypes.RadioCapPacket.getSupportTX(audioCap); + civUdp.civAddress = IComPacketTypes.RadioCapPacket.getCivAddress(audioCap); + audioName = IComPacketTypes.RadioCapPacket.getAudioName(audioCap); + } + break; + } + } + + /** + * 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=0,第二次busy=1。 + * 在0x90数据包中取macAddress,电台名称 + * + * @param data 0x90数据包 + */ + public void onReceiveConnInfoPacket(byte[] data) { + rigMacAddress = IComPacketTypes.ConnInfoPacket.getMacAddress(data); + rigIsBusy = IComPacketTypes.ConnInfoPacket.getBusy(data); + rigName = IComPacketTypes.ConnInfoPacket.getRigName(data); + //if (!rigIsBusy) {//说明是第一次收到0x90数据包,要回复一个x090数据包 + Log.e(TAG, "onReceiveConnInfoPacket: send 0x90"); + sendTrackedPacket( + IComPacketTypes.ConnInfoPacket.connInfoPacketData(data, (short) 0 + , localId, remoteId + , (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken + , rigName, userName + , IComPacketTypes.AUDIO_SAMPLE_RATE//48000采样率 + , civUdp.localPort, audioUdp.localPort + , IComPacketTypes.TX_BUFFER_SIZE)); + innerSeq++; + //} + } + + /** + * 处理电台回复登录数据包 + * + * @param data 0x60数据包 + */ + public void onReceiveLoginResponse(byte[] data) { + if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; + connectionMode = IComPacketTypes.LoginResponsePacket.getConnection(data); + Log.d(TAG, "connection mode:" + connectionMode); + if (IComPacketTypes.LoginResponsePacket.authIsOK(data)) {//errorCode=0x00,认证成功 + Log.d(TAG, "onReceiveLoginResponse: Login succeed!"); + if (!isAuthenticated) { + rigToken = IComPacketTypes.LoginResponsePacket.getToken(data); + Log.d(TAG, "onReceiveLoginResponse: send token confirm 0x02"); + sendTokenPacket(IComPacketTypes.TOKEN_TYPE_CONFIRM);//发送令牌确认包 + startTokenTimer();//启动令牌续订时钟 + isAuthenticated = true; + } + } + if (onStreamEvents!=null){//触发认证事件 + onStreamEvents.OnLoginResponse(IComPacketTypes.LoginResponsePacket.authIsOK(data)); + } + } + + /** + * 处理电台回复我的参数。0x50数据包 + * + * @param data 0x50数据包 + */ + public void onReceiveStatusPacket(byte[] data) { + if (IComPacketTypes.ControlPacket.getType(data) == 0x01) return; + if (IComPacketTypes.StatusPacket.getAuthOK(data) + && IComPacketTypes.StatusPacket.getIsConnected(data)) {//令牌认证成功,且处于连接状态 + audioUdp.rigPort = IComPacketTypes.StatusPacket.getRigAudioPort(data); + audioUdp.rigIp = rigIp; + civUdp.rigPort = IComPacketTypes.StatusPacket.getRigCivPort(data); + civUdp.rigIp = rigIp; + Log.e(TAG, String.format("onReceiveStatusPacket: Status packet 0x50: civRigPort:%d,audioRigPort:%d" + ,civUdp.rigPort,audioUdp.rigPort )); + civUdp.startAreYouThereTimer();//civ端口启动连接电台 + audioUdp.startAreYouThereTimer();//audio端口启动连接电台 + }//else处理关闭连接??? + } + + /** + * 处理令牌数据包 + * + * @param data 0x40数据包 + */ + public void onReceiveTokenPacket(byte[] data) { + //看是不是续订令牌包 + if (IComPacketTypes.TokenPacket.getRequestType(data) == IComPacketTypes.TOKEN_TYPE_RENEWAL + && IComPacketTypes.TokenPacket.getRequestReply(data) == 0x02 + && IComPacketTypes.ControlPacket.getType(data) != IComPacketTypes.CMD_RETRANSMIT) { + int response = IComPacketTypes.TokenPacket.getResponse(data); + if (response == 0x0000) {//说明续订成功了 + gotAuthOK = true; + } else if (response == 0xffffffff) { + remoteId = IComPacketTypes.ControlPacket.getSentId(data); + localToken = IComPacketTypes.TokenPacket.getTokRequest(data); + rigToken = IComPacketTypes.TokenPacket.getToken(data); + sendConnectionRequest();//申请连接 + } else { + Log.e(TAG, "Token renewal failed,unknow response"); + } + } + } + + /** + * 发送音频数据到电台 + * @param data 数据 + */ + public void sendWaveData(float[] data){ + audioUdp.sendTxAudioData(data); + } + + /** + * 发送0x90数据包,向电台请求连接 + */ + public void sendConnectionRequest() { + sendTrackedPacket(IComPacketTypes.ConnInfoPacket.connectRequestPacket((short) 0 + , localId, remoteId, (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken + , rigMacAddress, rigName, userName, IComPacketTypes.AUDIO_SAMPLE_RATE + , civUdp.getLocalPort(), audioUdp.getLocalPort() + , IComPacketTypes.TX_BUFFER_SIZE)); + innerSeq++; + } + + /** + * 发送登录数据包 + */ + public void sendLoginPacket() { + sendTrackedPacket(IComPacketTypes.LoginPacket.loginPacketData((short) 0 + , localId, remoteId, innerSeq, localToken, rigToken, userName, password, APP_NAME)); + innerSeq++; + } + + @Override + public void setOnStreamEvents(OnStreamEvents onStreamEvents) { + super.setOnStreamEvents(onStreamEvents); + audioUdp.onStreamEvents=onStreamEvents; + civUdp.onStreamEvents=onStreamEvents; + } + + /** + * 启动令牌续订时钟 + */ + public void startTokenTimer() { + stopTimer(tokenTimer); + Log.d(TAG, String.format("start Toke Timer: local port:%d,remote port %d", localPort, rigPort)); + tokenTimer = new Timer(); + tokenTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + sendTokenPacket(IComPacketTypes.TOKEN_TYPE_RENEWAL); + } + }, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS, IComPacketTypes.TOKEN_RENEWAL_PERIOD_MS); + } + public void closeAll(){ + sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short)0 + ,localId,remoteId,IComPacketTypes.TOKEN_TYPE_DELETE,innerSeq,localToken,rigToken)); + innerSeq++; + this.close(); + civUdp.close(); + audioUdp.close(); + + civUdp.sendOpenClose(false); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java new file mode 100644 index 0000000..54ba904 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java @@ -0,0 +1,78 @@ +package com.bg7yoz.ft8cn.icom; +/** + * ICom指令数据的缓存。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import java.util.ArrayList; + +public class IcomSeqBuffer { + private long last=System.currentTimeMillis(); + + public static class SeqBufEntry { + public short seq;//序号 + public byte[] data;//数据 + public long addedAt;//添加的时间,最多保存10秒钟 + + public SeqBufEntry(short seq, byte[] data) { + this.seq = seq; + this.data = data; + addedAt = System.currentTimeMillis(); + } + } + + public ArrayList entries = new ArrayList<>(); + + /** + * 添加指令号缓存 + * + * @param seq 序号 + * @param data 指令 + */ + public synchronized void add(short seq, byte[] data) { + entries.add(new SeqBufEntry(seq, data)); + last=System.currentTimeMillis(); + purgeOldEntries();//要删除多余的历史记录 + } + + /** + * 弹出旧的指令,保证指令在缓存的限定范围内 + */ + public void purgeOldEntries() { + if (entries.size() == 0) return; + long now=System.currentTimeMillis(); + for (int i = entries.size()-1; i >=0 ; i--) {//删除超过10秒的历史记录 + if (now-entries.get(i).addedAt>IComPacketTypes.PURGE_MILLISECONDS){ + entries.remove(i); + } + } + + //while (entries.size() > MaxBufferCount) { + // entries.remove(0); + //} + } + + /** + * 按序号查找缓存中是否有历史指令 + * + * @param seqNum 序号 + * @return 指令数据,如果没有为NULL。 + */ + public synchronized byte[] get(int seqNum) { + int founded = -1; + for (int i = entries.size() - 1; i >= 0; i--) { + if (entries.get(i).seq == seqNum) { + founded = i; + } + } + if (founded != -1) { + return entries.get(founded).data; + } else { + return null; + } + } + public long getTimeOut(){ + return System.currentTimeMillis()-last; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java new file mode 100644 index 0000000..caf4560 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java @@ -0,0 +1,430 @@ +package com.bg7yoz.ft8cn.icom; +/** + * 简单封装的udp流处理。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Timer; +import java.util.TimerTask; + +public class IcomUdpBase { + public enum IcomUdpStyle{//数据流的类型 + UdpBase, + ControlUdp, + CivUdp, + AudioUdp + } + public static String getUdpStyle(IcomUdpStyle style){ + switch (style){ + case ControlUdp: + return GeneralVariables.getStringFromResource(R.string.control_stream); + case CivUdp: + return GeneralVariables.getStringFromResource(R.string.civ_stream); + case AudioUdp: + return GeneralVariables.getStringFromResource(R.string.audio_stream); + default: + return GeneralVariables.getStringFromResource(R.string.data_stream); + } + } + /** + * 事件接口 + */ + public interface OnStreamEvents { + void OnReceivedIAmHere(byte[] data); + void OnReceivedCivData(byte[] data); + void OnReceivedAudioData(byte[] audioData); + void OnUdpSendIOException(IcomUdpStyle style,IOException e); + void OnLoginResponse(boolean authIsOK); + //void OnWatchDogAlert(IcomUdpStyle style,boolean isAlerted); + } + public IcomUdpStyle udpStyle=IcomUdpStyle.UdpBase; + + private static final String TAG = "IcomUdpBase"; + public int rigPort; + public String rigIp; + public int localPort; + public int localId = (int) System.currentTimeMillis();//随机码,以时间为随机变量 + public int remoteId; + public boolean authDone = false;//登录 + public boolean rigReadyDone = false;//电台已经ready,control可以执行登录,ci-v可以执行open. + public short trackedSeq = 1;//因为are you there=0,are you ready=1。从are you ready之后才发track包 + public short pingSeq = 0;//ping的起始值是0 + public short innerSeq = 0x30; + public int rigToken;//电台提供的令牌 + public short localToken = (short) System.currentTimeMillis();//本地生成的令牌,可以是随机数 + public boolean isPttOn=false; + + + + public IcomSeqBuffer txSeqBuffer = new IcomSeqBuffer();//发送命令的历史列表 + //public IcomSeqBuffer rxSeqBuffer = new IcomSeqBuffer();//接收命令的历史列表 + public long lastReceivedTime=System.currentTimeMillis();//最后收到数据的时间 + public long lastSentTime=System.currentTimeMillis();//最后收到数据的时间 + + + public IcomUdpClient udpClient;//用于与电台通讯的udp + + + public OnStreamEvents onStreamEvents;//一些事件处理 + //Timer,执行代码部分:TimerTask,具体执行:timer.schedule(task,delay,period) + public Timer areYouThereTimer; + public Timer pingTimer; + public Timer idleTimer;//发送空数据包的时钟 + + + public void close(){ + onStreamEvents=null;//不用弹出网络异常的消息了 + sendUntrackedPacket(IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_DISCONNECT + ,(short)0,localId,remoteId)); + stopTimer(areYouThereTimer); + stopTimer(pingTimer); + stopTimer(idleTimer); + } + + /** + * 关闭udpClient + */ + public void closeStream(){ + if (udpClient!=null){ + try { + udpClient.setActivated(false); + } catch (SocketException e) { + e.printStackTrace(); + Log.e(TAG, "closeStream: "+e.getMessage() ); + } + } + } + /** + * 打开Udp流端口,如果Udp端口已经打开了,会再打开一次,本地端口应该会变化 + */ + public void openStream() {//打开 + if (udpClient == null) { + udpClient = new IcomUdpClient(-1); + } + udpClient.setOnUdpEvents(new IcomUdpClient.OnUdpEvents() { + @Override + public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { + //屏蔽掉非法的数据包 + if (data.length data.length - 1) break;//做一个保护,如果字节数不是偶数,防止数组下标溢出 + //重新传输指令 + retransmitPacket(IComPacketTypes.readShortBigEndianData(data, i)); + } + } + + + + /** + * 启动Are you there 时钟 + */ + public void startAreYouThereTimer() { + stopTimer(areYouThereTimer); + areYouThereTimer = new Timer(); + areYouThereTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + Log.d(TAG, String.format("AreYouThereTimer: local port:%d,remote port %d", localPort, rigPort)); + sendUntrackedPacket( + IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_ARE_YOU_THERE + , (short) 0, localId, 0)); + } + }, 0, IComPacketTypes.ARE_YOU_THERE_PERIOD_MS); + } + + /** + * 启动ping时钟 + */ + public void startPingTimer() { + stopTimer(pingTimer);//如果之前有打开的时钟,就关闭 + Log.d(TAG, String.format("start PingTimer: local port:%d,remote port %d", localPort, rigPort)); + pingTimer = new Timer(); + pingTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + sendPingPacket();//发送Ping包 + } + }, 0, IComPacketTypes.PING_PERIOD_MS);//500ms周期 + } + /** + * 发送空包时钟 + */ + public void startIdleTimer() { + stopTimer(idleTimer); + Log.d(TAG, String.format("start Idle Timer: local port:%d,remote port %d", localPort, rigPort)); + idleTimer = new Timer(); + idleTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (txSeqBuffer.getTimeOut()>200) {//当超过200毫秒没有发送指令,就发送一个空指令 + sendTrackedPacket( + IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_NULL + , (short) 0, localId, remoteId)); + } + } + }, IComPacketTypes.IDLE_PERIOD_MS, IComPacketTypes.IDLE_PERIOD_MS); + } + + /** + * 停止时钟 + * + * @param timer 时钟 + */ + public void stopTimer(Timer timer) { + if (timer != null) { + timer.cancel(); + timer.purge(); + timer = null; + } + } + + + public void onReceivedPingPacket(byte[] data) { + //两种情况,一种是电台ping我,另一个是电台回复我的Ping包 + if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_PING) { + if (IComPacketTypes.PingPacket.getReply(data) == 0x00) {//电台ping我 + sendReplyPingPacket(data);//回复电台Ping + } else {//回复我的ping,序号++ + if (IComPacketTypes.ControlPacket.getSeq(data) == pingSeq) { + pingSeq++; + } + } + } + } + + /** + * 发送令牌包0x40 + * @param requestType 令牌类型,0x02确认,0x05续订 + */ + public void sendTokenPacket(byte requestType){ + sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short)0 + ,localId,remoteId,requestType,innerSeq,localToken,rigToken)); + innerSeq++; + } + + /** + * 发Ping电台数据包 + */ + public void sendPingPacket() { + byte[] data = IComPacketTypes.PingPacket.sendPingData(localId, remoteId, pingSeq); + sendUntrackedPacket(data);//因为Ping包走自己的序列,所以发送unTracked包 + //pingSeq++;要在电台回复我之后,再自增 + } + + /** + * 回复电台的ping + * + * @param data 对方的ping数据 + */ + public void sendReplyPingPacket(byte[] data) { + byte[] packet = IComPacketTypes.PingPacket.sendReplayPingData(data, localId, remoteId); + sendUntrackedPacket(packet); + } + + /** + * 发送指令数据包 + * + * @param data 数据包 + */ + public synchronized void sendUntrackedPacket(byte[] data) { + try { + udpClient.sendData(data, rigIp, rigPort); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + } + + /** + * 发送tracked数据包 + * + * @param data 数据包 + */ + public synchronized void sendTrackedPacket(byte[] data) { + try { + lastSentTime=System.currentTimeMillis(); + System.arraycopy(IComPacketTypes.shortToBigEndian(trackedSeq), 0 + , data, 6, 2);//把序号写到数据列表里 + udpClient.sendData(data, rigIp, rigPort); + txSeqBuffer.add(trackedSeq, data); + trackedSeq++; + } catch (UnknownHostException e) { + e.printStackTrace(); + } + } + + public int getLocalPort() { + return localPort; + } + + /** + * 发送空数据包,是Tracked发送。此函数,一般在idleTimer中调用。放在此处,是为了方便调用。 + */ + public void sendIdlePacket() { + //seq设置为0,是因为:在sendTrackedPacket中,会把trackedSeq写到数据包中 + sendTrackedPacket(IComPacketTypes.ControlPacket.idlePacketData((short) 0, localPort, remoteId)); + } + + + public OnStreamEvents getOnStreamEvents() { + return onStreamEvents; + } + + public void setOnStreamEvents(OnStreamEvents onStreamEvents) { + this.onStreamEvents = onStreamEvents; + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java new file mode 100644 index 0000000..36cf8c5 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java @@ -0,0 +1,226 @@ +package com.bg7yoz.ft8cn.icom; +/** + * 简单封装的udp协议处理 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.util.Log; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class IcomUdpClient { + private static final String TAG = "RadioUdpSocket"; + + + private final int MAX_BUFFER_SIZE = 1024 *2; + private DatagramSocket sendSocket; + //private int remotePort; + private int localPort=-1; + private boolean activated = false; + private OnUdpEvents onUdpEvents = null; + private final ExecutorService doReceiveThreadPool = Executors.newCachedThreadPool(); + private DoReceiveRunnable doReceiveRunnable=new DoReceiveRunnable(this); + private final ExecutorService sendDataThreadPool = Executors.newCachedThreadPool(); + private SendDataRunnable sendDataRunnable=new SendDataRunnable(this); + + public IcomUdpClient() {//本地端口随机 + localPort=-1; + } + public IcomUdpClient(int localPort) {//如果localPort==-1,本地端口随机 + this.localPort=localPort; + } + + public void sendData(byte[] data, String ip,int port) throws UnknownHostException { + if (!activated) return; + + InetAddress address = InetAddress.getByName(ip); + sendDataRunnable.address=address; + sendDataRunnable.data=data; + sendDataRunnable.port=port; + sendDataThreadPool.execute(sendDataRunnable); +// new Thread(new Runnable() { +// @Override +// public void run() { +// DatagramPacket packet = new DatagramPacket(data, data.length, address, port); +// synchronized (this) { +// try { +// sendSocket.send(packet); +// } catch (IOException e) { +// e.printStackTrace(); +// Log.e(TAG, "IComUdpClient: " + e.getMessage()); +// if (onUdpEvents!=null){ +// onUdpEvents.OnUdpSendIOException(e); +// } +// } +// } +// } +// }).start(); + } + private static class SendDataRunnable implements Runnable{ + byte[] data; + int port; + InetAddress address; + IcomUdpClient client; + + public SendDataRunnable(IcomUdpClient client) { + this.client = client; + } + + @Override + public void run() { + DatagramPacket packet = new DatagramPacket(data, data.length, address, port); + synchronized (this) { + try { + client.sendSocket.send(packet); + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "IComUdpClient: " + e.getMessage()); + if (client.onUdpEvents!=null){ + client.onUdpEvents.OnUdpSendIOException(e); + } + } + } + } + } + + public boolean isActivated() { + return activated; + } + + public synchronized void setActivated(boolean activated) throws SocketException { + this.activated = activated; + if (activated) {//通过activated判断是否结束接收线程,并清空sendSocket指针 + sendSocket = new DatagramSocket(); + //new DatagramSocket(null);//绑定的端口号随机 + sendSocket.setReuseAddress(true); + if (localPort!=-1) {//绑定指定的本机端口 + sendSocket.bind(new InetSocketAddress(localPort)); + } + + //更新一下本地端口值 + localPort=sendSocket.getLocalPort(); + Log.e(TAG, "openUdpPort: " + sendSocket.getLocalPort()); + //Log.e(TAG, "openUdpIp: " + sendSocket.getLocalAddress()); + + + receiveData(); + } else { + if (sendSocket != null) { + sendSocket.close(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + private void receiveData() { + doReceiveThreadPool.execute(doReceiveRunnable); +// new Thread(new Runnable() { +// @Override +// public void run() { +// while (activated) { +// byte[] data = new byte[MAX_BUFFER_SIZE]; +// DatagramPacket packet = new DatagramPacket(data, data.length); +// try { +// sendSocket.receive(packet); +// if (onUdpEvents != null) { +// byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength()); +// onUdpEvents.OnReceiveData(sendSocket, packet, temp); +// } +// //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName()); +// } catch (IOException e) { +// e.printStackTrace(); +// Log.e(TAG, "receiveData: error:" + e.getMessage()); +// } +// +// } +// Log.e(TAG, "udpClient: is exit!"); +// sendSocket.close(); +// sendSocket = null; +// } +// }).start(); + + } + + public void setOnUdpEvents(OnUdpEvents onUdpEvents) { + this.onUdpEvents = onUdpEvents; + } + + public interface OnUdpEvents { + void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data); + void OnUdpSendIOException(IOException e); + } + + public int getLocalPort() { + if (sendSocket != null) { + return sendSocket.getLocalPort(); + } else { + return 0; + } + } + + public String getLocalIp() { + if (sendSocket != null) { + return sendSocket.getLocalAddress().toString(); + } else { + return "127.0.0.1"; + } + } + + public DatagramSocket getSendSocket() { + return sendSocket; + } + + + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%02x ", data[i] & 0xff)); + } + return s.toString(); + } + private static class DoReceiveRunnable implements Runnable{ + IcomUdpClient icomUdpClient; + + public DoReceiveRunnable(IcomUdpClient icomUdpClient) { + this.icomUdpClient = icomUdpClient; + } + + @Override + public void run() { + while (icomUdpClient.activated) { + byte[] data = new byte[icomUdpClient.MAX_BUFFER_SIZE]; + DatagramPacket packet = new DatagramPacket(data, data.length); + try { + icomUdpClient.sendSocket.receive(packet); + if (icomUdpClient.onUdpEvents != null) { + byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength()); + icomUdpClient.onUdpEvents.OnReceiveData(icomUdpClient.sendSocket, packet, temp); + } + //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName()); + } catch (IOException e) { + e.printStackTrace(); + Log.e(TAG, "receiveData: error:" + e.getMessage()); + } + + } + Log.e(TAG, "udpClient: is exit!"); + icomUdpClient.sendSocket.close(); + icomUdpClient.sendSocket = null; + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java new file mode 100644 index 0000000..1e8368e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java @@ -0,0 +1,117 @@ +package com.bg7yoz.ft8cn.log; +/** + * 用于记录SWL QSO所用的哈希表类型 ,因为要记录上方的呼号,所以要有2个String KEY,HashMap并不合适, + * 这里采用谷歌的guava:31.1-jre库 + * + * BG7YOZ + * 2023-03-20 + * + */ + +import androidx.annotation.Nullable; + +import com.google.common.collect.Table; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public class HashTable implements Table { + @Override + public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) { + return false; + } + + @Override + public boolean containsRow(@Nullable Object rowKey) { + return false; + } + + @Override + public boolean containsColumn(@Nullable Object columnKey) { + return false; + } + + @Override + public boolean containsValue(@Nullable Object value) { + return false; + } + + @Nullable + @Override + public @org.checkerframework.checker.nullness.qual.Nullable Object get(@Nullable Object rowKey, @Nullable Object columnKey) { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public void clear() { + + } + + @Nullable + @Override + public @org.checkerframework.checker.nullness.qual.Nullable Object put(Object rowKey, Object columnKey, Object value) { + return null; + } + + @Override + public void putAll(Table table) { + + } + + @Nullable + @Override + public @org.checkerframework.checker.nullness.qual.Nullable Object remove(@Nullable Object rowKey, @Nullable Object columnKey) { + return null; + } + + @Override + public Map row(Object rowKey) { + return null; + } + + @Override + public Map column(Object columnKey) { + return null; + } + + @Override + public Set cellSet() { + return null; + } + + @Override + public Set rowKeySet() { + return null; + } + + @Override + public Set columnKeySet() { + return null; + } + + @Override + public Collection values() { + return null; + } + + @Override + public Map rowMap() { + return null; + } + + @Override + public Map columnMap() { + return null; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java new file mode 100644 index 0000000..980842a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java @@ -0,0 +1,199 @@ +package com.bg7yoz.ft8cn.log; +/** + * 日志中通联呼号的列表 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.callsign.CallsignInfo; +import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; + +import java.util.ArrayList; + +public class LogCallsignAdapter extends RecyclerView.Adapter { + //private ArrayList callsignRecords=new ArrayList<>(); + private final MainViewModel mainViewModel; + private final Context context; + + public LogCallsignAdapter(Context context,MainViewModel mainViewModel) { + this.mainViewModel = mainViewModel; + this.context=context; + } + + @NonNull + @Override + public LogCallsignItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.log_callsign_holder_item, parent, false); + return new LogCallsignItemHolder(view); + } + + + + + /** + * 获取记录 + * @param position 位置 + * @return 记录 + */ + public QSLCallsignRecord getRecord(int position){ + return mainViewModel.callsignRecords.get(position); + } + /** + * 返回查询结题 + * @param records 记录 + */ + @SuppressLint("NotifyDataSetChanged") + public void setQSLCallsignList(ArrayList records){ + mainViewModel.callsignRecords.addAll(records); + //mainViewModel.callsignRecords=records; + notifyDataSetChanged(); + } + + /** + * 清空记录 + */ + public void clearRecords(){ + mainViewModel.callsignRecords.clear(); + } + + @SuppressLint("SetTextI18n") + @Override + public void onBindViewHolder(@NonNull LogCallsignItemHolder holder, int position) { + if ((position%2)==0){ + holder.logCallSignQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); + }else { + holder.logCallSignQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); + } + holder.record=mainViewModel.callsignRecords.get(position); + + + if (holder.record.isQSL||holder.record.isLotW_QSL){ + holder.callsignQSOIsQSLextView.setText(GeneralVariables.getStringFromResource(R.string.confirmed)); + holder.callsignQSOIsQSLextView.setTextColor(context.getResources().getColor( + R.color.is_qsl_text_color)); + }else { + holder.callsignQSOIsQSLextView.setText(GeneralVariables.getStringFromResource(R.string.unconfirmed)); + holder.callsignQSOIsQSLextView.setTextColor(context.getResources().getColor( + R.color.is_not_qsl_text_color)); + } + if (holder.record.isLotW_QSL){ + holder.isQSLModeDistTextView.setText(GeneralVariables.getStringFromResource(R.string.lotw_confirmation)); + }else if (holder.record.isQSL){ + holder.isQSLModeDistTextView.setText(GeneralVariables.getStringFromResource(R.string.manual_confirmation)); + }else { + holder.isQSLModeDistTextView.setText(""); + } + + holder.callsignLogTextView.setText(holder.record.getCallsign()); + + holder.callsignQSOLastTimeTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.log_last_time) + ,holder.record.getLastTime())); + if (holder.record.getGrid().length()>0) { + holder.callsignQSOGridTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.log_grid) + , holder.record.getGrid())); + }else { + holder.callsignQSOGridTextView.setText(""); + } + + holder.callsignQSLBandTextView.setText(holder.record.getBand()); + holder.callsignQSOModeTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.log_mode) + ,holder.record.getMode())); + //计算距离 + holder.callsignQSLDistTextView.setText(MaidenheadGrid.getDistStr( + GeneralVariables.getMyMaidenheadGrid() + , holder.record.getGrid())); + + if (holder.record.where==null){ + setQueryHolderCallsign(holder); + }else {holder.callsignQSOWhereTextView.setText(holder.record.where);} + holder.callsignDxccZoneTextView.setText(holder.record.dxccStr); + } + + + //查呼号的归属地 + private void setQueryHolderCallsign(@NonNull LogCallsignAdapter.LogCallsignItemHolder holder) { + GeneralVariables.callsignDatabase.getCallsignInformation(holder.record.getCallsign() + , new OnAfterQueryCallsignLocation() { + @Override + public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { + holder.callsignQSOWhereTextView.post(new Runnable() { + @SuppressLint("DefaultLocale") + @Override + public void run() { + if (GeneralVariables.isChina) { + holder.callsignQSOWhereTextView.setText(callsignInfo.CountryNameCN); + holder.record.where = callsignInfo.CountryNameCN; + }else { + holder.callsignQSOWhereTextView.setText(callsignInfo.CountryNameEn); + holder.record.where = callsignInfo.CountryNameEn; + } + holder.record.dxccStr=String.format("DXCC : %s, ITU : %d, CQZONE : %d" + ,callsignInfo.DXCC,callsignInfo.ITUZone,callsignInfo.CQZone); + holder.callsignDxccZoneTextView.setText(holder.record.dxccStr); + } + }); + } + }); + } + + + + @Override + public int getItemCount() { + return mainViewModel.callsignRecords.size(); + } + + static class LogCallsignItemHolder extends RecyclerView.ViewHolder{ + QSLCallsignRecord record; + ConstraintLayout logCallSignQSLHolderConstraintLayout; + TextView callsignLogTextView,callsignQSOLastTimeTextView,callsignQSLBandTextView + ,callsignQSOGridTextView,callsignQSOModeTextView,callsignQSLDistTextView + ,callsignQSOWhereTextView,callsignQSOIsQSLextView + ,isQSLModeDistTextView,callsignDxccZoneTextView; + public LogCallsignItemHolder(@NonNull View itemView) { + super(itemView); + logCallSignQSLHolderConstraintLayout=itemView.findViewById(R.id.logCallSignQSLHolderConstraintLayout); + callsignLogTextView=itemView.findViewById(R.id.callsignLogTextView); + callsignQSOLastTimeTextView=itemView.findViewById(R.id.callsignQSOLastTimeTextView); + callsignQSLBandTextView=itemView.findViewById(R.id.callsignQSLBandTextView); + callsignQSOGridTextView=itemView.findViewById(R.id.callsignQSOGridTextView); + callsignQSOModeTextView=itemView.findViewById(R.id.callsignQSOModeTextView); + callsignQSLDistTextView=itemView.findViewById(R.id.callsignQSLDistTextView); + callsignQSOWhereTextView=itemView.findViewById(R.id.callsignQSOWhereTextView); + callsignQSOIsQSLextView=itemView.findViewById(R.id.callsignQSOIsQSLextView); + isQSLModeDistTextView=itemView.findViewById(R.id.isQSLModeDistTextView); + callsignDxccZoneTextView=itemView.findViewById(R.id.callsignDxccZoneTextView); + + itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view + , ContextMenu.ContextMenuInfo contextMenuInfo) { + view.setTag(getAdapterPosition()); + contextMenu.add(0,2,0 + ,String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) + ,record.getCallsign())).setActionView(view); + } + }); + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java new file mode 100644 index 0000000..d040203 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java @@ -0,0 +1,117 @@ +package com.bg7yoz.ft8cn.log; + +import android.util.Log; + +import com.bg7yoz.ft8cn.html.ImportTaskList; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * 日志文件导入。 + * 构建方法需要日志文件名,此处的文件是由NanoHTTPd的session中的post过来的。 + * getFileContext是获取全部文件内容。 + * getLogBody是获取日志文件中全部的原始记录内容,也就是全部以后面的数据 + * getLogRecords是获取拆解后的全部记录列表,记录是以HashMap方式保存的,其中HashMap的Key是字段名(大写),value是实际的值 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +public class LogFileImport { + private static final String TAG = "LogFileImport"; + private final String fileContext; + private final HashMap errorLines=new HashMap<>(); + private ImportTaskList.ImportTask importTask; + + /** + * 构建函数,需要文件名,如果在读取文件时出错,会回抛异常 + * + * @param logFileName 日志文件名 + * @throws IOException 回抛异常 + */ + public LogFileImport(ImportTaskList.ImportTask task, String logFileName) throws IOException { + importTask=task; + FileInputStream logFileStream = new FileInputStream(logFileName); + byte[] bytes = new byte[logFileStream.available()]; + logFileStream.read(bytes); + fileContext = new String(bytes); + } + + /** + * 获取日志文件的全部内容 + * + * @return 全部文本 + */ + public String getFileContext() { + return fileContext; + } + + public String getLogBody() { + String[] temp = fileContext.split("[<][Ee][Oo][Hh][>]"); + if (temp.length > 1) { + return temp[temp.length - 1]; + } else { + return ""; + } + } + + /** + * 获取日志文件中全部的记录,每条记录是以HashMap保存的。HashMap的Key是字段名(大写),Value是值。 + * + * @return 记录列表。ArrayList + */ + public ArrayList> getLogRecords() { + String[] temp = getLogBody().split("[<][Ee][Oo][Rr][>]");//拆解出每个记录的原始内容 + ArrayList> records = new ArrayList<>(); + int count=0;//解析计数器 + for (String s : temp) {//对每一个原始记录内容做拆解 + count++; + if (!s.contains("<")) { + continue; + }//说明没有标签,不做拆解 + try { + HashMap record = new HashMap<>();//创建一个记录 + String[] fields = s.split("<");//拆解记录的每一个字段 + + for (String field : fields) {//对每一个原始记录做拆解 + + if (field.length() > 1) {//如果是可拆解的 + String[] values = field.split(">");//拆解记录的字段名和值 + + if (values.length > 1) {//如果是可拆解的 + if (values[0].contains(":")) {//拆解字段名和字段的长度,冒号前的字段名,后面是长度 + String[] ttt = values[0].split(":"); + if (ttt.length > 1) { + String name = ttt[0];//字段名 + int valueLen = Integer.parseInt(ttt[1]);//字段长度 + if (valueLen > 0) { + if (values[1].length() < valueLen) { + valueLen = values[1].length() - 1; + } + String value = values[1].substring(0, valueLen);//字段值 + record.put(name.toUpperCase(), value);//保存字段,key要大写 + } + } + + } + } + } + } + records.add(record);//保存记录 + }catch (Exception e){ + errorLines.put(count,s.replace("<","<"));//把错误的内容保存下来。 + importTask.readErrorCount=errorLines.size(); + } + } + return records; + } + public int getErrorCount(){ + return errorLines.size(); + } + public HashMap getErrorLines(){ + return errorLines; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java new file mode 100644 index 0000000..8e170f8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java @@ -0,0 +1,241 @@ +package com.bg7yoz.ft8cn.log; +/** + * 通联日志的列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.callsign.CallsignInfo; +import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation; + +import java.util.ArrayList; + +public class LogQSLAdapter extends RecyclerView.Adapter { + private ArrayList qslRecords=new ArrayList<>(); + private final MainViewModel mainViewModel; + private final Context context; + + public LogQSLAdapter(Context context, MainViewModel mainViewModel) { + this.mainViewModel = mainViewModel; + this.context = context; + } + + @NonNull + @Override + public LogQSLItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.log_qsl_holder_item, parent, false); + return new LogQSLItemHolder(view); + } + + + @SuppressLint("NotifyDataSetChanged") + public void setQSLList(ArrayList list) { + qslRecords.addAll(list); + notifyDataSetChanged(); + } + + /** + * 清空记录列表 + */ + public void clearRecords(){ + qslRecords.clear(); + } + + /** + * 删除日志 + * + * @param position 在列表中的位置 + */ + public void deleteRecord(int position) { + mainViewModel.databaseOpr.deleteQSLByID(qslRecords.get(position).id); + qslRecords.remove(position); + } + + public QSLRecordStr getRecord(int position) { + return qslRecords.get(position); + } + + /** + * 修改手工确认项 + * + * @param position 列表位置 + * @param b 状态 + */ + public void setRecordIsQSL(int position, boolean b) { + qslRecords.get(position).isQSL = b; + mainViewModel.databaseOpr.setQSLTableIsQSL(b, qslRecords.get(position).id); + + } + + @SuppressLint({"DefaultLocale", "SetTextI18n"}) + @Override + public void onBindViewHolder(@NonNull LogQSLItemHolder holder, int position) { + holder.record = qslRecords.get(position); + + if ((position % 2) == 0) { + holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); + } else { + holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); + } + holder.logQSLCallsignTextView.setText(holder.record.getCall()); + if (!holder.record.getGridsquare().equals("")) { + holder.logQSOGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid) + , holder.record.getGridsquare())); + } else { + holder.logQSOGridTextView.setText(""); + } + holder.logQSLMyCallsignTextView.setText(holder.record.getStation_callsign()); + if (!holder.record.getMy_gridsquare().equals("")) { + holder.logQSLMyGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid) + , holder.record.getMy_gridsquare())); + } else { + holder.logQSLMyGridTextView.setText(""); + } + holder.logQSOStartTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time) + , holder.record.getTime_on())); + holder.logQSOEndTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time) + , holder.record.getTime_off())); + + holder.logQSOReceiveTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd) + , holder.record.getRst_rcvd().equals("-120") + ||holder.record.getRst_rcvd().equals("-100") + ?"":holder.record.getRst_rcvd())); + holder.logQSOSendTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent) + ,holder.record.getRst_sent().equals("-120") + ||holder.record.getRst_sent().equals("-100") + ?"": holder.record.getRst_sent())); + holder.logQSLBandTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_band) + , holder.record.getBand())); + holder.logQSLFreqTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq) + , holder.record.getFreq())); + holder.logQSOModeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode) + , holder.record.getMode())); + holder.logQSOcCommentTextView.setText(holder.record.getComment()); + + if (holder.record.isLotW_QSL) { + holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_lotw_confirmation)); + holder.logIsQSLTextView.setTextColor(context.getResources().getColor( + R.color.is_qsl_text_color)); + } else if (holder.record.isQSL) { + holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation)); + holder.logIsQSLTextView.setTextColor(context.getResources().getColor( + R.color.is_qsl_text_color)); + + } else { + holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_unconfirmed)); + holder.logIsQSLTextView.setTextColor(context.getResources().getColor( + R.color.is_not_qsl_text_color)); + } + + //查呼号的位置 + if (holder.record.where == null) { + setQueryHolderCallsign(holder); + } else if (holder.record.where.equals("")) { + setQueryHolderCallsign(holder); + } else { + holder.logQSLWhereTextView.setText(holder.record.where); + } + } + + //查呼号的归属地 + private void setQueryHolderCallsign(@NonNull LogQSLAdapter.LogQSLItemHolder holder) { + GeneralVariables.callsignDatabase.getCallsignInformation(holder.record.getCall() + , new OnAfterQueryCallsignLocation() { + @Override + public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) { + holder.logQSLWhereTextView.post(new Runnable() { + @Override + public void run() { + if (GeneralVariables.isChina) { + holder.logQSLWhereTextView.setText(callsignInfo.CountryNameCN); + holder.record.where = callsignInfo.CountryNameCN; + } else { + holder.logQSLWhereTextView.setText(callsignInfo.CountryNameEn); + holder.record.where = callsignInfo.CountryNameEn; + } + } + }); + + } + }); + } + + + @Override + public int getItemCount() { + return qslRecords.size(); + } + + public ArrayList getRecords() { + return qslRecords; + } + + static class LogQSLItemHolder extends RecyclerView.ViewHolder { + QSLRecordStr record; + ConstraintLayout logQSLHolderConstraintLayout; + TextView logQSLCallsignTextView, logQSOGridTextView, logQSOStartTimeTextView, logQSOEndTimeTextView, logQSOReceiveTextView, logQSOSendTextView, logQSLBandTextView, logQSLFreqTextView, logQSOModeTextView, logQSOcCommentTextView, logQSLMyCallsignTextView, logQSLMyGridTextView, logQSLWhereTextView, logIsQSLTextView; + + public LogQSLItemHolder(@NonNull View itemView) { + super(itemView); + logQSLHolderConstraintLayout = itemView.findViewById(R.id.logQSLHolderConstraintLayout); + logQSLCallsignTextView = itemView.findViewById(R.id.logQSLCallsignTextView); + logQSOGridTextView = itemView.findViewById(R.id.logQSOGridTextView); + logQSOStartTimeTextView = itemView.findViewById(R.id.logQSOStartTimeTextView); + logQSOEndTimeTextView = itemView.findViewById(R.id.logQSOEndTimeTextView); + logQSOReceiveTextView = itemView.findViewById(R.id.logQSOReceiveTextView); + logQSOSendTextView = itemView.findViewById(R.id.logQSOSendTextView); + logQSLBandTextView = itemView.findViewById(R.id.logQSLBandTextView); + logQSLFreqTextView = itemView.findViewById(R.id.logQSLFreqTextView); + logQSOModeTextView = itemView.findViewById(R.id.logQSOModeTextView); + logQSOcCommentTextView = itemView.findViewById(R.id.logQSOcCommentTextView); + logQSLMyCallsignTextView = itemView.findViewById(R.id.logQSLMyCallsignTextView); + logQSLMyGridTextView = itemView.findViewById(R.id.logQSLMyGridTextView); + logQSLWhereTextView = itemView.findViewById(R.id.logQSLWhereTextView); + logIsQSLTextView = itemView.findViewById(R.id.logIsQSLTextView); + + itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view + , ContextMenu.ContextMenuInfo contextMenuInfo) { + view.setTag(getAdapterPosition()); + //添加菜单的参数i1:组,i2:id值,i3:显示顺序 + if (record.isQSL) { + contextMenu.add(0, 0, 0 + , String.format(GeneralVariables.getStringFromResource(R.string.qsl_cancel_confirmation) + , record.getCall())).setActionView(view); + } else { + contextMenu.add(0, 1, 0 + , String.format(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation_s) + , record.getCall())).setActionView(view); + } + contextMenu.add(0, 2, 0 + , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) + , record.getCall())).setActionView(view); + + if (record.getGridsquare() != null && !record.getGridsquare().equals("") + && record.getMy_gridsquare() != null && !record.getMy_gridsquare().equals("")) { + contextMenu.add(0, 3, 0 + , GeneralVariables.getStringFromResource(R.string.log_menu_location)) + .setActionView(view); + } + } + }); + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java new file mode 100644 index 0000000..ea54026 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLCallsign.java @@ -0,0 +1,12 @@ +package com.bg7yoz.ft8cn.log; +/** + * 查询呼号日志的回调。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import java.util.ArrayList; + +public interface OnQueryQSLCallsign { + void afterQuery(ArrayList records); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java new file mode 100644 index 0000000..e24b8d9 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/OnQueryQSLRecordCallsign.java @@ -0,0 +1,12 @@ +package com.bg7yoz.ft8cn.log; +/** + * 查询通联日志的回调。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import java.util.ArrayList; + +public interface OnQueryQSLRecordCallsign { + void afterQuery(ArrayList records); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java new file mode 100644 index 0000000..7c0b466 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLCallsignRecord.java @@ -0,0 +1,80 @@ +package com.bg7yoz.ft8cn.log; + +/** + * 通联过的呼号的日志。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class QSLCallsignRecord { + private String callsign; + private String mode; + private String grid; + private String band; + private String lastTime; + public String where=null; + public String dxccStr=""; + public boolean isQSL=false;//是否手工确认 + public boolean isLotW_QSL = false;//是否是lotw确认的 + + public String getCallsign() { + return callsign; + } + + public void setCallsign(String callsign) { + if (callsign!=null) { + this.callsign = callsign; + }else { + this.callsign=""; + } + } + + public String getLastTime() { + return lastTime; + } + + public void setLastTime(String lastTime) { + if (lastTime!=null) { + this.lastTime = lastTime; + }else { + this.lastTime=""; + } + } + + + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + if (mode!=null) { + this.mode = mode; + }else { + this.mode=""; + } + } + + public String getGrid() { + return grid; + } + + public void setGrid(String grid) { + if (grid!=null) { + this.grid = grid; + }else { + this.grid=""; + } + } + + public String getBand() { + return band; + } + + public void setBand(String band) { + if (band!=null) { + this.band = band; + }else { + this.band=""; + } + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java new file mode 100644 index 0000000..974690c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecord.java @@ -0,0 +1,386 @@ +package com.bg7yoz.ft8cn.log; + +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.HashMap; +import java.util.Objects; + +/** + * 用于记录通联成功信息的类。通联成功是指FT8完成6条消息的通联。并不是互认。 + * isLotW_import是指是否是外部的数据导入,因为用户可能使用了JTDX等软件通联,这样可以把通联的结果导入到FT8CN + * isLotW_QSL是指是否被平台确认。 + * isQSL是指是否被手工确认 + * + * @author BGY70Z + * @date 2023-03-20 + */ +public class QSLRecord { + private static final String TAG = "QSLRecord"; + public long id = -1; + //private long startTime;//起始时间 + private String qso_date; + private String time_on; + private String qso_date_off; + private String time_off; + //private long endTime;//结束时间 + + private final String myCallsign;//我的呼号 + private String myMaidenGrid;//我的网格 + private String toCallsign;//对方的呼号 + private String toMaidenGrid;//对方的网格 + private int sendReport;//对方收到我的报告(也就是我发送的信号强度) + private int receivedReport;//我收到对方的报告(也就是SNR) + private String mode = "FT8"; + private String bandLength = ""; + private long bandFreq;//发射的波段 + private int wavFrequency;//发射的频率 + private String comment; + public boolean isQSL = false;//手工确认 + public boolean isLotW_import = false;//是否是从外部数据导入的,此项需要在数据库中比对才能设定 + public boolean isLotW_QSL = false;//是否是lotw确认的 + + public boolean saved = false;//是否被保存到数据库中 + + public boolean isInvalid=false;//是否解析出错 + public String errorMSG="";//如果解析出错,错误的消息 + + /** + * 用于SWL QSO记录,记录SWL QSO的条件是收听到双方的信号报告 + * + * @param msg FT8消息 + */ + public QSLRecord(Ft8Message msg) { + this.qso_date_off = UtcTimer.getYYYYMMDD(msg.utcTime); + this.time_off = UtcTimer.getTimeHHMMSS(msg.utcTime); + this.myCallsign = msg.callsignFrom; + this.toCallsign = msg.callsignTo; + wavFrequency = Math.round(msg.freq_hz); + sendReport = -100; + receivedReport = -100; + bandLength = BaseRigOperation.getMeterFromFreq(GeneralVariables.band);//获取波长 + bandFreq = GeneralVariables.band; + comment = "SWL By FT8CN"; + } + + /** + * 构建通联成功的对象 + * + * @param startTime 起始时间 + * @param endTime 结束时间 + * @param myCallsign 我的呼号 + * @param myMaidenGrid 我的网格 + * @param toCallsign 对方呼号 + * @param toMaidenGrid 对方网格 + * @param sendReport 发送的报告 + * @param receivedReport 接收的报告 + * @param mode 模式 默认FT8 + * @param bandFreq 载波频率 + * @param wavFrequency 声音频率 + */ + public QSLRecord(long startTime, long endTime, String myCallsign, String myMaidenGrid + , String toCallsign, String toMaidenGrid, int sendReport, int receivedReport + , String mode, long bandFreq, int wavFrequency) { + //this.startTime = startTime; + this.qso_date = UtcTimer.getYYYYMMDD(startTime); + this.time_on = UtcTimer.getTimeHHMMSS(startTime); + this.qso_date_off = UtcTimer.getYYYYMMDD(endTime); + this.time_off = UtcTimer.getTimeHHMMSS(endTime); + this.myCallsign = myCallsign; + this.myMaidenGrid = myMaidenGrid; + this.toCallsign = toCallsign; + this.toMaidenGrid = toMaidenGrid; + this.sendReport = sendReport; + this.receivedReport = receivedReport; + this.mode = mode; + this.bandLength = BaseRigOperation.getMeterFromFreq(bandFreq);//获取波长 + this.bandFreq = bandFreq; + this.wavFrequency = wavFrequency; + String distance = ""; + if (!myMaidenGrid.equals("") && !toMaidenGrid.equals("")) { + distance = MaidenheadGrid.getDistStrEN(myMaidenGrid, toMaidenGrid); + } + this.comment = + distance.equals("") ? "QSO by FT8CN" + : String.format("Distance: %s, QSO by FT8CN", distance); + } + + public void update(QSLRecord record) { + this.qso_date_off = record.qso_date_off; + this.time_off = record.time_off; + this.toMaidenGrid = record.toMaidenGrid; + this.sendReport = record.sendReport; + this.receivedReport = record.receivedReport; + } + + public QSLRecord(HashMap map) { + isLotW_import = true;//说明是外部导入的数据 + if (map.containsKey("CALL")) {//对方呼号 + toCallsign = map.get("CALL"); + } + if (map.containsKey("STATION_CALLSIGN")) {//我的呼号 + myCallsign = map.get("STATION_CALLSIGN"); + } else { + myCallsign = ""; + } + if (map.containsKey("BAND")) {//载波波长 + bandLength = map.get("BAND"); + } else { + bandLength = ""; + } + + if (map.containsKey("FREQ")) {//载波频率 + try {//要把float转成Long + float freq = Float.parseFloat(Objects.requireNonNull(map.get("FREQ"))); + bandFreq = Math.round(freq * 1000000); + } catch (NumberFormatException e) { + isInvalid=true; + errorMSG="freq:"+e.getMessage(); + e.printStackTrace(); + Log.e(TAG, "QSLRecord: freq" + e.getMessage()); + } + } + if (map.containsKey("MODE")) {//模式 + mode = map.get("MODE"); + } else { + mode = ""; + } + if (map.containsKey("QSO_DATE")) {//通联日期 + qso_date = map.get("QSO_DATE"); + } else { + qso_date = ""; + } + if (map.containsKey("TIME_ON")) {//通联起始时间 + time_on = map.get("TIME_ON"); + } else { + time_on = ""; + } + if (map.containsKey("QSO_DATE_OFF")) {//通联结束日期,此字段只在JTDX中有。 + qso_date_off = map.get("QSO_DATE_OFF"); + } else { + qso_date_off = qso_date; + } + if (map.containsKey("TIME_OFF")) {//通联结束时间,n1mm、Log32、JTDX有,Lotw没有 + time_off = map.get("TIME_OFF"); + } else { + time_off = ""; + } + if (map.containsKey("QSL_RCVD")) {//通联互认,lotw中有。 + isLotW_QSL = Objects.requireNonNull(map.get("QSL_RCVD")).equalsIgnoreCase("Y"); + } + if (map.containsKey("LOTW_QSL_RCVD")) {//通联互认,log32中有。 + isLotW_QSL = Objects.requireNonNull(map.get("LOTW_QSL_RCVD")).equalsIgnoreCase("Y"); + } + if (map.containsKey("QSL_MANUAL")) {//通联互认,lotw中有。 + isQSL = Objects.requireNonNull(map.get("QSL_MANUAL")).equalsIgnoreCase("Y"); + } + + if (map.containsKey("MY_GRIDSQUARE")) {//我的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格 + myMaidenGrid = map.get("MY_GRIDSQUARE"); + } else { + myMaidenGrid = ""; + } + + if (map.containsKey("GRIDSQUARE")) {//对方的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格 + toMaidenGrid = map.get("GRIDSQUARE"); + } else { + toMaidenGrid = ""; + } + + + if (map.containsKey("RST_RCVD")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有 + try {//要把float转成Long + receivedReport = Integer.parseInt(Objects.requireNonNull(map.get("RST_RCVD").trim())); + } catch (NumberFormatException e) { + isInvalid=true; + errorMSG="RST_RCVD:"+e.getMessage(); + e.printStackTrace(); + Log.e(TAG, "QSLRecord: RST_RCVD:" + e.getMessage()); + } + } else { + receivedReport = -120; + } + + if (map.containsKey("RST_SENT")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有 + try {//要把float转成Long + sendReport = Integer.parseInt(Objects.requireNonNull(map.get("RST_SENT").trim())); + } catch (NumberFormatException e) { + isInvalid=true; + errorMSG="RST_SENT:"+e.getMessage(); + e.printStackTrace(); + Log.e(TAG, "QSLRecord: RST_SENT:" + e.getMessage()); + } + } else { + sendReport = -120; + } + if (map.containsKey("COMMENT")) {//注释,JTDX中有 + comment = map.get("COMMENT"); + } else { + comment = String.format(GeneralVariables.getStringFromResource(R.string.qsl_record_import_time) + , UtcTimer.getDatetimeStr(UtcTimer.getSystemTime())); + } + + + } + + /** + * SWL QSO的提示 + * + * @return 提示 + */ + public String swlQSOInfo() { + return String.format("QSO of SWL:%s<--%s", toCallsign, myCallsign); + } + + @Override + public String toString() { + return "QSLRecord{" + + "id=" + id + + ", qso_date='" + qso_date + '\'' + + ", time_on='" + time_on + '\'' + + ", qso_date_off='" + qso_date_off + '\'' + + ", time_off='" + time_off + '\'' + + ", myCallsign='" + myCallsign + '\'' + + ", myMaidenGrid='" + myMaidenGrid + '\'' + + ", toCallsign='" + toCallsign + '\'' + + ", toMaidenGrid='" + toMaidenGrid + '\'' + + ", sendReport=" + sendReport + + ", receivedReport=" + receivedReport + + ", mode='" + mode + '\'' + + ", bandLength='" + bandLength + '\'' + + ", bandFreq=" + bandFreq + + ", wavFrequency=" + wavFrequency + + ", isQSL=" + isQSL + + ", isLotW_import=" + isLotW_import + + ", isLotW_QSL=" + isLotW_QSL + + ", saved=" + saved + + ", comment='" + comment + '\'' + + '}'; + } + + public String toHtmlString() { + String ss = saved ? ", saved=true" : ", saved=false"; + return "QSLRecord{" + + "id=" + id + + ", qso_date='" + qso_date + '\'' + + ", time_on='" + time_on + '\'' + + ", qso_date_off='" + qso_date_off + '\'' + + ", time_off='" + time_off + '\'' + + ", myCallsign='" + myCallsign + '\'' + + ", myMaidenGrid='" + myMaidenGrid + '\'' + + ", toCallsign='" + toCallsign + '\'' + + ", toMaidenGrid='" + toMaidenGrid + '\'' + + ", sendReport=" + sendReport + + ", receivedReport=" + receivedReport + + ", mode='" + mode + '\'' + + ", bandLength='" + bandLength + '\'' + + ", bandFreq=" + bandFreq + + ", wavFrequency=" + wavFrequency + + ", isQSL=" + isQSL + + ", isLotW_import=" + isLotW_import + + ", isLotW_QSL=" + isLotW_QSL + + ss + + ", comment='" + comment + '\'' + + '}'; + } + + public String getBandLength() { + return bandLength; + } + + public String getToCallsign() { + return toCallsign; + } + + public String getToMaidenGrid() { + return toMaidenGrid; + } + + public String getMode() { + return mode; + } + + public long getBandFreq() { + return bandFreq; + } + + public int getWavFrequency() { + return wavFrequency; + } + + + public String getMyCallsign() { + return myCallsign; + } + + public String getMyMaidenGrid() { + return myMaidenGrid; + } + + public void setMyMaidenGrid(String myMaidenGrid) { + this.myMaidenGrid = myMaidenGrid; + } + + public int getSendReport() { + return sendReport; + } + + public int getReceivedReport() { + return receivedReport; + } + + public String getQso_date() { + return qso_date; + } + + public String getTime_on() { + return time_on; + } + + public String getQso_date_off() { + return qso_date_off; + } + + public String getTime_off() { + return time_off; + } + + public String getStartTime() { + return qso_date + "-" + time_on; + } + + public String getEndTime() { + return qso_date_off + "-" + time_off; + } + + public String getComment() { + return comment; + } + + + public void setToMaidenGrid(String toMaidenGrid) { + this.toMaidenGrid = toMaidenGrid; + } + + public void setSendReport(int sendReport) { + this.sendReport = sendReport; + } + + public void setReceivedReport(int receivedReport) { + this.receivedReport = receivedReport; + } + + public void setQso_date(String qso_date) { + this.qso_date = qso_date; + } + + public void setTime_on(String time_on) { + this.time_on = time_on; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java new file mode 100644 index 0000000..42e74fd --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/QSLRecordStr.java @@ -0,0 +1,180 @@ +package com.bg7yoz.ft8cn.log; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.bg7yoz.ft8cn.Ft8Message; + +import java.io.Serializable; + +/** + * 用于在ADAPTER中显示内容,此数据在数据库的查询中生成 + * @author BGY70Z + * @date 2023-03-20 + */ +public class QSLRecordStr implements Serializable { + public int id; + private String call=""; + private String gridsquare=""; + private String mode=""; + private String rst_sent=""; + private String rst_rcvd=""; + private String time_on="";//此时间包括日期(QSO_DATE+TIME_ON组成) + private String time_off="";//此时间包括日期(QSO_DATE_OFF+TIME_OFF组成) + private String band="";//波长 + private String freq=""; + private String station_callsign=""; + private String my_gridsquare=""; + private String comment; + public String where = null; + public boolean isQSL = false;//手工确认 + public boolean isLotW_import = false;//是否是lotw导入的 + public boolean isLotW_QSL = false;//是否是lotw确认的 + + + public String getCall() { + return call; + } + + public void setCall(String call) { + if (call!=null) { + this.call = call; + }else { + this.call=""; + } + } + + public String getGridsquare() { + return gridsquare; + } + + public void setGridsquare(String gridsquare) { + if (gridsquare!=null) { + this.gridsquare = gridsquare; + }else { + this.gridsquare=""; + } + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + if (mode!=null) { + this.mode = mode; + }else { + this.mode=""; + } + } + + public String getRst_sent() { + return rst_sent; + } + + public void setRst_sent(String rst_sent) { + if (rst_sent!=null) { + this.rst_sent = rst_sent; + }else { + this.rst_sent=""; + } + } + + public String getRst_rcvd() { + return rst_rcvd; + } + + public void setRst_rcvd(String rst_rcvd) { + if (rst_rcvd!=null) { + this.rst_rcvd = rst_rcvd; + }else { + this.rst_rcvd=""; + } + } + + public String getTime_on() { + return time_on; + } + + public void setTime_on(String time_on) { + if (time_on!=null) { + this.time_on = time_on; + }else { + this.time_on=""; + } + } + + public String getTime_off() { + return time_off; + } + + public void setTime_off(String time_off) { + if (time_off!=null) { + this.time_off = time_off; + }else { + this.time_off=""; + } + } + + public String getBand() { + return band; + } + + public void setBand(String band) { + if (band!=null) { + this.band = band; + }else { + this.band=""; + } + } + + public String getFreq() { + return freq; + } + + public void setFreq(String freq) { + if (freq!=null) { + this.freq = freq; + }else { + this.freq=""; + } + } + + public String getStation_callsign() { + return station_callsign; + } + + public void setStation_callsign(String station_callsign) { + if (station_callsign!=null) { + this.station_callsign = station_callsign; + }else { + this.station_callsign=""; + } + } + + public String getMy_gridsquare() { + return my_gridsquare; + } + + public void setMy_gridsquare(String my_gridsquare) { + if (my_gridsquare!=null) { + this.my_gridsquare = my_gridsquare; + }else { + this.my_gridsquare=""; + } + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + if (comment!=null) { + this.comment = comment; + }else { + this.comment=""; + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java new file mode 100644 index 0000000..6604a1a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java @@ -0,0 +1,159 @@ +package com.bg7yoz.ft8cn.log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; + +/** + * 用于计算和处理SWL消息中的QSO记录。 + * QSO的计算方法:把FT8通联的6个阶段分成3部分: + * 1.CQ C1 grid + * 2.C1 C2 grid + * ---------第一部分--- + * 3.C2 C1 report + * 4.C1 C2 r-report + * --------第二部分---- + * 5.C2 C1 RR73(RRR) + * 6.C1 C2 73 + * --------第三部分---- + *

+ * 一个基本的QSO,必须有自己的结束点(第三部分),双方的信号报告(在第二部分判断),网格报告可有可无(第一部分) + * 以RR73、RRR、73为检查点,符合以上第一、二部分 + * swlQsoList是个双key的HashMap,用于防止重复记录QSO。 + * C1与C2顺序不同,代表不同的呼叫方。体现在station_callsign和call字段上 + * + * @author BG7YOZ + * @date 2023-03-07 + */ +public class SWLQsoList { + private static final String TAG = "SWLQsoList"; + //通联成功的列表,防止重复,两个KEY顺序分别是:station_callsign和call,Boolean=true,已经QSO + private final HashTable qsoList =new HashTable(); + + public SWLQsoList() { + } + + /** + * 检查有没有QSO消息 + * + * @param newMessages 新的FT8消息 + * @param allMessages 全部的FT8消息 + * @param onFoundSwlQso 当有发现的回调 + */ + public void findSwlQso(ArrayList newMessages, ArrayList allMessages + , OnFoundSwlQso onFoundSwlQso) { + for (int i = 0; i < newMessages.size(); i++) { + Ft8Message msg = newMessages.get(i); + if (msg.inMyCall()) continue;//对包含我自己的消息不处理 + + if (GeneralVariables.checkFun4_5(msg.extraInfo)//结束标识RRR、RR73、73 + && !qsoList.contains(msg.callsignFrom, msg.callsignTo)) {//没有QSO记录 + + QSLRecord qslRecord = new QSLRecord(msg); + + if (checkPart2(allMessages, qslRecord)) {//找双方的信号报告,一个基本的QSO,必须有双方的信号报告 + + checkPart1(allMessages, qslRecord);//找双方的网格报告,顺便更新time_on的时间 + + if (onFoundSwlQso != null) {//触发回调,用于记录到数据库 + qsoList.put(msg.callsignFrom, msg.callsignTo, true);//把QSO记录保存下来 + onFoundSwlQso.doFound(qslRecord);//触发找到QSO的动作 + } + } + } + } + } + + /** + * 查第2部分是否存在,顺便把信号报告保存到QSLRecord中 + * + * @param allMessages 消息列表 + * @param record QSLRecord + * @return 返回值 没有发现:0,存在:2 + */ + private boolean checkPart2(ArrayList allMessages, QSLRecord record) { + boolean foundFromReport = false; + boolean foundToReport = false; + long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间 + for (int i = allMessages.size() - 1; i >= 0; i--) { + Ft8Message msg = allMessages.get(i); + if (msg.callsignFrom.equals(record.getMyCallsign()) + && msg.callsignTo.equals(record.getToCallsign()) + && !foundFromReport) {//callsignFrom发出的信号报告 + int report = GeneralVariables.checkFun2_3(msg.extraInfo); + + if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 + if (report != -100) { + record.setSendReport(report); + foundFromReport = true; + } + } + + if (msg.callsignFrom.equals(record.getToCallsign()) + && msg.callsignTo.equals(record.getMyCallsign()) + && !foundToReport) {//callsignTo发出的信号报告 + int report = GeneralVariables.checkFun2_3(msg.extraInfo); + if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 + if (report != -100) { + record.setReceivedReport(report); + foundToReport = true; + } + } + if (foundToReport && foundFromReport) {//如果双方的信号报告都找到了,就退出循环 + record.setQso_date(UtcTimer.getYYYYMMDD(time_on)); + record.setTime_on(UtcTimer.getTimeHHMMSS(time_on)); + break; + } + } + return foundToReport && foundFromReport;//双方的信号报告都有,才算一个QSO + } + + /** + * 查第2部分是否存在,顺便把网格报告保存到QSLRecord中 + * + * @param allMessages 消息列表 + * @param record QSLRecord + */ + private void checkPart1(ArrayList allMessages, QSLRecord record) { + boolean foundFromGrid = false; + boolean foundToGrid = false; + long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间 + for (int i = allMessages.size() - 1; i >= 0; i--) { + Ft8Message msg = allMessages.get(i); + if (!foundFromGrid + && msg.callsignFrom.equals(record.getMyCallsign()) + && (msg.callsignTo.equals(record.getToCallsign()) || msg.checkIsCQ())) {//callsignFrom的网格报告 + + if (GeneralVariables.checkFun1_6(msg.extraInfo)) { + record.setMyMaidenGrid(msg.extraInfo.trim()); + foundFromGrid = true; + } + if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 + } + + if (!foundToGrid + && msg.callsignFrom.equals(record.getToCallsign()) + && (msg.callsignTo.equals(record.getMyCallsign())|| msg.checkIsCQ())) {//callsignTo发出的信号报告 + if (GeneralVariables.checkFun1_6(msg.extraInfo)) { + record.setToMaidenGrid(msg.extraInfo.trim()); + foundToGrid = true; + } + if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 + } + if (foundToGrid && foundFromGrid) {//如果双方的信号报告都找到了,就退出循环 + break; + } + } + + if (foundFromGrid || foundToGrid) {//发现网格报告,至少一个方向的 + record.setQso_date(UtcTimer.getYYYYMMDD(time_on)); + record.setTime_on(UtcTimer.getTimeHHMMSS(time_on)); + } + } + + public interface OnFoundSwlQso { + void doFound(QSLRecord record); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java new file mode 100644 index 0000000..27b5e9d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/maidenhead/MaidenheadGrid.java @@ -0,0 +1,406 @@ +package com.bg7yoz.ft8cn.maidenhead; +/** + * 梅登海德网格的处理。包括经纬度换算、距离计算。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.location.Location; +import android.location.LocationManager; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.google.android.gms.maps.model.LatLng; + +import java.util.List; + +public class MaidenheadGrid { + private static final String TAG = "MaidenheadGrid"; + private static final double EARTH_RADIUS = 6371393; // 平均半径,单位:m;不是赤道半径。赤道为6378左右 + + /** + * 计算梅登海德网格的经纬度,4字符或6字符。如果网格数据不正确,返回null。如果是四字符的,尾部加ll,取中间的位置。 + * + * @param grid 梅登海德网格数据 + * @return LatLng 返回经纬度,如果数据不正确,返回null + */ + public static LatLng gridToLatLng(String grid) { + if (grid==null) return null; + if (grid.length()==0) return null; + //判断是不是符合梅登海德网格的规则 + if (grid.length() != 2&&grid.length() != 4 && grid.length() != 6) { + return null; + } + if (grid.equalsIgnoreCase("RR73")) return null; + if (grid.equalsIgnoreCase("RR")) return null; + double x=0; + double y=0; + double z=0; + //纬度 + double lat=0; + if (grid.length()==2){ + x=grid.toUpperCase().getBytes()[1]-'A'+0.5f; + }else { + x=grid.toUpperCase().getBytes()[1]-'A'; + } + x*=10; + + if (grid.length()==4){ + y=grid.getBytes()[3]-'0'+0.5f; + }else if (grid.length()==6){ + y=grid.getBytes()[3]-'0'; + } + + if (grid.length()==6){ + z=grid.toUpperCase().getBytes()[5]-'A'+0.5f; + z=z*(1/18f); + } + lat=x+y+z-90; + + //经度 + x=0; + y=0; + z=0; + double lng=0; + if (grid.length()==2){ + x=grid.toUpperCase().getBytes()[0]-'A'+0.5; + }else { + x=grid.toUpperCase().getBytes()[0]-'A'; + } + x*=20; + if (grid.length()==4){ + y=grid.getBytes()[2]-'0'+0.5; + }else if (grid.length()==6){ + y=grid.getBytes()[2]-'0'; + } + y*=2; + if (grid.length()==6){ + z=grid.toUpperCase().getBytes()[4]-'A'+0.5; + z=z*(2/18f); + } + lng=x+y+z-180; + if (lat>85) lat=85;//防止在地图上越界 + if (lat<-85) lat=-85;//防止在地图上越界 + + + return new LatLng(lat,lng); + + + } + + + + + + + public static LatLng[] gridToPolygon(String grid) { + if (grid.length() != 2 && grid.length() != 4 && grid.length() != 6) { + return null; + } + LatLng[] latLngs = new LatLng[4]; + + //纬度1 + double x; + double y = 0; + double z = 0; + double lat1; + x = grid.toUpperCase().getBytes()[1] - 'A'; + x *= 10; + if (grid.length() > 2) { + y = grid.getBytes()[3] - '0'; + } + if (grid.length() > 4) { + z = grid.toUpperCase().getBytes()[5] - 'A'; + z = z * (1f / 18f); + } + lat1 = x + y + z - 90; + if (lat1<-85.0){ + lat1=-85.0; + } + if (lat1>85.0){ + lat1=85.0; + } + + //纬度2 + x = 0; + y = 0; + z = 0; + double lat2; + if (grid.length() == 2) { + x = grid.toUpperCase().getBytes()[1] - 'A' + 1; + } else { + x = grid.toUpperCase().getBytes()[1] - 'A'; + } + x *= 10; + if (grid.length() == 4) { + y = grid.getBytes()[3] - '0' + 1; + } else if (grid.length() == 6) { + y = grid.getBytes()[3] - '0'; + } + if (grid.length() == 6) { + z = grid.toUpperCase().getBytes()[5] - 'A' + 1; + z = z * (1f / 18f); + } + lat2 = x + y + z - 90; + if (lat2<-85.0){ + lat2=-85.0; + } + if (lat2>85.0){ + lat2=85.0; + } + + + //经度1 + x=0;y=0;z=0; + double lng1; + x=grid.toUpperCase().getBytes()[0]-'A'; + x*=20; + + if (grid.length()>2){ + y=grid.getBytes()[2]-'0'; + y*=2; + } + if (grid.length()>4){ + z=grid.toUpperCase().getBytes()[4]-'A'; + z=z*2/18f; + } + lng1=x+y+z-180; + + //经度2 + x=0;y=0;z=0; + double lng2; + if (grid.length()==2){ + x=grid.toUpperCase().getBytes()[0]-'A'+1; + }else { + x=grid.toUpperCase().getBytes()[0]-'A'; + } + x*=20; + if (grid.length()==4){ + y=grid.getBytes()[2]-'0'+1; + }else if (grid.length()==6){ + y=grid.getBytes()[2]-'0'; + } + y*=2; + if (grid.length()==6){ + z=grid.toUpperCase().getBytes()[4]-'A'+1; + z=z*2/18f; + } + lng2=x+y+z-180; + + latLngs[0] = new LatLng(lat1,lng1); + latLngs[1] = new LatLng(lat1,lng2); + latLngs[2] = new LatLng(lat2,lng2); + latLngs[3] = new LatLng(lat2,lng1); + + return latLngs; + + + } + + /** + * 此函数根据纬度计算 6 字符 Maidenhead网格。 + * 经纬度采用 NMEA 格式。换句话说,西经和南纬度为负数。它们被指定为double类型 + * + * @param location 经纬度 + * @return String 梅登海德字符 + */ + public static String getGridSquare(LatLng location) { + double tempNumber;//用于中间计算 + int index;//确定要显示的字符 + double _long = location.longitude; + double _lat = location.latitude; + StringBuilder buff = new StringBuilder(); + + /* + * 计算第一对两个字符 + */ + _long += 180; // 从太平洋中部开始 + tempNumber = _long / 20; // 每个主要正方形都是 20 度宽 + index = (int) tempNumber; // 大写字母的索引 + buff.append(String.valueOf((char) (index + 'A'))); // 设置第一个字符 + _long = _long - (index * 20); // 第 2 步的剩余部分 + + _lat += 90; //从南极开始 180 度 + tempNumber = _lat / 10; // 每个大正方形高 10 度 + index = (int) tempNumber; // 大写字母的索引 + buff.append(String.valueOf((char) (index + 'A')));//设置第二个字符 + _lat = _lat - (index * 10); // 第 2 步的剩余部分 + + /* + * 现在是第二对两数字: + */ + tempNumber = _long / 2; // 步骤 1 的余数除以 2 + index = (int) tempNumber; // 数字索引 + buff.append(String.valueOf((char) (index + '0')));//设置第三个字符 + _long = _long - (index * 2); //第 3 步的剩余部分 + + tempNumber = _lat; // 步骤 1 的余数除以 1 + index = (int) tempNumber; // 数字索引 + buff.append(String.valueOf((char) (index + '0')));//设置第四个字符 + _lat = _lat - index; //第 3 步的剩余部分 + + /* + *现在是第三对两个小写字符: + */ + tempNumber = _long / 0.083333; //步骤 2 的余数除以 0.083333 + index = (int) tempNumber; // 小写字母的索引 + buff.append(String.valueOf((char) (index + 'a')));//设置第五个字符 + + tempNumber = _lat / 0.0416665; // 步骤 2 的余数除以 0.0416665 + index = (int) tempNumber; // 小写字母的索引 + buff.append(String.valueOf((char) (index + 'a')));//设置第五个字符 + + return buff.toString().substring(0, 4); + } + + /** + * 计算经纬度之间的距离 + * + * @param latLng1 经纬度 + * @param latLng2 经纬度 + * @return 距离,公里。 + */ + public static double getDist(LatLng latLng1, LatLng latLng2) { + double radiansAX = Math.toRadians(latLng1.longitude); // A经弧度 + double radiansAY = Math.toRadians(latLng1.latitude); // A纬弧度 + double radiansBX = Math.toRadians(latLng2.longitude); // B经弧度 + double radiansBY = Math.toRadians(latLng2.latitude); // B纬弧度 + + // 公式中“cosβ1cosβ2cos(α1-α2)+sinβ1sinβ2”的部分,得到∠AOB的cos值 + double cos = Math.cos(radiansAY) * Math.cos(radiansBY) * Math.cos(radiansAX - radiansBX) + + Math.sin(radiansAY) * Math.sin(radiansBY); + double acos = Math.acos(cos); // 反余弦值 + return EARTH_RADIUS * acos / 1000; // 最终结果km + } + + /** + * 计算梅登海德网格之间的距离 + * + * @param mGrid1 梅登海德网格 + * @param mGrid2 梅登海德网格2 + * @return double 两个网格之间的距离 + */ + public static double getDist(String mGrid1, String mGrid2) { + LatLng latLng1 = gridToLatLng(mGrid1); + LatLng latLng2 = gridToLatLng(mGrid2); + if (latLng1 != null && latLng2 != null) { + return getDist(latLng1, latLng2); + } else { + return 0; + } + } + + /** + * 计算两个网格之间的距离 + * + * @param mGrid1 网格 + * @param mGrid2 网格 + * @return 距离 + */ + @SuppressLint("DefaultLocale") + public static String getDistStr(String mGrid1, String mGrid2) { + double dist = getDist(mGrid1, mGrid2); + if (dist == 0) { + return ""; + } else { + return String.format(GeneralVariables.getStringFromResource(R.string.distance), dist); + } + } + public static String getDistLatLngStr(LatLng latLng1,LatLng latLng2){ + return String.format(GeneralVariables.getStringFromResource(R.string.distance), getDist(latLng1,latLng2)); + + } + + /** + * 计算两个网格之间的距离,以英文显示公里数 + * + * @param mGrid1 网格 + * @param mGrid2 网格 + * @return 距离 + */ + @SuppressLint("DefaultLocale") + public static String getDistStrEN(String mGrid1, String mGrid2) { + double dist = getDist(mGrid1, mGrid2); + if (dist == 0) { + return ""; + } else { + return String.format("%.0f km", dist); + } + } + + /** + * 获取本设备的经纬度 + * + * @param context context + * @return 经纬度 + */ + public static LatLng getLocalLocation(Context context) { + // 获取位置服务 + String serviceName = Context.LOCATION_SERVICE; + // 调用getSystemService()方法来获取LocationManager对象 + LocationManager locationManager = (LocationManager) context.getSystemService(serviceName); + // 指定LocationManager的定位方法 + //String provider = LocationManager.GPS_PROVIDER; + // 调用getLastKnownLocation()方法获取当前的位置信息 + + List providers = locationManager.getProviders(true); + Location location = null; + for (String s : providers) { + @SuppressLint("MissingPermission") Location l = locationManager.getLastKnownLocation(s); + if (l == null) { + continue; + } + if (location == null || l.getAccuracy() < location.getAccuracy()) { + // Found best last known location: %s", l); + location = l; + } + } + + if (location != null) { + return new LatLng(location.getLatitude(), location.getLongitude()); + } else { + return null; + } + } + + + /** + * 获取本机的梅登海德网格数据。需要定位的权限。 + * + * @param context context + * @return String 返回6字符的梅登海德网格。 + */ + public static String getMyMaidenheadGrid(Context context) { + LatLng latLng = getLocalLocation(context); + + if (latLng != null) { + return getGridSquare(latLng); + } else { + //ToastMessage.show("无法定位,请确认是否有定位的权限。"); + return ""; + } + } + + /** + * 检查是不是梅登海德网格。如果不是返回false。 + * + * @param s 梅登海德网格 + * @return boolean 是否是梅登海德网格。 + */ + public static boolean checkMaidenhead(String s) { + if (s.length() != 4 && s.length() != 6) { + return false; + } else { + if (s.equals("RR73")) { + return false; + } + return Character.isAlphabetic(s.charAt(0)) + && Character.isAlphabetic(s.charAt(1)) + && Character.isDigit(s.charAt(2)) + && Character.isDigit(s.charAt(3)); + } + } + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java new file mode 100644 index 0000000..def2611 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRig.java @@ -0,0 +1,133 @@ +package com.bg7yoz.ft8cn.rigs; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.connector.BaseRigConnector; + +/** + * 电台的抽象类。 + * @author BGY70Z + * @date 2023-03-20 + */ +public abstract class BaseRig { + private long freq;//当前频率值 + public MutableLiveData mutableFrequency = new MutableLiveData<>(); + private int controlMode;//控制模式 + private OnRigStateChanged onRigStateChanged;//当电台的一些状态发生变化的回调 + private int civAddress;//CIV地址 + private int baudRate;//波特率 + private boolean isPttOn=false;//ptt是否打开 + private BaseRigConnector connector = null;//连接电台的对象 + + public abstract boolean isConnected();//确认电台是否连接 + + public abstract void setUsbModeToRig();//设置电台上边带方式 + + public abstract void setFreqToRig();//设置电台频率 + + public abstract void onReceiveData(byte[] data);//当电台发送回数据的动作 + + public abstract void readFreqFromRig();//从电台读取频率 + + public abstract String getName();//获取电台的名字 + + private final OnConnectReceiveData onConnectReceiveData = new OnConnectReceiveData() { + @Override + public void onData(byte[] data) { + onReceiveData(data); + } + }; + + public void setPTT(boolean on) {//设置PTT打开或关闭 + isPttOn=on; + if (onRigStateChanged != null) { + onRigStateChanged.onPttChanged(on); + } + } + +// public void sendWaveData(float[] data) { +// //留给ICOM电台使用 +// } + public void sendWaveData(Ft8Message message) { + //留给ICOM电台使用 + } + + public long getFreq() { + return freq; + } + + public void setFreq(long freq) { + if (freq == this.freq) return; + if (freq == 0) return; + if (freq == -1) return; + mutableFrequency.postValue(freq); + this.freq = freq; + if (onRigStateChanged != null) { + onRigStateChanged.onFreqChanged(freq); + } + } + + public void setConnector(BaseRigConnector connector) { + this.connector = connector; + + this.connector.setOnRigStateChanged(onRigStateChanged); + this.connector.setOnConnectReceiveData(new OnConnectReceiveData() { + @Override + public void onData(byte[] data) { + onReceiveData(data); + } + }); + } + + public void setControlMode(int mode) { + controlMode = mode; + if (connector != null) { + connector.setControlMode(mode); + } + } + + public int getControlMode() { + return controlMode; + } + + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("%02x ", data[i] & 0xff)); + } + return s.toString(); + } + + public BaseRigConnector getConnector() { + return connector; + } + + public OnRigStateChanged getOnRigStateChanged() { + return onRigStateChanged; + } + + public void setOnRigStateChanged(OnRigStateChanged onRigStateChanged) { + this.onRigStateChanged = onRigStateChanged; + } + + public int getCivAddress() { + return civAddress; + } + + public void setCivAddress(int civAddress) { + this.civAddress = civAddress; + } + + public int getBaudRate() { + return baudRate; + } + + public void setBaudRate(int baudRate) { + this.baudRate = baudRate; + } + + public boolean isPttOn() { + return isPttOn; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java new file mode 100644 index 0000000..796fb00 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/BaseRigOperation.java @@ -0,0 +1,138 @@ +package com.bg7yoz.ft8cn.rigs; +/** + * 电台相关计算。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; + +public class BaseRigOperation { + @SuppressLint("DefaultLocale") + public static String getFrequencyStr(long freq) { + return String.format("%d.%03dMHz", freq / 1000000, (freq % 1000000) / 1000); + } + /** + * 检查是不是在WSPR2的频段内 + * + * @param freq 频率 + * @return 是否 + */ + public static boolean checkIsWSPR2(long freq) { + //freq=电台频率+声音频率 + return (freq >= 137400 && freq <= 137600) //2190m + || (freq >= 475400 && freq <= 475600) //630m + || (freq >= 1838000 && freq <= 1838200) //160m + || (freq >= 3594000 && freq <= 3594200) //80m + || (freq >= 5288600 && freq <= 5288800) //60m + || (freq >= 7040000 && freq <= 7040200) //40m + || (freq >= 10140100 && freq <= 10140300) //30m + || (freq >= 14097000 && freq <= 14097200) //20m + || (freq >= 18106000 && freq <= 18106200) //17m + || (freq >= 21096000 && freq <= 21096200) //15m + || (freq >= 24926000 && freq <= 24926200) //12m + || (freq >= 28126000 && freq <= 28126200) //10m + || (freq >= 50294400 && freq <= 50294600) //6m + || (freq >= 70092400 && freq <= 70092600) //4m + || (freq >= 144489900 && freq <= 144490100) //2m + || (freq >= 432301600 && freq <= 432301800) //70cm + || (freq >= 1296501400 && freq <= 1296501600);//23cm + } + + /** + * 通过频率获取波长 + * + * @param freq 频率 + * @return 波长 + */ + @SuppressLint("DefaultLocale") + public static String getMeterFromFreq(long freq) { + if (freq >= 135700 && freq <= 137800) { + return "2200m"; + } // 2200m + else if (freq >= 472000 && freq <= 479000) { + return "630m"; + } // 160m + else if (freq >= 1800000 && freq <= 2000000) { + return "160m"; + } // 160m + else if (freq >= 3500000 && freq <= 4000000) { + return "80m"; + } // 80m + else if (freq >= 5351500 && freq <= 5366500) { + return "60m"; + } // 80m + else if (freq >= 7000000 && freq <= 7300000) { + return "40m"; + } // 40m + else if (freq >= 10100000 && freq <= 10150000) { + return "30m"; + } // 30m + else if (freq >= 14000000 && freq <= 14350000) { + return "20m"; + } // 20m + else if (freq >= 18068000 && freq <= 18168000) { + return "17m"; + } // 17m + else if (freq >= 21000000 && freq <= 21450000) { + return "15m"; + } // 15m + else if (freq >= 24890000 && freq <= 24990000) { + return "12m"; + } // 12m + else if (freq >= 28000000 && freq <= 29700000) { + return "10m"; + } // 10m + else if (freq >= 50000000 && freq <= 54000000) { + return "6m"; + } // 6m + else if (freq >= 144000000 && freq <= 148000000) { + return "2m"; + } // 2m + else if (freq >= 220000000 && freq <= 225000000) { + return "1.25m"; + } //1.25m + else if (freq >= 420000000 && freq <= 450000000) { + return "70cm"; + } //0.7m + else if (freq >= 902000000 && freq <= 928000000) { + return "33cm"; + } //0.33m + else if (freq >= 1240000000 && freq <= 1300000000) { + return "23cm"; + } //0.23m + else { + return calculationMeterFromFreq(freq); + }//不在范围内,就计算一下 + } + + @SuppressLint("DefaultLocale") + private static String calculationMeterFromFreq(Long freq) { + if (freq == 0) return ""; + float meter = 300000000f / (float) freq; + if (meter < 1) {//以厘米为单位 + return String.format("%dcm", Math.round(meter * 10) * 10); + } else if (meter < 20) {//小于20米,以米为单位 + return String.format("%dm", Math.round(meter)); + } else {//大于20M,以10米为单位 + return String.format("%dm", Math.round(meter / 10) * 10); + } + } + + public static String getFrequencyAllInfo(long freq) { + return String.format("%s (%s)", getFrequencyStr(freq), getMeterFromFreq(freq)); + } + + @SuppressLint("DefaultLocale") + public static String getFrequencyFloat(long freq) { + return String.format("%d.%06d", freq / 1000000, (freq % 1000000)); + } + + public static String byteToStr(byte[] data) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + s.append(String.format("0x%02x ", data[i] & 0xff)); + } + return s.toString(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java new file mode 100644 index 0000000..33cfeab --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/CRC16.java @@ -0,0 +1,93 @@ +package com.bg7yoz.ft8cn.rigs; + +/** + * crc16-ccitt-false加密工具 + * + * @author BGY70Z + * @date 2023-03-20 + * + */ +public class CRC16 { + + /** + * crc16-ccitt-false加/解密(四字节) + * + * @param bytes + * @return + */ + public static int crc16(byte[] bytes) { + return crc16(bytes, bytes.length); + } + + /** + * crc16-ccitt-false加/解密(四字节) + * + * @param bytes -字节数组 + * @return + */ + public static int crc16(byte[] bytes, int len) { + int crc = 0xFFFF; + for (int j = 0; j < len; j++) { + crc = ((crc >>> 8) | (crc << 8)) & 0xffff; + crc ^= (bytes[j] & 0xff);// byte to int, trunc sign + crc ^= ((crc & 0xff) >> 4); + crc ^= (crc << 12) & 0xffff; + crc ^= ((crc & 0xFF) << 5) & 0xffff; + } + crc &= 0xffff; + return crc; + } + + /** + * crc16-ccitt-false加/解密(四字节) + * + * @param bytes + * @return + */ + public static int crc16(byte[] bytes, int start, int len) { + int crc = 0xFFFF; + for (; start < len; start++) { + crc = ((crc >>> 8) | (crc << 8)) & 0xffff; + crc ^= (bytes[start] & 0xff);// byte to int, trunc sign + crc ^= ((crc & 0xff) >> 4); + crc ^= (crc << 12) & 0xffff; + crc ^= ((crc & 0xFF) << 5) & 0xffff; + } + crc &= 0xffff; + return crc; + } + + /** + * crc16-ccitt-false加/解密 + * + * @param bytes + * -字节数组 + * @return + */ + public static short crc16_short(byte[] bytes) { + return crc16_short(bytes, 0, bytes.length); + } + + /** + * crc16-ccitt-false加/解密(计算从0位置开始的len长度) + * + * @param bytes + * -字节数组 + * @param len + * -长度 + * @return + */ + public static short crc16_short(byte[] bytes, int len) { + return (short) crc16(bytes, len); + } + + /** + * crc16-ccitt-false加/解密(两字节) + * + * @param bytes + * @return + */ + public static short crc16_short(byte[] bytes, int start, int len) { + return (short) crc16(bytes, start, len); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java new file mode 100644 index 0000000..f1454a3 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftCommand.java @@ -0,0 +1,78 @@ +package com.bg7yoz.ft8cn.rigs; + + +import android.util.Log; + +public class ElecraftCommand { + private static final String TAG = "ElecraftCommand"; + private final String commandID; + private final String data; + /** + * 获取命令(两字节字符串) + * + * @return 主命令值 + */ + public String getCommandID() {//获取主命令 + return commandID; + } + + /** + * 获取命令数据,字符串,没有分号 + * + * @return 命令数据 + */ + public String getData() {//获取命令数据 + return data; + } + + public ElecraftCommand(String commandID, String data) { + this.commandID = commandID; + this.data = data; + } + //解析接收的指令 + + /** + * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 + * + * @param buffer 从串口接收到的数据 + * @return 返回电台指令对象,如果不符合指令的格式,返回null。 + */ + public static ElecraftCommand getCommand(String buffer) { + if (buffer.length() < 2) {//指令的长度必须大于等于2 + return null; + } + if (buffer.substring(0,2).matches("[a-zA-Z][a-zA-Z]")) { + return new ElecraftCommand(buffer.substring(0, 2), buffer.substring(2)); + } + return null; + } + + + /** + * 计算频率 + * @param command 指令 + * @return 频率 + */ + public static long getFrequency(ElecraftCommand command) { + try { + if(command.getCommandID().equals("FA")||command.getCommandID().equals("FB")) { + return Long.parseLong(command.getData()); + }else { + return 0; + } + }catch (Exception e){ + Log.e(TAG, "获取频率失败: "+command.getData()+"\n"+e.getMessage() ); + } + return 0; + } + + public static boolean isSWRMeter(ElecraftCommand command) { + return command.data.length() >= 3; + } + + public static int getSWRMeter(ElecraftCommand command) { + return Integer.parseInt(command.data.substring(0,3)); + } + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java new file mode 100644 index 0000000..bb5a98d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRig.java @@ -0,0 +1,174 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 + */ +public class ElecraftRig extends BaseRig { + private static final String TAG = "ElecraftRig"; + private final StringBuilder buffer = new StringBuilder(); + private int swr = 0; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(ElecraftRigConstant.setReadMetersSWR()); + } + } + + private void showAlert() { + if (swr >= ElecraftRigConstant.swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + + } + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(ElecraftRigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(ElecraftRigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(ElecraftRigConstant.setOperationFreq11Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains(";")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + // return;//说明数据还没接收完。 + }else { + if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf(";"))); + } + + //开始分析数据 + ElecraftCommand elecraftCommand = ElecraftCommand.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";")+1)); + + if (elecraftCommand == null) { + return; + } + if (elecraftCommand.getCommandID().equalsIgnoreCase("FA") + || elecraftCommand.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq=ElecraftCommand.getFrequency(elecraftCommand); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(ElecraftCommand.getFrequency(elecraftCommand)); + } + }else if (elecraftCommand.getCommandID().equalsIgnoreCase("SW")) {//METER + if (ElecraftCommand.isSWRMeter(elecraftCommand)) { + swr = ElecraftCommand.getSWRMeter(elecraftCommand); + } + + showAlert(); + } + + + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(ElecraftRigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "Elecraft series"; + } + + public ElecraftRig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java new file mode 100644 index 0000000..0c3c15e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/ElecraftRigConstant.java @@ -0,0 +1,86 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; + +public class ElecraftRigConstant { + private static final String TAG = "ElecraftRigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x01; + public static final int USB = 0x02; + public static final int CW = 0x03; + public static final int FM = 0x04; + public static final int AM = 0x05; + public static final int DATA = 0x06; + public static final int CW_R = 0x07; + public static final int DATA_R = 0x08; + public static final int swr_alert_max=30;//相当于3.0 + + //PTT状态 + + //指令集 + private static final String PTT_ON = "TX;"; + private static final String PTT_OFF = "RX;"; + private static final String USB_MODE = "MD2;"; + private static final String READ_FREQ = "FA;"; + private static final String READ_SWR = "SWR;"; + + + + + + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW: + return "CW"; + case FM: + return "FM"; + case AM: + return "AM"; + case CW_R: + return "CW_R"; + case DATA: + return "DATA"; + case DATA_R: + return "DATA_R"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + + public static byte[] setOperationUSBMode() { + return USB_MODE.getBytes(); + } + + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 + return String.format("FA%011d;",freq).getBytes(); + } + + + + public static byte[] setReadOperationFreq(){ + return READ_FREQ.getBytes(); + } + + public static byte[] setReadMetersSWR(){ + return READ_SWR.getBytes(); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java new file mode 100644 index 0000000..483889e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Command.java @@ -0,0 +1,72 @@ +package com.bg7yoz.ft8cn.rigs; + + +import android.util.Log; + +public class Flex6000Command { + private static final String TAG = "Flex6000Command"; + private final String commandID; + private final String data; + /** + * 获取命令(两字节字符串) + * + * @return 主命令值 + */ + public String getCommandID() {//获取主命令 + return commandID; + } + + /** + * 获取命令数据,字符串,没有分号 + * + * @return 命令数据 + */ + public String getData() {//获取命令数据 + return data; + } + + public Flex6000Command(String commandID, String data) { + this.commandID = commandID; + this.data = data; + } + //解析接收的指令 + + /** + * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 + * + * @param buffer 从串口接收到的数据 + * @return 返回电台指令对象,如果不符合指令的格式,返回null。 + */ + public static Flex6000Command getCommand(String buffer) { + if (buffer.length() < 4) {//指令的长度必须大于等于4,ZZFA + return null; + } + if (buffer.substring(0,4).matches("[a-zA-Z][a-zA-Z][a-zA-Z][a-zA-Z]")) { + return new Flex6000Command(buffer.substring(0, 4), buffer.substring(4)); + } + return null; + } + + + /** + * 计算频率 + * @param command 指令 + * @return 频率 + */ + public static long getFrequency(Flex6000Command command) { + try { + if(command.getCommandID().equals("ZZFA")||command.getCommandID().equals("ZZFB")) { + return Long.parseLong(command.getData()); + }else { + return 0; + } + }catch (Exception e){ + Log.e(TAG, "获取频率失败: "+command.getData()+"\n"+e.getMessage() ); + } + return 0; + } + + + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java new file mode 100644 index 0000000..003d598 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java @@ -0,0 +1,144 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.os.Handler; +import android.util.Log; + +import com.bg7yoz.ft8cn.database.ControlMode; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * KENWOOD TS590,与YAESU3代指令接近,命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 + */ +public class Flex6000Rig extends BaseRig { + private static final String TAG = "KenwoodTS590Rig"; + private final StringBuilder buffer = new StringBuilder(); + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + readFreqFromRig(); + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Flex6000RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(Flex6000RigConstant.setOperationUSB_DIGI_Mode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Flex6000RigConstant.setOperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains(";")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + return;//说明数据还没接收完。 + }else { + if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf(";"))); + } + //开始分析数据 + Flex6000Command flex6000Command = Flex6000Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";")+1)); + + if (flex6000Command == null) { + return; + } + if (flex6000Command.getCommandID().equalsIgnoreCase("ZZFA")) { + long tempFreq=Flex6000Command.getFrequency(flex6000Command); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(Flex6000Command.getFrequency(flex6000Command)); + } + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Flex6000RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "FLEX 6000 series"; + } + + public Flex6000Rig() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (getConnector()!=null){//切换VFO A + //getConnector().sendData(Flex6000RigConstant.setVFOMode()); + } + } + },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java new file mode 100644 index 0000000..26e4870 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000RigConstant.java @@ -0,0 +1,94 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; + +public class Flex6000RigConstant { + private static final String TAG = "Flex6000RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x00; + public static final int USB = 0x01; + public static final int CW_L = 0x03; + public static final int CW_U = 0x04; + public static final int FM = 0x05; + public static final int AM = 0x06; + public static final int DIGI_U= 0x07; + public static final int DIGI_L= 0x09; + + //PTT状态 + + //指令集 + private static final String PTT_ON = "ZZTX1;"; + private static final String PTT_OFF = "ZZTX0;"; + private static final String USB_DIGI = "ZZMD07;"; + private static final String READ_FREQ = "ZZFA;"; + private static final String SET_VFO = "ZZFR"; + + + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW_L: + return "CW_L"; + case CW_U: + return "CW_U"; + case FM: + return "FM"; + case AM: + return "AM"; + case DIGI_U: + return "DIGI_U"; + case DIGI_L: + return "DIGI_L"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + + public static byte[] setFlexTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + + + + //设置成VFO模式 + public static byte[] setVFOMode(){ + return SET_VFO.getBytes(); + } + + + public static byte[] setOperationUSB_DIGI_Mode() { + return USB_DIGI.getBytes(); + } + + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq(long freq) { + return String.format("ZZFA%011d\r",freq).getBytes(); + } + + public static byte[] setReadOperationFreq(){ + return READ_FREQ.getBytes(); + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java new file mode 100644 index 0000000..1c9a0a8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/FlexNetworkRig.java @@ -0,0 +1,113 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.connector.FlexConnector; +import com.bg7yoz.ft8cn.flex.FlexCommand; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; + +public class FlexNetworkRig extends BaseRig { + private static final String TAG = "FlexNetworkRig"; + private int commandSeq = 1;//指令的序列 + private FlexCommand flexCommand; + private String commandStr; + + //private final int ctrAddress=0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 + //private byte[] dataBuffer=new byte[0];//数据缓冲区 + @SuppressLint("DefaultLocale") + public void sendCommand(FlexCommand command, String cmdContent) { + if (getConnector().isConnected()) { + commandSeq++; + flexCommand = command; + commandStr = String.format("C%d%03d|%s\n", commandSeq, command.ordinal() + , cmdContent); + getConnector().sendData(commandStr.getBytes()); + } + } + + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceTune(int sliceOder, String freq) { + sendCommand(FlexCommand.SLICE_TUNE, String.format("slice t %d %s", sliceOder, freq)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSliceSetMode(int sliceOder, FlexRadio.FlexMode mode) { + sendCommand(FlexCommand.SLICE_SET_TX_ANT, String.format("slice s %d mode=%s", sliceOder, mode.toString())); + } + + @Override + public void setPTT(boolean on) { + getConnector().setPttOn(on); + + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式 + } + } + + @SuppressLint("DefaultLocale") + @Override + public void setFreqToRig() { + if (getConnector() != null) { + commandSliceTune(0, String.format("%.3f", getFreq() / 1000000f)); + } + } + + + @Override + public void onReceiveData(byte[] data) { + //ToastMessage.show("--"+byteToStr(data)); + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + //getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); + } + } + + @Override + public void sendWaveData(Ft8Message message) { + + if (getConnector() != null) { + float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() + , 24000);//flex音频的采样率是24000,todo 此处可改为动态设置24000,48000 + if (data == null) { + setPTT(false); + return; + } + getConnector().sendWaveData(data); + } + } + + @Override + public String getName() { + return "FlexRadio series"; + } + + + public String getFrequencyStr() { + return BaseRigOperation.getFrequencyStr(getFreq()); + } + + public FlexNetworkRig() { + Log.d(TAG, "FlexRadio: Create."); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java new file mode 100644 index 0000000..bba0124 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeQ900Rig.java @@ -0,0 +1,188 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * YAESU的部分电台,回送的数据不是连续的,所以,要做一个缓冲区,接受5字节长度。满了就复位。或发送指令时,就复位。 + */ +public class GuoHeQ900Rig extends BaseRig { + private static final String TAG = "GuoHeQ900Rig"; + private Timer readFreqTimer = new Timer(); + private byte[] buffer; + private int dataCount=-1; + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + readFreqFromRig(); + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + Log.d(TAG, "setPTT: " + on); + + if (getConnector() != null) { + getConnector().setPttOn(GuoHeRigConstant.setPTTState(on)); + } + } + + public synchronized void setPttOn(byte[] command) { + + getConnector().sendData(command);//以CAT指令发送PTT + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(GuoHeRigConstant.setOperationUSBMode());//USB模式 + //getConnector().sendData(GuoHeRigConstant.setOperationFT8Mode());//FT8模式 + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(GuoHeRigConstant.setOperationFreq(getFreq())); + } + } + + + private int checkHead(byte[] data) { + int count = 0; + for (int i = 0; i < data.length; i++) { + if (data[i] == (byte) 0xa5) { + count++; + if (count == 4) { + return i+1; + } + } + } + return -1; + } + + private void clearBuffer(){ + dataCount=-1; + buffer=null; + } + + @Override + public void onReceiveData(byte[] data) { + synchronized (this) { + try { + int startIndex = checkHead(data); + if (startIndex != -1) { + int len = data[startIndex]+1; + + buffer = new byte[len]; + dataCount = 0; + for (int i = startIndex ; i < data.length; i++) { + buffer[dataCount] = data[i]; + dataCount++; + if (dataCount == buffer.length) { + break; + } + } + } else { + if (buffer == null) { + return; + } + if (dataCount < buffer.length && dataCount > 0) { + for (int i = 0; i < data.length; i++) { + buffer[dataCount] = data[i]; + dataCount++; + if (dataCount == buffer.length) { + break; + } + } + } + } + + if (buffer.length == dataCount) {//说明已经收取全部指令 + byte[] crcData=new byte[buffer.length-2]; + for (int i = 0; i < crcData.length; i++) { + crcData[i]=buffer[i]; + } + //Log.e(TAG, "onReceiveData: crc data:"+byteToStr(crcData) ); + //Log.e(TAG, "onReceiveData: crc --->"+String.format("%x",CRC16.crc16(crcData)) ); + int crc=CRC16.crc16(crcData); + int ttt=((buffer[buffer.length-2]& 0xFF)<<8)|(buffer[buffer.length-1]&0xff); + //Log.e(TAG, "onReceiveData:数据内容:" + byteToStr(buffer)); + if (crc==ttt) {//crc校验成功 + + if (buffer[1] == (byte) 0x0b) {//是电台状态指令 + + long vfoa = ((buffer[5] & 0xFFL) << 24) | + ((buffer[6] & 0xFFL) << 16) | + ((buffer[7] & 0xFFL) << 8) | + ((buffer[8] & 0xFFL)); + long vfob = ((buffer[9] & 0xFFL) << 24) | + ((buffer[10] & 0xFFL) << 16) | + ((buffer[11] & 0xFFL) << 8) | + ((buffer[12] & 0xFFL)); + + if (buffer[13] == (byte) 0x00) { + setFreq(vfoa); + //Log.e(TAG, "onReceiveData: is VFO a"); + } else { + setFreq(vfob); + //Log.e(TAG, "onReceiveData: is VFO b"); + } + //Log.e(TAG, "onReceiveData: vfoa:" + vfoa); + //Log.e(TAG, "onReceiveData: vfob:" + vfob); + + } + } + clearBuffer(); + } + } catch (Exception e) { + Log.e(TAG, "onReceiveData: " + e.getMessage()); + } + } + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + getConnector().sendData(GuoHeRigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "GuoHe series"; + } + + public GuoHeQ900Rig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java new file mode 100644 index 0000000..d07a6f1 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/GuoHeRigConstant.java @@ -0,0 +1,112 @@ +package com.bg7yoz.ft8cn.rigs; + +public class GuoHeRigConstant { + private static final String TAG = "Yaesu2RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int USB = 0x00; + public static final int LSB = 0x01; + public static final int CW_R = 0x02; + public static final int CW_L = 0x03; + public static final int AM = 0x04; + public static final int WFM = 0x05; + public static final int NFM = 0x06; + public static final int DIGI = 0x07; + public static final int PKT = 0x08; + + //PTT状态 + + //指令集 +// private static final byte[] PTT_ON = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}; +// private static final byte[] PTT_OFF = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88}; + private static final byte[] PTT_ON = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x04, (byte) 0x07, (byte) 0x00, (byte) 0x89, (byte) 0xCB}; + private static final byte[] PTT_OFF = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x04, (byte) 0x07, (byte) 0x01, (byte) 0x99, (byte) 0xEA}; + private static final byte[] USB_MODE = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x05, (byte) 0x0A, (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0x44}; + private static final byte[] FT8_MODE = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x05, (byte) 0x0A, (byte) 0x08, (byte) 0x08, (byte) 0xF7, (byte) 0xE5}; + private static final byte[] READ_FREQ = {(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x03, (byte) 0x0B, (byte) 0xF9, (byte) 0x37}; +// private static final byte[] READ_FREQ = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03}; + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW_R: + return "CW_R"; + case CW_L: + return "CW_L"; + case AM: + return "AM"; + case WFM: + return "WFM"; + case NFM: + return "NFM"; + case DIGI: + return "DIGI"; + case PKT: + return "PKT"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON; + } else { + return PTT_OFF; + } + + } + + public static byte[] setOperationUSBMode() { + return USB_MODE; + } + public static byte[] setOperationFT8Mode() { + return FT8_MODE; + } + public static byte[] setOperationFreq(long freq) { + + byte[] data = new byte[]{(byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0xA5, (byte) 0x0b, (byte) 0x09 + , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00//频率VFOA + , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00//频率VFOB + , (byte) 0x3B, (byte) 0x6B}; + + data[6] = (byte) ((0x0000000000ff000000 & freq) >> 24); + data[7] = (byte) ((0x000000000000ff0000 & freq) >> 16); + data[8] = (byte) ((0x00000000000000ff00 & freq) >> 8); + data[9] = (byte) (0x0000000000000000ff & freq); + data[10] = (byte) ((0x0000000000ff000000 & freq) >> 24); + data[11] = (byte) ((0x000000000000ff0000 & freq) >> 16); + data[12] = (byte) ((0x00000000000000ff00 & freq) >> 8); + data[13] = (byte) (0x0000000000000000ff & freq); + + byte[] crcData=new byte[]{(byte)0x0b,(byte) 0x09 + ,(byte) ((0x0000000000ff000000 & freq) >> 24) + ,(byte) ((0x000000000000ff0000 & freq) >> 16) + ,(byte) ((0x00000000000000ff00 & freq) >> 8) + ,(byte) (0x0000000000000000ff & freq) + ,(byte) ((0x0000000000ff000000 & freq) >> 24) + ,(byte) ((0x000000000000ff0000 & freq) >> 16) + ,(byte) ((0x00000000000000ff00 & freq) >> 8) + ,(byte) (0x0000000000000000ff & freq)}; + + int crc=CRC16.crc16(crcData); + data[14]=(byte) ((crc&0x00ff00)>>8); + data[15]=(byte) (crc&0x0000ff); + + + + return data; + + } + + public static byte[] setReadOperationFreq() { + return READ_FREQ; + } + + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java new file mode 100644 index 0000000..dae403b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java @@ -0,0 +1,189 @@ +package com.bg7yoz.ft8cn.rigs; + + +import android.util.Log; + +public class IcomCommand { + private static final String TAG = "RigCommand"; + private byte[] rawData; + + /** + * 获取主命令 + * + * @return 主命令值 + */ + public int getCommandID() {//获取主命令 + if (rawData.length < 5) { + return -1; + } + return rawData[4]; + } + + /** + * 获取子命令,有的指令没有子命令,要注意。 + * + * @return 子命令 + */ + public int getSubCommand() {//获取子命令 + if (rawData.length < 7) { + return -1; + } + return rawData[5]; + } + + /** + * 获取带2字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。 + * @return 子指令 + */ + public int getSubCommand2() {//获取子命令 + if (rawData.length < 8) { + return -1; + } + return readShortData(rawData,6); + } + /** + * 获取带3字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。 + * @return 子指令 + */ + public int getSubCommand3() {//获取子命令 + if (rawData.length < 9) { + return -1; + } + return ((int) rawData[7] & 0xff) + | ((int) rawData[6] & 0xff) << 8 + | ((int) rawData[5] & 0xff) << 16; + + + } + + /** + * 获取数据区,有的指令有子命令,有的没有子命令,所以要区分出来。子命令占一个字节 + * + * @param hasSubCommand 是否有子命令 + * @return 返回数据区 + */ + public byte[] getData(boolean hasSubCommand) { + int pos; + + if (hasSubCommand) { + pos = 6; + } else { + pos = 5; + } + if (rawData.length < pos + 1) {//没有数据区了 + return null; + } + + byte[] data = new byte[rawData.length - pos]; + + for (int i = 0; i < rawData.length - pos; i++) { + data[i] = rawData[pos + i]; + } + return data; + } + + public byte[] getData2Sub() { + if (rawData.length < 9) {//没有数据区了 + return null; + } + + byte[] data = new byte[rawData.length - 8]; + + System.arraycopy(rawData, 8, data, 0, rawData.length - 8); + return data; + } + //解析接收的指令 + + /** + * 从串口中接到的数据解析出指令的数据:FE FE E0 A4 Cn Sc data FD + * + * @param ctrAddr 控制者地址,默认E0或00 + * @param rigAddr 电台地址,705默认是A4 + * @param buffer 从串口接收到的数据 + * @return 返回电台指令对象,如果不符合指令的格式,返回null。 + */ + public static IcomCommand getCommand(int ctrAddr, int rigAddr, byte[] buffer) { + Log.d(TAG, "getCommand: "+BaseRig.byteToStr(buffer) ); + if (buffer.length <= 5) {//指令的长度不可能小于等5 + return null; + } + int position = -1;//指令的位置 + for (int i = 0; i < buffer.length; i++) { + if (i + 6 > buffer.length) {//说明没找到指令 + return null; + } + if (buffer[i] == (byte) 0xfe + && buffer[i + 1] == (byte) 0xfe//命令头0xfe 0xfe + && (buffer[i + 2] == (byte) ctrAddr || buffer[i + 2] == (byte) 0x00)//控制者地址默认E0或00 + && buffer[i + 3] == (byte) rigAddr) {//电台地址,705的默认值是A4 + position = i; + break; + } + } + //说明没找到 + if (position == -1) { + return null; + } + + int dataEnd = -1; + //从命令头之后查起。所以i=position + for (int i = position; i < buffer.length; i++) { + if (buffer[i] == (byte) 0xfd) {//是否到结尾了 + dataEnd = i; + break; + } + } + if (dataEnd == -1) {//说明没找到结尾 + return null; + } + + IcomCommand icomCommand = new IcomCommand(); + icomCommand.rawData = new byte[dataEnd - position]; + int pos = 0; + for (int i = position; i < dataEnd; i++) {//把指令数据搬到rawData中 + //icomCommand.rawData[i] = buffer[i]; + icomCommand.rawData[pos] = buffer[i];//定位错误 + pos++; + } + return icomCommand; + } + + + /** + * 从数据区中计算频率BCD码 + * + * @param hasSubCommand 是否含有子命令 + * @return 返回频率值 + */ + public long getFrequency(boolean hasSubCommand) { + byte[] data = getData(hasSubCommand); + if (data.length < 5) { + return -1; + } + return (int) (data[0] & 0x0f)//取个位 1hz + + ((int) (data[0] >> 4) & 0xf) * 10//取十位 10hz + + (int) (data[1] & 0x0f) * 100//百位 100hz + + ((int) (data[1] >> 4) & 0xf) * 1000//千位 1khz + + (int) (data[2] & 0x0f) * 10000//万位 10khz + + ((int) (data[2] >> 4) & 0xf) * 100000//十万位 100khz + + (int) (data[3] & 0x0f) * 1000000//百万位 1Mhz + + ((int) (data[3] >> 4) & 0xf) * 10000000//千万位 10Mhz + + (int) (data[4] & 0x0f) * 100000000//亿位 100Mhz + + ((int) (data[4] >> 4) & 0xf) * 100000000;//十亿位 1Ghz + } + + + /** + * 把字节转换成short,不做小端转换!! + * + * @param data 字节数据 + * @return short + */ + public static short readShortData(byte[] data, int start) { + if (data.length - start < 2) return 0; + return (short) ((short) data[start + 1] & 0xff + | ((short) data[start] & 0xff) << 8); + } + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java new file mode 100644 index 0000000..129aea2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java @@ -0,0 +1,275 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; +import com.bg7yoz.ft8cn.icom.IComPacketTypes; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +public class IcomRig extends BaseRig { + private static final String TAG = "IcomRig"; + + private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 + private byte[] dataBuffer = new byte[0];//数据缓冲区 + private int alc = 0; + private int swr = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + private Timer meterTimer;//查询meter的Timer + //private boolean isPttOn = false; + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + //isPttOn = on; + alcMaxAlert = false; + swrAlert = false; + if (on) { + //修正连接方式0x03是wlan,01是usb,0x02是usb+mic,确保声音能发送到电台 + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x03)); + } else if (GeneralVariables.connectMode == ConnectMode.USB_CABLE) { + sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x01)); + } else { + sendCivData(IcomRigConstant.setConnectorDataMode(ctrAddress, getCivAddress(), (byte) 0x02)); + } + } + + if (getConnector() != null) { + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + getConnector().setPttOn(on); + return; + } + + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() + , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); + break; + //case ControlMode.NETWORK: + case ControlMode.RTS: + case ControlMode.DTR: + + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + //因为担心老的ICOM电台不一定支持USB-D,所以,先做个一USB模式,再进入USB-D模式, + // 这样,如果USB-D模式不支持,USB-D的指令就是无效的,电台就停留在USB模式下了 + //getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress + // , getCivAddress(), IcomRigConstant.USB));//usb + getConnector().sendData(IcomRigConstant.setOperationDataMode(ctrAddress + , getCivAddress(), IcomRigConstant.USB));//usb-d + } + } + + private void sendCivData(byte[] data) { + if (getConnector() != null) { + getConnector().sendData(data); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress + , getCivAddress(), getFreq())); + } + } + + /** + * 查找指令的结尾的位置,如果没找到,值是-1。 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandEnd(byte[] data) { + for (int i = 0; i < data.length; i++) { + if (data[i] == (byte) 0xFD) { + return i; + } + } + return -1; + } + + /** + * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandHead(byte[] data) { + if (data.length < 2) return -1; + for (int i = 0; i < data.length - 1; i++) { + if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { + return i; + } + } + return -1; + } + + @Override + public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式 + if (getConnector() != null) { + float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() + ,12000);//此处icom电台发射音频的采样率是12000,todo 此处可改为动态设置24000,48000 + if (data==null){ + setPTT(false); + return; + } + getConnector().sendWaveData(data); + } + } + + private void analysisCommand(byte[] data) { + int headIndex = getCommandHead(data); + if (headIndex == -1) {//说明没有指令头 + return; + } + IcomCommand icomCommand; + if (headIndex == 0) { + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); + } else { + byte[] temp = new byte[data.length - headIndex]; + System.arraycopy(data, headIndex, temp, 0, temp.length); + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); + } + if (icomCommand == null) { + return; + } + + //目前只对频率和模式消息作反应 + switch (icomCommand.getCommandID()) { + case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 + case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: + //获取频率 + setFreq(icomCommand.getFrequency(false)); + break; + case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 + case IcomRigConstant.CMD_READ_OPERATING_MODE: + break; + case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 + if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_ALC) { + alc = IcomRigConstant.twoByteBcdToInt(icomCommand.getData(true)); + } + if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { + swr = IcomRigConstant.twoByteBcdToInt(icomCommand.getData(true)); + } + showAlert();//检查meter值是否在告警范围 + break; + case IcomRigConstant.CMD_CONNECTORS: + break; + + } + } + + private void showAlert() { + if (swr >= IcomRigConstant.swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > IcomRigConstant.alc_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + + @Override + public void onReceiveData(byte[] data) { + + //ToastMessage.show("--"+byteToStr(data)); + + + int commandEnd = getCommandEnd(data); + if (commandEnd <= -1) {//这是没有指令结尾 + byte[] temp = new byte[dataBuffer.length + data.length]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + System.arraycopy(data, 0, temp, dataBuffer.length, data.length); + dataBuffer = temp; + } else { + byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + dataBuffer = temp; + System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); + } + if (commandEnd != -1) { + analysisCommand(dataBuffer); + } + dataBuffer = new byte[0];//清空缓冲区 + if (commandEnd <= -1 || commandEnd < data.length) { + byte[] temp = new byte[data.length - commandEnd + 1]; + for (int i = 0; i < data.length - commandEnd - 1; i++) { + temp[i] = data[commandEnd + i + 1]; + } + dataBuffer = temp; + } + + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); + } + } + + @Override + public String getName() { + return "ICOM series"; + } + + public void startMeterTimer() { + meterTimer = new Timer(); + meterTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (isPttOn()) {//当Ptt被按下去的时候测量 + sendCivData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); + sendCivData(IcomRigConstant.getALCState(ctrAddress, getCivAddress())); + } + } + }, 0, IComPacketTypes.METER_TIMER_PERIOD_MS); + } + + + public String getFrequencyStr() { + return BaseRigOperation.getFrequencyStr(getFreq()); + } + + public IcomRig(int civAddress) { + Log.d(TAG, "IcomRig: Create."); + setCivAddress(civAddress); + startMeterTimer(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java new file mode 100644 index 0000000..5f781e0 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRigConstant.java @@ -0,0 +1,280 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.util.Log; + +public class IcomRigConstant { + private static final String TAG = "IcomRigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0; + public static final int USB = 1; + public static final int AM = 2; + public static final int CW = 3; + public static final int RTTY = 4; + public static final int FM = 5; + public static final int WFM = 6; + public static final int CW_R = 7; + public static final int RTTY_R = 8; + public static final int DV = 0x17; + public static final int UNKNOWN = -1; + + + public static final int swr_alert_max=120;//相当于3.0 + public static final int alc_alert_max=120;//超过,在表上显示红色 + + + + //PTT状态 + public static final int PTT_ON = 1; + public static final int PTT_OFF = 0; + + //指令集 + public static final byte CMD_RESULT_OK = (byte) 0xfb;// + public static final byte CMD_RESULT_FAILED = (byte) 0xfa;// + + public static final byte[] SEND_FREQUENCY_DATA = {0x00};//发送频率数据 + public static final byte CMD_SEND_FREQUENCY_DATA = 0x00;//发送频率数据 + + public static final byte[] SEND_MODE_DATA = {0x01};//发送模式数据 + public static final byte CMD_SEND_MODE_DATA = 0x01;//发送模式数据 + + public static final byte[] READ_BAND_EDGE_DATA = {0x02};//读频率的波段边界 + public static final byte CMD_READ_BAND_EDGE_DATA = 0x02;//读频率的波段边界 + + public static final byte[] READ_OPERATING_FREQUENCY = {0x03};//发送模式数据 + public static final byte CMD_READ_OPERATING_FREQUENCY = 0x03;//发送模式数据 + + public static final byte[] READ_OPERATING_MODE = {0x04};//读取操作模式 + public static final byte CMD_READ_OPERATING_MODE = 0x04;//读取操作模式 + + public static final byte[] SET_OPERATING_FREQUENCY = {0x05};//设置操作的频率 + public static final byte CMD_SET_OPERATING_FREQUENCY = 0x05;//设置操作的频率 + + public static final byte[] SET_OPERATING_MODE = {0x06};//设置操作的模式 + public static final byte CMD_SET_OPERATING_MODE = 0x06;//设置操作的模式 + + public static final byte CMD_READ_METER = 0x15;//读meter + public static final byte CMD_READ_METER_SWR = 0x12;//读meter子命令,驻波表 + public static final byte CMD_READ_METER_ALC = 0x13;//读meter子命令,ALC表 + public static final byte CMD_CONNECTORS = 0x1A;//Connector设置,读取 + public static final byte CMD_CONNECTORS_DATA_MODE = 0x05;//Connector设置,读取 + public static final int CMD_CONNECTORS_DATA_WLAN_LEVEL = 0x050117;//Connector设置,读取 + + + + + + public static final byte CMD_COMMENT_1A = 0x1A;//1A指令 + public static final byte[] SET_READ_PTT_STATE = {0x1A, 0x00, 0x48};//读取或设置PTT状态,不建议使用 + + public static final byte[] READ_TRANSCEIVER_STATE = {0x1A, 0x00, 0x48};//读取电台发射状态 + public static final byte[] SET_TRANSCEIVER_STATE_ON = {0x1C, 0x00, 0x01};//设置电台处于发射状态TX + public static final byte[] SET_TRANSCEIVER_STATE_OFF = {0x1C, 0x00, 0x00};//设置电台关闭发射状态RX + public static final byte[] READ_TRANSMIT_FREQUENCY = {0x1C, 0x03};//读取电台发射时的频率 + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case AM: + return "AM"; + case CW: + return "CW"; + case RTTY: + return "RTTY"; + case FM: + return "FM"; + case CW_R: + return "CW_R"; + case RTTY_R: + return "RTTY_R"; + case DV: + return "DV"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(int ctrAddr, int rigAddr, int state) { + //1C指令,例如PTT ON:FE FE A1 E0 1C 00 01 FD + byte[] data = new byte[8]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) 0x1c;//主指令代码 + data[5] = (byte) 0x00;//子指令代码 + data[6] = (byte) state;//状态 01=tx 00=rx + data[7] = (byte) 0xfd; + return data; + } + + /** + * 读驻波表 + * + * @param ctrAddr 我的地址 + * @param rigAddr 电台地址 + * @return 指令数据包 + */ + public static byte[] getSWRState(int ctrAddr, int rigAddr) { + //1C指令,例如PTT ON:FE FE A1 E0 15 12 FD + byte[] data = new byte[7]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) CMD_READ_METER;//主指令代码 + data[5] = (byte) CMD_READ_METER_SWR;//子指令代码SWR + data[6] = (byte) 0xfd; + return data; + } + + /** + * 读ALC表 + * + * @param ctrAddr 我的地址 + * @param rigAddr 电台地址 + * @return 指令数据包 + */ + public static byte[] getALCState(int ctrAddr, int rigAddr) { + //1C指令,例如PTT ON:FE FE A1 E0 15 12 FD + byte[] data = new byte[7]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) CMD_READ_METER;//主指令代码 + data[5] = (byte) CMD_READ_METER_ALC;//子指令代码ALC + data[6] = (byte) 0xfd; + return data; + } + + public static byte[] getConnectorWLanLevel(int ctrAddr, int rigAddr){ + //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 17 FD + byte[] data = new byte[9]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) CMD_CONNECTORS;//主指令代码1A + data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//WLan level + data[6] = (byte) 0x01; + data[7] = (byte) 0x17; + data[8] = (byte) 0xfd; + return data; + } + + public static byte[] setConnectorWLanLevel(int ctrAddr, int rigAddr,int level){ + //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 17 FD + byte[] data = new byte[11]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) CMD_CONNECTORS;//主指令代码1A + data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//子指令代码ALC + data[6] = (byte) 0x01; + data[7] = (byte) 0x17; + data[8] = (byte) (level >> 8 & 0xff); + data[9] = (byte) (level &0xff); + data[10] = (byte) 0xfd; + return data; + } + + //设置数据通讯方式 + public static byte[] setConnectorDataMode(int ctrAddr, int rigAddr,byte mode){ + //1A指令,例如DATA MODE=WLAN:FE FE A1 E0 1A 05 01 19 FD + byte[] data = new byte[10]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) CMD_CONNECTORS;//主指令代码1A + data[5] = (byte) CMD_CONNECTORS_DATA_MODE;//子指令代码ALC + data[6] = (byte) 0x01;// + data[7] = (byte) 0x19;// + data[8] = (byte) mode;//数据连接的方式 + data[9] = (byte) 0xfd; + return data; + } + public static byte[] setOperationMode(int ctrAddr, int rigAddr, int mode) { + //06指令,例如USB=01:FE FE A1 E0 06 01 FD + byte[] data = new byte[8]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) 0x06;//指令代码 + data[5] = (byte) mode;//USB=01 + data[6] = (byte) 0x01;//fil1 + data[7] = (byte) 0xfd; + return data; + } + + public static byte[] setOperationDataMode(int ctrAddr, int rigAddr, int mode) { + //26指令,例如USB-D=01:FE FE A1 E0 26 01 01 01 FD + byte[] data = new byte[10]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr;//70 + data[3] = (byte) ctrAddr;//E0 + data[4] = (byte) 0x26;//指令代码 + data[5] = (byte) 0x00;//指令代码 + data[6] = (byte) mode;//USB=01 + data[7] = (byte) 0x01;//data模式 + data[8] = (byte) 0x01;//fil1 + data[9] = (byte) 0xfd; + return data; + } + + public static byte[] setReadFreq(int ctrAddr, int rigAddr) { + //06指令,例如USB=01:FE FE A1 E0 06 01 FD + byte[] data = new byte[6]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) 0x03;//指令代码 + data[5] = (byte) 0xfd; + return data; + } + + + public static byte[] setOperationFrequency(int ctrAddr, int rigAddr, long freq) { + //05指令,例如14.074M:FE FE A4 E0 05 00 40 07 14 00 FD + byte[] data = new byte[11]; + data[0] = (byte) 0xfe; + data[1] = (byte) 0xfe; + data[2] = (byte) rigAddr; + data[3] = (byte) ctrAddr; + data[4] = (byte) 0x05;//指令代码 + data[5] = (byte) (((byte) (freq % 100 / 10) << 4) + (byte) (freq % 10)); + data[6] = (byte) (((byte) (freq % 10000 / 1000) << 4) + (byte) (freq % 1000 / 100)); + data[7] = (byte) (((byte) (freq % 1000000 / 100000) << 4) + (byte) (freq % 100000 / 10000)); + data[8] = (byte) (((byte) (freq % 100000000 / 10000000) << 4) + (byte) (freq % 10000000 / 1000000)); + data[9] = (byte) (((byte) (freq / 1000000000) << 4) + (byte) (freq % 1000000000 / 100000000)); + data[10] = (byte) 0xfd; + + Log.d(TAG, "setOperationFrequency: " + BaseRig.byteToStr(data)); + return data; + } + + public static int twoByteBcdToInt(byte[] data) { + if (data.length < 2) return 0; + return (int) (data[1] & 0x0f)//取个位 + + ((int) (data[1] >> 4) & 0xf) * 10//取十位 + + (int) (data[0] & 0x0f) * 100//百位 + + ((int) (data[0] >> 4) & 0xf) * 1000;//千位 + + } + public static int twoByteBcdToIntBigEnd(byte[] data) { + if (data.length < 2) return 0; + return (int) (data[0] & 0x0f)//取个位 + + ((int) (data[0] >> 4) & 0xf) * 10//取十位 + + (int) (data[1] & 0x0f) * 100//百位 + + ((int) (data[1] >> 4) & 0xf) * 1000;//千位 + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java new file mode 100644 index 0000000..46ad6b2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java @@ -0,0 +1,22 @@ +package com.bg7yoz.ft8cn.rigs; + +public class InstructionSet { + public static final int ICOM=0; + public static final int YAESU_2=1;//5字节数据 817 + public static final int YAESU_3_9=2;//9字节频率 + public static final int YAESU_3_8=3;//8字节频率 + public static final int YAESU_3_450=4;//PTT发射不通 + public static final int KENWOOD_TK90=5;//11字节频率,VFO模式下才可用 + public static final int YAESU_DX10=6;//9字节,DATA-U模式 + public static final int KENWOOD_TS590=7;//11字节频率,VFO模式 + public static final int GUOHE_Q900=8;//国赫Q900,5字节一次性接收,与YAESU2代不同 + public static final int XIEGUG90S=9;//协谷G90S,指令与ICOM兼容,但不会主动发送频率的变化,需要定期获取 + public static final int ELECRAFT=10;//Elecraft k3 + public static final int FLEX_CABLE=11;//FLEX6000; + public static final int FLEX_NETWORK=12;//FLEX网络模式连接; + public static final int XIEGU_6100=13;//协谷X6100; + public static final int KENWOOD_TS2000=14;//建武TS2000,发射指令是TX0; + public static final int WOLF_SDR_DIGU=15;//UA3REO Wolf SDR,DIG-U模式,兼容YAESU 450D; + public static final int WOLF_SDR_USB=16;//UA3REO Wolf SDR,USB模式,兼容YAESU 450D; + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java new file mode 100644 index 0000000..d044da8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodKT90Rig.java @@ -0,0 +1,144 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.os.Handler; +import android.util.Log; + +import com.bg7yoz.ft8cn.database.ControlMode; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 + */ +public class KenwoodKT90Rig extends BaseRig { + private static final String TAG = "KenwoodKT90Rig"; + private final StringBuilder buffer = new StringBuilder(); + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + readFreqFromRig(); + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(KenwoodTK90RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setOperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains("\r")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + //说明数据还没接收完。 + }else { + if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf("\r"))); + } + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf("\r")+1)); + + if (yaesu3Command == null) { + return; + } + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA")) { + long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "KENWOOD TK90"; + } + + public KenwoodKT90Rig() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (getConnector()!=null){ + getConnector().sendData(KenwoodTK90RigConstant.setVFOMode()); + } + } + },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java new file mode 100644 index 0000000..624488b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTK90RigConstant.java @@ -0,0 +1,135 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; + +public class KenwoodTK90RigConstant { + private static final String TAG = "KenwoodTK90RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x01; + public static final int USB = 0x02; + public static final int CW = 0x03; + public static final int FSK = 0x04; + public static final int AM = 0x05; + public static final int DATA = 0x06; + + public static final int ts_590_swr_alert_max=15;//相当于3.0 + public static final int ts_590_alc_alert_max=15;//超过,在表上显示红色 + + //PTT状态 + + //指令集 + private static final String PTT_ON = "TX\r"; + private static final String PTT_OFF = "RX\r"; + private static final String USB_MODE = "MD2\r"; + private static final String READ_FREQ = "FA\r"; + private static final String READ_METERS = "RM\r"; + private static final String SET_VFO = "FR0\r"; + + + + private static final String TS590_VFO_A="FR0;";//KENWOOD TS590,设置VFO -A + private static final String TS2000_PTT_ON="TX0;";//KENWOOD TS2000,PTT + private static final String TS590_PTT_ON="TX1;";//KENWOOD TS590,PTT + private static final String FLEX_6000_PTT_ON="TX01;";//FLEX_6000,PTT + private static final String TS590_PTT_OFF="RX;";//KENWOOD TS590,PTT + private static final String FLEX_SET_USB_DATA="MD9;";//FLEX6000 DIGU + private static final String TS590_SET_USB="MD2;";//KENWOOD USB MODE + private static final String TS590_READ_FREQ = "FA;";//KENWOOD 读频率 + private static final String TS590_READ_METERS = "RM;";//KENWOOD 读METER + + + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW: + return "CW"; + case FSK: + return "FSK"; + case AM: + return "AM"; + case DATA: + return "DATA"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + + public static byte[] setTS590PTTState(boolean on) { + if (on) { + return TS590_PTT_ON.getBytes(); + } else { + return TS590_PTT_OFF.getBytes(); + } + + } public static byte[] setTS2000PTTState(boolean on) { + if (on) { + return TS2000_PTT_ON.getBytes(); + } else { + return TS590_PTT_OFF.getBytes(); + } + + } + public static byte[] setFLEX6000PTTState(boolean on) { + if (on) { + return FLEX_6000_PTT_ON.getBytes(); + } else { + return TS590_PTT_OFF.getBytes(); + } + + } + + + //设置成VFO模式 + public static byte[] setVFOMode(){ + return SET_VFO.getBytes(); + } + public static byte[] setTS590VFOMode(){ + return TS590_VFO_A.getBytes(); + } + + public static byte[] setOperationUSBMode() { + return USB_MODE.getBytes(); + } + public static byte[] setTS590OperationUSBMode() { + return TS590_SET_USB.getBytes(); + } + public static byte[] setFLEX6000OperationUSBMode() { + return FLEX_SET_USB_DATA.getBytes(); + } + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq(long freq) { + return String.format("FA%011d\r",freq).getBytes(); + } + @SuppressLint("DefaultLocale") + public static byte[] setTS590OperationFreq(long freq) { + return String.format("FA%011d;",freq).getBytes(); + } + + public static byte[] setReadOperationFreq(){ + return READ_FREQ.getBytes(); + } + public static byte[] setRead590Meters(){ + return TS590_READ_METERS.getBytes(); + } + + public static byte[] setTS590ReadOperationFreq(){ + return TS590_READ_FREQ.getBytes(); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java new file mode 100644 index 0000000..848884c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS2000Rig.java @@ -0,0 +1,194 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.os.Handler; +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * KENWOOD TS2000,与YAESU3代指令接近,与TS590基本相同,唯一不同的是,PTT打开指令是TX0;而TS590是用的TX1; + * 命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 + */ +public class KenwoodTS2000Rig extends BaseRig { + private static final String TAG = "KenwoodTS590Rig"; + private final StringBuilder buffer = new StringBuilder(); + + private Timer readFreqTimer = new Timer(); + private int swr=0; + private int alc=0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()){ + readMeters();//读METER + }else { + readFreqFromRig();//读频率 + } + + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 读取Meter RM; + */ + private void readMeters(){ + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setRead590Meters()); + } + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(KenwoodTK90RigConstant.setTS2000PTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains("\r")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + //return;//说明数据还没接收完。 + }else { + if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf("\r"))); + } + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf("\r")+1)); + + if (yaesu3Command == null) { + return; + } + String cmd=yaesu3Command.getCommandID(); + if (cmd.equalsIgnoreCase("FA")) {//频率 + long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + }else if (cmd.equalsIgnoreCase("RM")){//meter + if (Yaesu3Command.is590MeterSWR(yaesu3Command)) { + swr = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + if (Yaesu3Command.is590MeterALC(yaesu3Command)) { + alc = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + showAlert(); + } + + } + + } + private void showAlert() { + if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq()); + } + } + + @Override + public String getName() { + return "KENWOOD TS-2000"; + } + + public KenwoodTS2000Rig() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (getConnector()!=null){ + getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode()); + } + } + },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java new file mode 100644 index 0000000..fb59880 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java @@ -0,0 +1,193 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.os.Handler; +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * KENWOOD TS590,与YAESU3代指令接近,命令结构使用Yaesu3Command,指令在KenwoodTK90RigConstant中。 + */ +public class KenwoodTS590Rig extends BaseRig { + private static final String TAG = "KenwoodTS590Rig"; + private final StringBuilder buffer = new StringBuilder(); + + private Timer readFreqTimer = new Timer(); + private int swr=0; + private int alc=0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()){ + readMeters();//读METER + }else { + readFreqFromRig();//读频率 + } + + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 读取Meter RM; + */ + private void readMeters(){ + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setRead590Meters()); + } + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(KenwoodTK90RigConstant.setTS590PTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains("\r")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + //return;//说明数据还没接收完。 + }else { + if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf("\r"))); + } + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf("\r")+1)); + + if (yaesu3Command == null) { + return; + } + String cmd=yaesu3Command.getCommandID(); + if (cmd.equalsIgnoreCase("FA")) {//频率 + long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + }else if (cmd.equalsIgnoreCase("RM")){//meter + if (Yaesu3Command.is590MeterSWR(yaesu3Command)) { + swr = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + if (Yaesu3Command.is590MeterALC(yaesu3Command)) { + alc = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + showAlert(); + } + + } + + } + private void showAlert() { + if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq()); + } + } + + @Override + public String getName() { + return "KENWOOD TS-480/590"; + } + + public KenwoodTS590Rig() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (getConnector()!=null){ + getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode()); + } + } + },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java new file mode 100644 index 0000000..3fc4542 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.rigs; + +/** + * 从电台接收到数据的回调 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnConnectReceiveData { + void onData(byte[] data); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java new file mode 100644 index 0000000..4e638cf --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java @@ -0,0 +1,14 @@ +package com.bg7yoz.ft8cn.rigs; + +/** + * 电台状态的回调。 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnRigStateChanged { + void onDisconnected(); + void onConnected(); + void onPttChanged(boolean isOn); + void onFreqChanged(long freq); + void onRunError(String message); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java new file mode 100644 index 0000000..924967a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Wolf_sdr_450Rig.java @@ -0,0 +1,201 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * wolf 的cat指令集兼容yaesu 450d,但是有的ham在实际测试中发现,450d默认是dig-u,此模式在wolf上测试无法满功率发射, + * 而采用usb模式就可以满功率发射,故增加一个usb模式的选项 + * 在创建rig时,用布尔参数是否时USB模式 + */ +public class Wolf_sdr_450Rig extends BaseRig { + private static final String TAG = "Wolf_sdr_450Rig"; + private final StringBuilder buffer = new StringBuilder(); + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + private boolean isUsbMode=true; + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); + } + } + + private void showAlert() { + if (swr >= Yaesu3RigConstant.swr_39_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu3RigConstant.setPTT_TX_On(on));//针对YAESU 450指令 + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + //getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); + //getConnector().sendData(Yaesu3RigConstant.setOperationUSB_Data_Mode()); + if (isUsbMode) {//usb模式 + getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); + }else {//dig-u模式 + getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); + } + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + //ToastMessage.showDebug("39 YAESU 读数据:"+new String(Yaesu3RigConstant.setReadOperationFreq())); + + if (!s.contains(";")) { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + //return;//说明数据还没接收完。 + } else { + if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0, s.indexOf(";"))); + } + + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";") + 1)); + + if (yaesu3Command == null) { + return; + } + //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + //} + + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER + if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { + swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + if (Yaesu3Command.isALCMeter38(yaesu3Command)) { + alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + showAlert(); + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "WOLF SDR"; + } + + public Wolf_sdr_450Rig(boolean usbMode) { + isUsbMode=usbMode; + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java new file mode 100644 index 0000000..232e495 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java @@ -0,0 +1,242 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +public class XieGu6100Rig extends BaseRig { + private static final String TAG = "IcomRig"; + + private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 + private byte[] dataBuffer = new byte[0];//数据缓冲区 + private int swr = 0; + private boolean swrAlert = false; + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()){ + readSWRMeter(); + }else { + readFreqFromRig(); + } + + } catch (Exception e) { + Log.e(TAG, "readFreq or meter error:" + e.getMessage()); + } + } + }; + } + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() + , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { +// getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress +// , getCivAddress(), 1));//usb=1 + getConnector().sendData(IcomRigConstant.setOperationDataMode(ctrAddress + , getCivAddress(), IcomRigConstant.USB));//usb-d + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress + , getCivAddress(), getFreq())); + } + } + + /** + * 查找指令的结尾的位置,如果没找到,值是-1。 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandEnd(byte[] data) { + for (int i = 0; i < data.length; i++) { + if (data[i] == (byte) 0xFD) { + return i; + } + } + return -1; + } + + /** + * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandHead(byte[] data) { + if (data.length < 2) return -1; + for (int i = 0; i < data.length - 1; i++) { + if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { + return i; + } + } + return -1; + } + + private void analysisCommand(byte[] data) { + int headIndex = getCommandHead(data); + if (headIndex == -1) {//说明没有指令头 + return; + } + IcomCommand icomCommand; + if (headIndex == 0) { + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); + } else { + byte[] temp = new byte[data.length - headIndex]; + System.arraycopy(data, headIndex, temp, 0, temp.length); + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); + } + if (icomCommand == null) { + return; + } + + //目前只对频率和模式消息作反应 + switch (icomCommand.getCommandID()) { + case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 + case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: + //获取频率 + long freqTemp = icomCommand.getFrequency(false); + if (freqTemp >= 500000 && freqTemp <= 250000000) {//协谷的频率范围 + setFreq(freqTemp); + } + break; + case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 + case IcomRigConstant.CMD_READ_OPERATING_MODE: + break; + case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 + if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { + //协谷的小端模式 + int temp=IcomRigConstant.twoByteBcdToIntBigEnd(icomCommand.getData(true)); + if (temp!=255) { + swr = temp;// + } + } + showAlert();//检查meter值是否在告警范围 + + break; + } + } + + + private void showAlert() { + if (swr >= IcomRigConstant.swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + } + + + + @Override + public void onReceiveData(byte[] data) { + int commandEnd = getCommandEnd(data); + if (commandEnd <= -1) {//这是没有指令结尾 + byte[] temp = new byte[dataBuffer.length + data.length]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + System.arraycopy(data, 0, temp, dataBuffer.length, data.length); + dataBuffer = temp; + } else { + byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + dataBuffer = temp; + System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); + } + if (commandEnd != -1) { + analysisCommand(dataBuffer); + } + dataBuffer = new byte[0];//清空缓冲区 + if (commandEnd <= -1 || commandEnd < data.length) { + byte[] temp = new byte[data.length - commandEnd + 1]; + for (int i = 0; i < data.length - commandEnd - 1; i++) { + temp[i] = data[commandEnd + i + 1]; + } + dataBuffer = temp; + } + + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); + } + } + + private void readSWRMeter() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); + } + } + + @Override + public String getName() { + return "XIEGU X6100 series"; + } + + + public String getFrequencyStr() { + return BaseRigOperation.getFrequencyStr(getFreq()); + } + + public XieGu6100Rig(int civAddress) { + Log.d(TAG, "XieGuRig: Create."); + setCivAddress(civAddress); + + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); + //readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,1000); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java new file mode 100644 index 0000000..d1200d4 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java @@ -0,0 +1,240 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +public class XieGuRig extends BaseRig { + private static final String TAG = "IcomRig"; + + private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 + private byte[] dataBuffer = new byte[0];//数据缓冲区 + private int swr = 0; + private boolean swrAlert = false; + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()){ + readSWRMeter(); + }else { + readFreqFromRig(); + } + + } catch (Exception e) { + Log.e(TAG, "readFreq or meter error:" + e.getMessage()); + } + } + }; + } + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress() + , on ? IcomRigConstant.PTT_ON : IcomRigConstant.PTT_OFF)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setOperationMode(ctrAddress + , getCivAddress(), 1));//usb=1 + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setOperationFrequency(ctrAddress + , getCivAddress(), getFreq())); + } + } + + /** + * 查找指令的结尾的位置,如果没找到,值是-1。 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandEnd(byte[] data) { + for (int i = 0; i < data.length; i++) { + if (data[i] == (byte) 0xFD) { + return i; + } + } + return -1; + } + + /** + * 查找指令头,没找到返回-1,找到返回FE FE的第一个位置 + * + * @param data 数据 + * @return 位置 + */ + private int getCommandHead(byte[] data) { + if (data.length < 2) return -1; + for (int i = 0; i < data.length - 1; i++) { + if (data[i] == (byte) 0xFE && data[i + 1] == (byte) 0xFE) { + return i; + } + } + return -1; + } + + private void analysisCommand(byte[] data) { + int headIndex = getCommandHead(data); + if (headIndex == -1) {//说明没有指令头 + return; + } + IcomCommand icomCommand; + if (headIndex == 0) { + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data); + } else { + byte[] temp = new byte[data.length - headIndex]; + System.arraycopy(data, headIndex, temp, 0, temp.length); + icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), temp); + } + if (icomCommand == null) { + return; + } + + //目前只对频率和模式消息作反应 + switch (icomCommand.getCommandID()) { + case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 + case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: + //获取频率 + long freqTemp = icomCommand.getFrequency(false); + if (freqTemp >= 500000 && freqTemp <= 250000000) {//协谷的频率范围 + setFreq(freqTemp); + } + break; + case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 + case IcomRigConstant.CMD_READ_OPERATING_MODE: + break; + case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现 + if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) { + //协谷的小端模式 + int temp=IcomRigConstant.twoByteBcdToIntBigEnd(icomCommand.getData(true)); + if (temp!=255) { + swr = temp;// + } + } + showAlert();//检查meter值是否在告警范围 + + break; + } + } + + + private void showAlert() { + if (swr >= IcomRigConstant.swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + } + + + + @Override + public void onReceiveData(byte[] data) { + int commandEnd = getCommandEnd(data); + if (commandEnd <= -1) {//这是没有指令结尾 + byte[] temp = new byte[dataBuffer.length + data.length]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + System.arraycopy(data, 0, temp, dataBuffer.length, data.length); + dataBuffer = temp; + } else { + byte[] temp = new byte[dataBuffer.length + commandEnd + 1]; + System.arraycopy(dataBuffer, 0, temp, 0, dataBuffer.length); + dataBuffer = temp; + System.arraycopy(data, 0, dataBuffer, dataBuffer.length - commandEnd - 1, commandEnd + 1); + } + if (commandEnd != -1) { + analysisCommand(dataBuffer); + } + dataBuffer = new byte[0];//清空缓冲区 + if (commandEnd <= -1 || commandEnd < data.length) { + byte[] temp = new byte[data.length - commandEnd + 1]; + for (int i = 0; i < data.length - commandEnd - 1; i++) { + temp[i] = data[commandEnd + i + 1]; + } + dataBuffer = temp; + } + + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress())); + } + } + + private void readSWRMeter() { + if (getConnector() != null) { + getConnector().sendData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); + } + } + + @Override + public String getName() { + return "XIEGU series"; + } + + + public String getFrequencyStr() { + return BaseRigOperation.getFrequencyStr(getFreq()); + } + + public XieGuRig(int civAddress) { + Log.d(TAG, "XieGuRig: Create."); + setCivAddress(civAddress); + + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); + //readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,1000); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java new file mode 100644 index 0000000..d8e18a4 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java @@ -0,0 +1,22 @@ +package com.bg7yoz.ft8cn.rigs; + +public class Yaesu2Command { + private static final String TAG="Yaesu 2 Command"; + + public static long getFrequency(byte[] rawData){ + if (rawData.length==5){ + return ((int) (rawData[0] >> 4) & 0xf) * 100000000 + +(int) (rawData[0] & 0x0f) * 10000000 + +((int) (rawData[1] >> 4) & 0xf) * 1000000 + +(int) (rawData[1] & 0x0f) * 100000 + +((int) (rawData[2] >> 4) & 0xf) * 10000 + +(int) (rawData[2] & 0x0f) * 1000 + +((int) (rawData[3] >> 4) & 0xf) * 10000 + +(int) (rawData[3] & 0x0f) * 1000; + }else { + return -1; + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java new file mode 100644 index 0000000..e54c6c6 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java @@ -0,0 +1,156 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * YAESU的部分电台,回送的数据不是连续的,所以,要做一个缓冲区,接受5字节长度。满了就复位。或发送指令时,就复位。 + */ +public class Yaesu2Rig extends BaseRig{ + private static final String TAG="Yaesu2Rig"; + private Timer readFreqTimer = new Timer(); + + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private TimerTask readTask(){ + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()){ + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer=null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + }catch (Exception e) + { + Log.e(TAG, "readFreq error:"+e.getMessage() ); + } + } + }; + } + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + + if (getConnector()!=null){ + switch (getControlMode()){ + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu2RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + + + + @Override + public boolean isConnected() { + if (getConnector()==null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector()!=null){ + getConnector().sendData(Yaesu2RigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector()!=null){ + getConnector().sendData(Yaesu2RigConstant.setOperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + //YAESU 817的指令,返回频率是5字节的,METER是2字节的。 + //Meter是2字节的,第一字节高位功率,0-A,低位ALC 0-9,第二字节高位驻波比,0-C,0为高驻波,低位音频输入0-8 + if (data.length == 5) {//频率 + long freq = Yaesu2Command.getFrequency(data); + if (freq > -1) { + setFreq(freq); + } + } else if (data.length == 2) {//METERS + alc = (data[0] & 0x0f); + swr = (data[1] & 0x0f0) >> 4; + showAlert(); + } + + } + + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + getConnector().sendData(Yaesu2RigConstant.readMeter()); + } + } + private void showAlert() { + if (swr > Yaesu2RigConstant.swr_817_alert_min) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc >= Yaesu2RigConstant.alc_817_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + @Override + public void readFreqFromRig(){ + if (getConnector()!=null){ + //clearBuffer();//清除一下缓冲区 + getConnector().sendData(Yaesu2RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU 817 series"; + } + + public Yaesu2Rig() { + readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java new file mode 100644 index 0000000..15de904 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java @@ -0,0 +1,84 @@ +package com.bg7yoz.ft8cn.rigs; + +public class Yaesu2RigConstant { + private static final String TAG = "Yaesu2RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x00; + public static final int USB = 0x01; + public static final int CW = 0x02; + public static final int CW_R = 0x03; + public static final int AM = 0x04; + public static final int FM = 0x05; + public static final int DIG = 0x0A; + public static final int PKT = 0x0C; + public static final int swr_817_alert_min=6;//相当于3.0, + public static final int alc_817_alert_max=7;//取值时0-9,合适值是7 + //PTT状态 + + //指令集 + private static final byte[] PTT_ON = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}; + private static final byte[] PTT_OFF = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88}; + private static final byte[] GET_METER = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xBD}; + //USB模式 + private static final byte[] USB_MODE = {(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x07}; + //DIG模式 + private static final byte[] DIG_MODE = {(byte) 0x0A, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x07}; + private static final byte[] READ_FREQ = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03}; + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case AM: + return "AM"; + case CW: + return "CW"; + case FM: + return "FM"; + case CW_R: + return "CW_R"; + case DIG: + return "DIG"; + case PKT: + return "PKT"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON; + } else { + return PTT_OFF; + } + + } + public static byte[] setOperationUSBMode() { + return DIG_MODE; + } + + public static byte[] readMeter() { + return GET_METER; + } + + public static byte[] setOperationFreq(long freq) { + byte[] data = new byte[]{ + (byte) (((byte) (freq % 1000000000 / 100000000) << 4) + (byte) (freq % 100000000 / 10000000)) + , (byte) (((byte) (freq % 10000000 / 1000000) << 4) + (byte) (freq % 1000000 / 100000)) + , (byte) (((byte) (freq % 100000 / 10000) << 4) + (byte) (freq % 10000 / 1000)) + , (byte) (((byte) (freq % 1000 / 100) << 4) + (byte) (freq % 100)) + ,(byte) 0x01 + }; + return data; + } + public static byte[] setReadOperationFreq(){ + return READ_FREQ; + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java new file mode 100644 index 0000000..20a4bbf --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java @@ -0,0 +1,190 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +public class Yaesu38Rig extends BaseRig { + private static final String TAG = "Yaesu3Rig"; + private final StringBuilder buffer = new StringBuilder(); + + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); + } + } + + private void showAlert() { + if (swr >= Yaesu3RigConstant.swr_39_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + if (!s.contains(";")) { + buffer.append(s); + if (buffer.length() > 1000) clearBufferData(); + return;//说明数据还没接收完。 + } else { + if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0, s.indexOf(";"))); + } + + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";") + 1)); + + if (yaesu3Command == null) { + return; + } + //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + //} + + + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER + if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { + swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + if (Yaesu3Command.isALCMeter38(yaesu3Command)) { + alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + showAlert(); + } + + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU FT-2000 series"; + } + + public Yaesu38Rig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java new file mode 100644 index 0000000..baa469c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java @@ -0,0 +1,190 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +public class Yaesu38_450Rig extends BaseRig { + private static final String TAG = "Yaesu38_450Rig"; + private final StringBuilder buffer = new StringBuilder(); + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); + } + } + + private void showAlert() { + if (swr >= Yaesu3RigConstant.swr_39_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu3RigConstant.setPTT_TX_On(on));//针对YAESU 450指令 + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); + //getConnector().sendData(Yaesu3RigConstant.setOperationUSB_Data_Mode()); + //getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationFreq8Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + //ToastMessage.showDebug("39 YAESU 读数据:"+new String(Yaesu3RigConstant.setReadOperationFreq())); + + if (!s.contains(";")) { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + //return;//说明数据还没接收完。 + } else { + if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0, s.indexOf(";"))); + } + + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";") + 1)); + + if (yaesu3Command == null) { + return; + } + //long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + //if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + //} + + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER + if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { + swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + if (Yaesu3Command.isALCMeter38(yaesu3Command)) { + alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + showAlert(); + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU FT-450"; + } + + public Yaesu38_450Rig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java new file mode 100644 index 0000000..0851512 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java @@ -0,0 +1,184 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 + */ +public class Yaesu39Rig extends BaseRig { + private static final String TAG = "Yaesu3Rig"; + private final StringBuilder buffer = new StringBuilder(); + private int swr=0; + private int alc=0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); + } + } + private void showAlert() { + if (swr >= Yaesu3RigConstant.swr_39_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationFreq9Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + + if (!s.contains(";")) { + buffer.append(s); + if (buffer.length() > 1000) clearBufferData(); + // return;//说明数据还没接收完。 + } else { + if (s.indexOf(";") > 0) {//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0, s.indexOf(";"))); + } + + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";") + 1)); + + if (yaesu3Command == null) { + return; + } + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + }else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")){//METER + if (Yaesu3Command.isSWRMeter39(yaesu3Command)){ + swr=Yaesu3Command.getSWROrALC39(yaesu3Command); + } + if (Yaesu3Command.isALCMeter39(yaesu3Command)){ + alc=Yaesu3Command.getSWROrALC39(yaesu3Command); + } + showAlert(); + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU FT-891"; + } + + public Yaesu39Rig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java new file mode 100644 index 0000000..976b787 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java @@ -0,0 +1,136 @@ +package com.bg7yoz.ft8cn.rigs; + + +import android.util.Log; + +public class Yaesu3Command { + private static final String TAG = "Yaesu3Command"; + private final String commandID; + private final String data; + + /** + * 获取命令(两字节字符串) + * + * @return 主命令值 + */ + public String getCommandID() {//获取主命令 + return commandID; + } + + /** + * 获取命令数据,字符串,没有分号 + * + * @return 命令数据 + */ + public String getData() {//获取命令数据 + return data; + } + + public Yaesu3Command(String commandID, String data) { + this.commandID = commandID; + this.data = data; + } + //解析接收的指令 + + /** + * 从串口中接到的数据解析出指令的数据:指令头+内容+分号 + * + * @param buffer 从串口接收到的数据 + * @return 返回电台指令对象,如果不符合指令的格式,返回null。 + */ + public static Yaesu3Command getCommand(String buffer) { + if (buffer.length() < 2) {//指令的长度必须大于等于2 + return null; + } + if (buffer.substring(0, 2).matches("[a-zA-Z][a-zA-Z]")) { + return new Yaesu3Command(buffer.substring(0, 2), buffer.substring(2)); + } + return null; + } + + + /** + * 计算频率 + * + * @param command 指令 + * @return 频率 + */ + public static long getFrequency(Yaesu3Command command) { + try { + if (command.getCommandID().equals("FA") || command.getCommandID().equals("FB")) { + return Long.parseLong(command.getData()); + } else { + return 0; + } + } catch (Exception e) { + Log.e(TAG, "获取频率失败: " + command.getData() + "\n" + e.getMessage()); + } + return 0; + } + + + /** + * 获取SWR_YAESU 950 + * + * @param command 指令 + * @return 值 + */ + public static int getALCOrSWR38(Yaesu3Command command) { + if (command.data.length() < 7) return 0; + return Integer.parseInt(command.data.substring(1, 4)); + } + + public static boolean isSWRMeter38(Yaesu3Command command) { + if (command.data.length() < 7) return false; + return (command.data.charAt(0) == '6'); + } + + public static boolean isALCMeter38(Yaesu3Command command) { + if (command.data.length() < 7) return false; + return (command.data.charAt(0) == '4'); + } + + + /** + * 获取SWR_YAESU 891 + * + * @param command 指令 + * @return 值 + */ + public static int getSWROrALC39(Yaesu3Command command) { + if (command.data.length() < 4) return 0; + return Integer.parseInt(command.data.substring(1, 4)); + } + + public static boolean isSWRMeter39(Yaesu3Command command) { + if (command.data.length() < 4) return false; + return (command.data.charAt(0) == '6'); + } + + public static boolean isALCMeter39(Yaesu3Command command) { + if (command.data.length() < 4) return false; + return (command.data.charAt(0) == '4'); + } + + /** + * 获取ts-590的ALC,SWR + * + * @param command 指令 + * @return 值 + */ + public static int get590ALCOrSWR(Yaesu3Command command) { + return Integer.parseInt(command.data.substring(1, 5)); + } + + public static boolean is590MeterALC(Yaesu3Command command){ + if (command.data.length() < 5) return false; + return command.data.charAt(2) == '3'; + } + public static boolean is590MeterSWR(Yaesu3Command command){ + if (command.data.length() < 5) return false; + return command.data.charAt(2) == '1'; + } + + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java new file mode 100644 index 0000000..aef3e00 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java @@ -0,0 +1,132 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; + +public class Yaesu3RigConstant { + private static final String TAG = "Yaesu3RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x01; + public static final int USB = 0x02; + public static final int CW = 0x03; + public static final int FM = 0x04; + public static final int AM = 0x05; + public static final int RTTY = 0x06; + public static final int CW_R = 0x07; + public static final int DATA = 0x08; + public static final int RTTY_R = 0x09; + public static final int NONE = 0x0A; + public static final int FM_N = 0x0B; + public static final int DATA_R = 0x0C; + public static final int AM_N = 0x0D; + + + public static final int swr_39_alert_max=125;//相当于3.0 + public static final int alc_39_alert_max=125;//超过,在表上显示红色 + //PTT状态 + + //指令集 + private static final String PTT_ON = "MX1;"; + private static final String PTT_OFF = "MX0;"; + private static final String USB_MODE = "MD02;"; + private static final String USB_MODE_DATA = "MD09;"; + private static final String DATA_U_MODE = "MD0C;"; + private static final String READ_FREQ = "FA;"; + private static final String READ_39METER_ALC = "RM4;";//38,39的指令都是一样的 + private static final String READ_39METER_SWR = "RM6;";//38,39的指令都是一样的 + + private static final String TX_ON = "TX1;";//用于FT450 ptt + private static final String TX_OFF = "TX0;";//用于FT450 ptt + + + + + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW: + return "CW"; + case FM: + return "FM"; + case AM: + return "AM"; + case RTTY: + return "RTTY"; + case CW_R: + return "CW_R"; + case DATA: + return "DATA"; + case RTTY_R: + return "RTTY_R"; + case NONE: + return "NONE"; + case FM_N: + return "FM_N"; + case DATA_R: + return "DATA_R"; + case AM_N: + return "AM_N"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + //针对YAESU 450的发射指令 + public static byte[] setPTT_TX_On(boolean on) {//用于FT450 + if (on) { + return TX_ON.getBytes(); + } else { + return TX_OFF.getBytes(); + } + + } + public static byte[] setOperationUSBMode() { + return USB_MODE.getBytes(); + } + public static byte[] setOperationUSB_Data_Mode() { + return USB_MODE_DATA.getBytes(); + } + + public static byte[] setOperationDATA_U_Mode() { + return DATA_U_MODE.getBytes(); + } + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 + return String.format("FA%011d;",freq).getBytes(); + } + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq9Byte(long freq) { + return String.format("FA%09d;",freq).getBytes(); + } + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq8Byte(long freq) { + return String.format("FA%08d;",freq).getBytes(); + } + public static byte[] setReadOperationFreq(){ + return READ_FREQ.getBytes(); + } + + public static byte[] setRead39Meters_ALC(){ + return READ_39METER_ALC.getBytes(); + } + public static byte[] setRead39Meters_SWR(){ + return READ_39METER_SWR.getBytes(); + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java new file mode 100644 index 0000000..1f7244e --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java @@ -0,0 +1,191 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 + */ +public class YaesuDX10Rig extends BaseRig { + private static final String TAG = "YaesuDX10Rig"; + private final StringBuilder buffer = new StringBuilder(); + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + + private Timer readFreqTimer = new Timer(); + + private TimerTask readTask() { + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()) { + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer = null; + return; + } + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + } catch (Exception e) { + Log.e(TAG, "readFreq error:" + e.getMessage()); + } + } + }; + } + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_ALC()); + getConnector().sendData(Yaesu3RigConstant.setRead39Meters_SWR()); + } + } + + private void showAlert() { + if (swr >= Yaesu3RigConstant.swr_39_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > Yaesu3RigConstant.alc_39_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + switch (getControlMode()) { + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu3RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationDATA_U_Mode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + getConnector().sendData(Yaesu3RigConstant.setOperationFreq9Byte(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + String s = new String(data); + if (!s.contains(";")) + { + buffer.append(s); + if (buffer.length()>1000) clearBufferData(); + return;//说明数据还没接收完。 + }else { + if (s.indexOf(";")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf(";"))); + } + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf(";")+1)); + + if (yaesu3Command == null) { + return; + } + //if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + // || yaesu3Command.getCommandID().toLowerCase().equals("FB")) { + // long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); + // if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + // setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + // } + //} + if (yaesu3Command.getCommandID().equalsIgnoreCase("FA") + || yaesu3Command.getCommandID().equalsIgnoreCase("FB")) { + long tempFreq = Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq != 0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + } else if (yaesu3Command.getCommandID().equalsIgnoreCase("RM")) {//METER + if (Yaesu3Command.isSWRMeter38(yaesu3Command)) { + swr = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + if (Yaesu3Command.isALCMeter38(yaesu3Command)) { + alc = Yaesu3Command.getALCOrSWR38(yaesu3Command); + } + showAlert(); + } + + } + + } + + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + + getConnector().sendData(Yaesu3RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU DX10 series"; + } + + public YaesuDX10Rig() { + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java new file mode 100644 index 0000000..be22673 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java @@ -0,0 +1,353 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * USB CDC/ACM serial driver implementation. + * + * @author mike wakerly (opensource@hoho.com) + * @see Universal + * Serial Bus Class Definitions for Communication Devices, v1.1 + */ +public class CdcAcmSerialDriver implements UsbSerialDriver { + + private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public CdcAcmSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + for( int i = 0; i < device.getInterfaceCount(); i++) { + if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM) + controlInterfaceCount++; + if(device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) + dataInterfaceCount++; + } + for( int port = 0; port < Math.min(controlInterfaceCount, dataInterfaceCount); port++) { + mPorts.add(new CdcAcmSerialPort(mDevice, port)); + } + if(mPorts.size() == 0) { + mPorts.add(new CdcAcmSerialPort(mDevice, -1)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class CdcAcmSerialPort extends CommonUsbSerialPort { + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + + private int mControlIndex; + + private boolean mRts = false; + private boolean mDtr = false; + + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 + private static final int GET_LINE_CODING = 0x21; + private static final int SET_CONTROL_LINE_STATE = 0x22; + private static final int SEND_BREAK = 0x23; + + public CdcAcmSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return CdcAcmSerialDriver.this; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + if (mPortNumber == -1) { + Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); + openSingleInterface(); + } else { + Log.d(TAG,"trying default interface logic"); + openInterface(); + } + } + + private void openSingleInterface() throws IOException { + // the following code is inspired by the cdc-acm driver in the linux kernel + + mControlIndex = 0; + mControlInterface = mDevice.getInterface(0); + mDataInterface = mDevice.getInterface(0); + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim shared control/data interface"); + } + + for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) { + UsbEndpoint ep = mControlInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { + mControlEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mWriteEndpoint = ep; + } + } + if (mControlEndpoint == null) { + throw new IOException("No control endpoint"); + } + } + + private void openInterface() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + mControlInterface = null; + mDataInterface = null; + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM) { + if(controlInterfaceCount == mPortNumber) { + mControlIndex = i; + mControlInterface = usbInterface; + } + controlInterfaceCount++; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + if(dataInterfaceCount == mPortNumber) { + mDataInterface = usbInterface; + } + dataInterfaceCount++; + } + } + + if(mControlInterface == null) { + throw new IOException("No control interface"); + } + Log.d(TAG, "Control iface=" + mControlInterface); + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim control interface"); + } + + mControlEndpoint = mControlInterface.getEndpoint(0); + if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { + throw new IOException("Invalid control endpoint"); + } + + if(mDataInterface == null) { + throw new IOException("No data interface"); + } + Log.d(TAG, "data iface=" + mDataInterface); + + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim data interface"); + } + + for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { + UsbEndpoint ep = mDataInterface.getEndpoint(i); + if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mReadEndpoint = ep; + if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mWriteEndpoint = ep; + } + } + + private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { + int len = mConnection.controlTransfer( + USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); + if(len < 0) { + throw new IOException("controlTransfer failed"); + } + return len; + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mControlInterface); + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + byte stopBitsByte; + switch (stopBits) { + case STOPBITS_1: stopBitsByte = 0; break; + case STOPBITS_1_5: stopBitsByte = 1; break; + case STOPBITS_2: stopBitsByte = 2; break; + default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + byte parityBitesByte; + switch (parity) { + case PARITY_NONE: parityBitesByte = 0; break; + case PARITY_ODD: parityBitesByte = 1; break; + case PARITY_EVEN: parityBitesByte = 2; break; + case PARITY_MARK: parityBitesByte = 3; break; + case PARITY_SPACE: parityBitesByte = 4; break; + default: throw new IllegalArgumentException("Invalid parity: " + parity); + } + byte[] msg = { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + (byte) dataBits}; + sendAcmControlMessage(SET_LINE_CODING, 0, msg); + } + + @Override + public boolean getDTR() throws IOException { + return mDtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + mDtr = value; + setDtrRts(); + } + + @Override + public boolean getRTS() throws IOException { + return mRts; + } + + @Override + public void setRTS(boolean value) throws IOException { + mRts = value; + setDtrRts(); + } + + private void setDtrRts() throws IOException { + int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); + sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); + } + + @Override + public EnumSet getControlLines() throws IOException { + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(mRts) set.add(ControlLine.RTS); + if(mDtr) set.add(ControlLine.DTR); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.of(ControlLine.RTS, ControlLine.DTR); + } + + @Override + public void setBreak(boolean value) throws IOException { + sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null); + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_ARDUINO, + new int[] { + UsbId.ARDUINO_UNO, + UsbId.ARDUINO_UNO_R3, + UsbId.ARDUINO_MEGA_2560, + UsbId.ARDUINO_MEGA_2560_R3, + UsbId.ARDUINO_SERIAL_ADAPTER, + UsbId.ARDUINO_SERIAL_ADAPTER_R3, + UsbId.ARDUINO_MEGA_ADK, + UsbId.ARDUINO_MEGA_ADK_R3, + UsbId.ARDUINO_LEONARDO, + UsbId.ARDUINO_MICRO, + }); + supportedDevices.put(UsbId.VENDOR_VAN_OOIJEN_TECH, + new int[] { + UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, + }); + + + supportedDevices.put(UsbId.VENDOR_ATMEL, + new int[] { + UsbId.ATMEL_LUFA_CDC_DEMO_APP, + }); + supportedDevices.put(UsbId.VENDOR_LEAFLABS, + new int[] { + UsbId.LEAFLABS_MAPLE, + }); + supportedDevices.put(UsbId.VENDOR_ARM, + new int[] { + UsbId.ARM_MBED, + }); + supportedDevices.put(UsbId.VENDOR_ST, + new int[] { + UsbId.ST_CDC, + UsbId.ST_CDC2, + UsbId.ST_CDC3, + UsbId.CDC_WOLF_PID + }); + supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI, + new int[] { + UsbId.RASPBERRY_PI_PICO_MICROPYTHON, + }); + + //国赫1带的USB口 + supportedDevices.put(UsbId.VENDOR_GUOHE1, + new int[] { + UsbId.PID_GUOHE1, + }); + + + supportedDevices.put(UsbId.VENDOR_ICOM, + new int[]{ + UsbId.IC_R30, + UsbId.IC_705, + UsbId.ICOM_USB_SERIAL_CONTROL + }); + supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ + UsbId.XIEGU_X6100,//协谷X6100 + }); + return supportedDevices; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java new file mode 100644 index 0000000..2029982 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java @@ -0,0 +1,388 @@ +/* Copyright 2014 Andreas Butti + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import com.bg7yoz.ft8cn.BuildConfig; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Ch34xSerialDriver implements UsbSerialDriver { + + private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + private static final int LCR_ENABLE_RX = 0x80; + private static final int LCR_ENABLE_TX = 0x40; + private static final int LCR_MARK_SPACE = 0x20; + private static final int LCR_PAR_EVEN = 0x10; + private static final int LCR_ENABLE_PAR = 0x08; + private static final int LCR_STOP_BITS_2 = 0x04; + private static final int LCR_CS8 = 0x03; + private static final int LCR_CS7 = 0x02; + private static final int LCR_CS6 = 0x01; + private static final int LCR_CS5 = 0x00; + + private static final int GCL_CTS = 0x01; + private static final int GCL_DSR = 0x02; + private static final int GCL_RI = 0x04; + private static final int GCL_CD = 0x08; + private static final int SCL_DTR = 0x20; + private static final int SCL_RTS = 0x40; + + public Ch34xSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new Ch340SerialPort(mDevice, 0); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + public class Ch340SerialPort extends CommonUsbSerialPort { + + private static final int USB_TIMEOUT_MILLIS = 5000; + + private final int DEFAULT_BAUD_RATE = 9600; + + private boolean dtr = false; + private boolean rts = false; + + public Ch340SerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Ch34xSerialDriver.this; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbIface = mDevice.getInterface(i); + if (!mConnection.claimInterface(usbIface, true)) { + throw new IOException("Could not claim data interface"); + } + } + + UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + initialize(); + setBaudRate(DEFAULT_BAUD_RATE); + } + + @Override + protected void closeInt() { + try { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) + mConnection.releaseInterface(mDevice.getInterface(i)); + } catch(Exception ignored) {} + } + + private int controlOut(int request, int value, int index) { + final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, + value, index, null, 0, USB_TIMEOUT_MILLIS); + } + + + private int controlIn(int request, int value, int index, byte[] buffer) { + final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, + value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); + } + + + private void checkState(String msg, int request, int value, int[] expected) throws IOException { + byte[] buffer = new byte[expected.length]; + int ret = controlIn(request, value, 0, buffer); + + if (ret < 0) { + throw new IOException("Failed send cmd [" + msg + "]"); + } + + if (ret != expected.length) { + throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); + } + + for (int i = 0; i < expected.length; i++) { + if (expected[i] == -1) { + continue; + } + + int current = buffer[i] & 0xff; + if (expected[i] != current) { + throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); + } + } + } + + private void setControlLines() throws IOException { + if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { + throw new IOException("Failed to set control lines"); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[2]; + int ret = controlIn(0x95, 0x0706, 0, buffer); + if (ret < 0) + throw new IOException("Error getting control lines"); + return buffer[0]; + } + + private void initialize() throws IOException { + checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); + + if (controlOut(0xa1, 0, 0) < 0) { + throw new IOException("Init failed: #2"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); + + if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { + throw new IOException("Init failed: #5"); + } + + checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); + + if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { + throw new IOException("Init failed: #7"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + setControlLines(); + + checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); + } + + + private void setBaudRate(int baudRate) throws IOException { + long factor; + long divisor; + + if (baudRate == 921600) { + divisor = 7; + factor = 0xf300; + } else { + final long BAUDBASE_FACTOR = 1532620800; + final int BAUDBASE_DIVMAX = 3; + + if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) + baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling + factor = BAUDBASE_FACTOR / baudRate; + divisor = BAUDBASE_DIVMAX; + while ((factor > 0xfff0) && divisor > 0) { + factor >>= 3; + divisor--; + } + if (factor > 0xfff0) { + throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); + } + factor = 0x10000 - factor; + } + + divisor |= 0x0080; // else ch341a waits until buffer full + int val1 = (int) ((factor & 0xff00) | divisor); + int val2 = (int) (factor & 0xff); + Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2)); + int ret = controlOut(0x9a, 0x1312, val1); + if (ret < 0) { + throw new IOException("Error setting baud rate: #1)"); + } + ret = controlOut(0x9a, 0x0f2c, val2); + if (ret < 0) { + throw new IOException("Error setting baud rate: #2"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; + + switch (dataBits) { + case DATABITS_5: + lcr |= LCR_CS5; + break; + case DATABITS_6: + lcr |= LCR_CS6; + break; + case DATABITS_7: + lcr |= LCR_CS7; + break; + case DATABITS_8: + lcr |= LCR_CS8; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + lcr |= LCR_ENABLE_PAR; + break; + case PARITY_EVEN: + lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; + break; + case PARITY_MARK: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; + break; + case PARITY_SPACE: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + lcr |= LCR_STOP_BITS_2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int ret = controlOut(0x9a, 0x2518, lcr); + if (ret < 0) { + throw new IOException("Error setting control byte"); + } + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & GCL_CD) == 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & GCL_CTS) == 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & GCL_DSR) == 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setControlLines(); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & GCL_RI) == 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setControlLines(); + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); + if((status & GCL_CD) == 0) set.add(ControlLine.CD); + if((status & GCL_RI) == 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void setBreak(boolean value) throws IOException { + byte[] req = new byte[2]; + if(controlIn(0x95, 0x1805, 0, req) < 0) { + throw new IOException("Error getting BREAK condition"); + } + if(value) { + req[0] &= ~1; + req[1] &= ~0x40; + } else { + req[0] |= 1; + req[1] |= 0x40; + } + int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); + if(controlOut(0x9a, 0x1805, val) < 0) { + throw new IOException("Error setting BREAK condition"); + } + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ + UsbId.QINHENG_CH340, + UsbId.QINHENG_CH341A, + UsbId.QINHENG_CH341K + }); + return supportedDevices; + } + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java new file mode 100644 index 0000000..73f36d6 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java @@ -0,0 +1,318 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbRequest; +import android.util.Log; + +import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.EnumSet; + +/** + * A base class shared by several driver implementations. + * + * @author mike wakerly (opensource@hoho.com) + */ +public abstract class CommonUsbSerialPort implements UsbSerialPort { + + public static boolean DEBUG = false; + + private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); + private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit + + protected final UsbDevice mDevice; + protected final int mPortNumber; + + // non-null when open() + protected UsbDeviceConnection mConnection = null; + protected UsbEndpoint mReadEndpoint; + protected UsbEndpoint mWriteEndpoint; + protected UsbRequest mUsbRequest; + + /** + * Internal write buffer. + * Guarded by {@link #mWriteBufferLock}. + * Default length = mReadEndpoint.getMaxPacketSize() + **/ + protected byte[] mWriteBuffer; + protected final Object mWriteBufferLock = new Object(); + + + public CommonUsbSerialPort(UsbDevice device, int portNumber) { + mDevice = device; + mPortNumber = portNumber; + } + + @Override + public String toString() { + return String.format("<%s device_name=%s device_id=%s port_number=%s>", + getClass().getSimpleName(), mDevice.getDeviceName(), + mDevice.getDeviceId(), mPortNumber); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public int getPortNumber() { + return mPortNumber; + } + + @Override + public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; } + + @Override + public UsbEndpoint getReadEndpoint() { return mReadEndpoint; } + + /** + * Returns the device serial number + * @return serial number + */ + @Override + public String getSerial() { + return mConnection.getSerial(); + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for write operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes, <= 0 resets original size + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize <= 0) { + if (mWriteEndpoint != null) { + bufferSize = mWriteEndpoint.getMaxPacketSize(); + } else { + mWriteBuffer = null; + return; + } + } + if (mWriteBuffer != null && bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public void open(UsbDeviceConnection connection) throws IOException { + if (mConnection != null) { + throw new IOException("Already open"); + } + if(connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + mConnection = connection; + try { + openInt(connection); + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read & write endpoints"); + } + mUsbRequest = new UsbRequest(); + mUsbRequest.initialize(mConnection, mReadEndpoint); + } catch(Exception e) { + try { + close(); + } catch(Exception ignored) {} + throw e; + } + } + + protected abstract void openInt(UsbDeviceConnection connection) throws IOException; + + @Override + public void close() throws IOException { + if (mConnection == null) { + throw new IOException("Already closed"); + } + try { + mUsbRequest.cancel(); + } catch(Exception ignored) {} + mUsbRequest = null; + try { + closeInt(); + } catch(Exception ignored) {} + try { + mConnection.close(); + } catch(Exception ignored) {} + mConnection = null; + } + + protected abstract void closeInt(); + + /** + * use simple USB request supported by all devices to test if connection is still valid + */ + protected void testConnection() throws IOException { + byte[] buf = new byte[2]; + int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); + if(len < 0) + throw new IOException("USB get_status request failed"); + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + return read(dest, timeout, true); + } + + protected int read(final byte[] dest, final int timeout, boolean testConnection) throws IOException { + if(mConnection == null) { + throw new IOException("Connection closed"); + } + if(dest.length <= 0) { + throw new IllegalArgumentException("Read buffer to small"); + } + final int nread; + if (timeout != 0) { + // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer + // https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data + // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, + // as it crashes with short timeout, e.g. + // A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test) + // /system/lib64/libusbhost.so (usb_request_wait+192) + // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) + // data loss / crashes were observed with timeout up to 200 msec + long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; + int readMax = Math.min(dest.length, MAX_READ_SIZE); + nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); + // Android error propagation is improvable: + // nread == -1 can be: timeout, connection lost, buffer to small, ??? + if(nread == -1 && testConnection && MonotonicClock.millis() < endTime) + testConnection(); + + } else { + final ByteBuffer buf = ByteBuffer.wrap(dest); + if (!mUsbRequest.queue(buf, dest.length)) { + throw new IOException("Queueing USB request failed"); + } + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Waiting for USB request failed"); + } + nread = buf.position(); + // Android error propagation is improvable: + // response != null & nread == 0 can be: connection lost, buffer to small, ??? + if(nread == 0) { + testConnection(); + } + } + return Math.max(nread, 0); + } + + @Override + public void write(final byte[] src, final int timeout) throws IOException { + int offset = 0; + final long endTime = (timeout == 0) ? 0 : (MonotonicClock.millis() + timeout); + + if(mConnection == null) { + throw new IOException("Connection closed"); + } + while (offset < src.length) { + int requestTimeout; + final int requestLength; + final int actualLength; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + if (mWriteBuffer == null) { + mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; + } + requestLength = Math.min(src.length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, requestLength); + writeBuffer = mWriteBuffer; + } + if (timeout == 0 || offset == 0) { + requestTimeout = timeout; + } else { + requestTimeout = (int)(endTime - MonotonicClock.millis()); + if(requestTimeout == 0) + requestTimeout = -1; + } + if (requestTimeout < 0) { + actualLength = -2; + } else { + actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); + } + } + if (DEBUG) { + Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + src.length + " timeout " + requestTimeout); + } + if (actualLength <= 0) { + if (timeout != 0 && MonotonicClock.millis() >= endTime) { + SerialTimeoutException ex = new SerialTimeoutException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + ", rc=" + actualLength); + ex.bytesTransferred = offset; + throw ex; + } else { + throw new IOException("Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length); + } + } + offset += actualLength; + } + } + + @Override + public boolean isOpen() { + return mConnection != null; + } + + @Override + public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; + + @Override + public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRI() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public abstract EnumSet getControlLines() throws IOException; + + @Override + public abstract EnumSet getSupportedControlLines() throws IOException; + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java new file mode 100644 index 0000000..0d8009b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java @@ -0,0 +1,335 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Cp21xxSerialDriver implements UsbSerialDriver { + + private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public Cp21xxSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new Cp21xxSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class Cp21xxSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + /* + * Configuration Request Types + */ + private static final int REQTYPE_HOST_TO_DEVICE = 0x41; + private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; + + /* + * Configuration Request Codes + */ + private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; + private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; + private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; + private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; + private static final int SILABSER_SET_BAUDRATE = 0x1E; + private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; + private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; + + private static final int FLUSH_READ_CODE = 0x0a; + private static final int FLUSH_WRITE_CODE = 0x05; + + /* + * SILABSER_IFC_ENABLE_REQUEST_CODE + */ + private static final int UART_ENABLE = 0x0001; + private static final int UART_DISABLE = 0x0000; + + /* + * SILABSER_SET_MHS_REQUEST_CODE + */ + private static final int DTR_ENABLE = 0x101; + private static final int DTR_DISABLE = 0x100; + private static final int RTS_ENABLE = 0x202; + private static final int RTS_DISABLE = 0x200; + + /* + * SILABSER_GET_MDMSTS_REQUEST_CODE + */ + private static final int STATUS_CTS = 0x10; + private static final int STATUS_DSR = 0x20; + private static final int STATUS_RI = 0x40; + private static final int STATUS_CD = 0x80; + + + private boolean dtr = false; + private boolean rts = false; + + // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity + // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored + private boolean mIsRestrictedPort; + + public Cp21xxSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Cp21xxSerialDriver.this; + } + + private void setConfigSingle(int request, int value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); + } + return buffer[0]; + } + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; + if(mPortNumber >= mDevice.getInterfaceCount()) { + throw new IOException("Unknown port number"); + } + UsbInterface dataIface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(dataIface, true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); + } + + @Override + protected void closeInt() { + try { + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + private void setBaudRate(int baudRate) throws IOException { + byte[] data = new byte[] { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff) + }; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE, + 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); + if (ret < 0) { + throw new IOException("Error setting baud rate"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int configDataBits = 0; + switch (dataBits) { + case DATABITS_5: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0500; + break; + case DATABITS_6: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0600; + break; + case DATABITS_7: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0700; + break; + case DATABITS_8: + configDataBits |= 0x0800; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + configDataBits |= 0x0010; + break; + case PARITY_EVEN: + configDataBits |= 0x0020; + break; + case PARITY_MARK: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: mark"); + configDataBits |= 0x0030; + break; + case PARITY_SPACE: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: space"); + configDataBits |= 0x0040; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported stop bits: 2"); + configDataBits |= 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); + } + + @Override + public EnumSet getControlLines() throws IOException { + byte status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + // note: only working on some devices, on other devices ignored w/o error + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) { + setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_SILABS, + new int[] { + UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 + UsbId.SILABS_CP2105, + UsbId.SILABS_CP2108, + }); + return supportedDevices; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java new file mode 100644 index 0000000..6fc4479 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java @@ -0,0 +1,432 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * Copyright 2020 kai morich + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/* + * driver is implemented from various information scattered over FTDI documentation + * + * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf + * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf + * device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice + * + */ + +public class FtdiSerialDriver implements UsbSerialDriver { + + private static final String TAG = FtdiSerialPort.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public FtdiSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new FtdiSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class FtdiSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS + + private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + + private static final int RESET_REQUEST = 0; + private static final int MODEM_CONTROL_REQUEST = 1; + private static final int SET_BAUD_RATE_REQUEST = 3; + private static final int SET_DATA_REQUEST = 4; + private static final int GET_MODEM_STATUS_REQUEST = 5; + private static final int SET_LATENCY_TIMER_REQUEST = 9; + private static final int GET_LATENCY_TIMER_REQUEST = 10; + + private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; + private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; + private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; + private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; + private static final int MODEM_STATUS_CTS = 0x10; + private static final int MODEM_STATUS_DSR = 0x20; + private static final int MODEM_STATUS_RI = 0x40; + private static final int MODEM_STATUS_CD = 0x80; + private static final int RESET_ALL = 0; + private static final int RESET_PURGE_RX = 1; + private static final int RESET_PURGE_TX = 2; + + private boolean baudRateWithPort = false; + private boolean dtr = false; + private boolean rts = false; + private int breakConfig = 0; + + public FtdiSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return FtdiSerialDriver.this; + } + + + @Override + protected void openInt(UsbDeviceConnection connection) throws IOException { + if (!connection.claimInterface(mDevice.getInterface(mPortNumber), true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { + throw new IOException("Not enough endpoints"); + } + mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); + mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Reset failed: result=" + result); + } + result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | + (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), + mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Init RTS,DTR failed: result=" + result); + } + + // mDevice.getVersion() would require API 23 + byte[] rawDescriptors = connection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int deviceType = rawDescriptors[13]; + baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices + || mDevice.getInterfaceCount() > 1; // FT2232C + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + if(dest.length <= READ_HEADER_LENGTH) { + throw new IllegalArgumentException("Read buffer to small"); + // could allocate larger buffer, including space for 2 header bytes, but this would + // result in buffers not being 64 byte aligned any more, causing data loss at continuous + // data transfer at high baud rates when buffers are fully filled. + } + int nread; + if (timeout != 0) { + long endTime = MonotonicClock.millis() + timeout; + do { + nread = super.read(dest, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); + } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); + if(nread <= 0 && MonotonicClock.millis() < endTime) + testConnection(); + } else { + do { + nread = super.read(dest, timeout, false); + } while (nread == READ_HEADER_LENGTH); + } + return readFilter(dest, nread); + } + + protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException { + final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); + int destPos = 0; + for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { + int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); + if (length < 0) + throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); + System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); + destPos += length; + } + //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); + return destPos; + } + + private void setBaudrate(int baudRate) throws IOException { + int divisor, subdivisor, effectiveBaudRate; + if (baudRate > 3500000) { + throw new UnsupportedOperationException("Baud rate to high"); + } else if(baudRate >= 2500000) { + divisor = 0; + subdivisor = 0; + effectiveBaudRate = 3000000; + } else if(baudRate >= 1750000) { + divisor = 1; + subdivisor = 0; + effectiveBaudRate = 2000000; + } else { + divisor = (24000000 << 1) / baudRate; + divisor = (divisor + 1) >> 1; // round + subdivisor = divisor & 0x07; + divisor >>= 3; + if (divisor > 0x3fff) // exceeds bit 13 at 183 baud + throw new UnsupportedOperationException("Baud rate to low"); + effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); + effectiveBaudRate = (effectiveBaudRate +1) >> 1; + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // can happen only > 1.5Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + int value = divisor; + int index = 0; + switch(subdivisor) { + case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 + case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 + case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 + case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 + case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 + case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625 + case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75 + case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 + } + if(baudRateWithPort) { + index <<= 8; + index |= mPortNumber+1; + } + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", + baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, + value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting baudrate failed: result=" + result); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudrate(baudRate); + + int config = 0; + switch (dataBits) { + case DATABITS_5: + case DATABITS_6: + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + case DATABITS_7: + case DATABITS_8: + config |= dataBits; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + config |= 0x100; + break; + case PARITY_EVEN: + config |= 0x200; + break; + case PARITY_MARK: + config |= 0x300; + break; + case PARITY_SPACE: + config |= 0x400; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + config |= 0x1000; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting parameters failed: result=" + result); + } + breakConfig = config; + } + + private int getStatus() throws IOException { + byte[] data = new byte[2]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 2) { + throw new IOException("Get modem status failed: result=" + result); + } + return data[0]; + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & MODEM_STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & MODEM_STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & MODEM_STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + dtr = value; + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & MODEM_STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + rts = value; + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge write buffer failed: result=" + result); + } + } + + if (purgeReadBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge read buffer failed: result=" + result); + } + } + } + + @Override + public void setBreak(boolean value) throws IOException { + int config = breakConfig; + if(value) config |= 0x4000; + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting BREAK failed: result=" + result); + } + } + + public void setLatencyTimer(int latencyTime) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST, + latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set latency timer failed: result=" + result); + } + } + + public int getLatencyTimer() throws IOException { + byte[] data = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != 1) { + throw new IOException("Get latency timer failed: result=" + result); + } + return data[0]; + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_FTDI, + new int[] { + UsbId.FTDI_FT232R, + UsbId.FTDI_ZERO, + UsbId.FTDI_FT232H, + UsbId.FTDI_FT2232H, + UsbId.FTDI_FT4232H, + UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD + }); + return supportedDevices; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java new file mode 100644 index 0000000..4174729 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java @@ -0,0 +1,87 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.util.Pair; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Maps (vendor id, product id) pairs to the corresponding serial driver. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class ProbeTable { + + private final Map, Class> mProbeTable = + new LinkedHashMap<>(); + + /** + * Adds or updates a (vendor, product) pair in the table. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @param driverClass the driver class responsible for this pair + * @return {@code this}, for chaining + */ + public ProbeTable addProduct(int vendorId, int productId, + Class driverClass) { + mProbeTable.put(Pair.create(vendorId, productId), driverClass); + return this; + } + + /** + * Internal method to add all supported products from + * {@code getSupportedProducts} static method. + * + * @param driverClass + * @return + */ + @SuppressWarnings("unchecked") + ProbeTable addDriver(Class driverClass) { + final Method method; + + try { + method = driverClass.getMethod("getSupportedDevices"); + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + final Map devices; + try { + devices = (Map) method.invoke(null); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + for (Map.Entry entry : devices.entrySet()) { + final int vendorId = entry.getKey(); + for (int productId : entry.getValue()) { + addProduct(vendorId, productId, driverClass); + } + } + + return this; + } + + /** + * Returns the driver for the given (vendor, product) pair, or {@code null} + * if no match. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @return the driver class matching this pair, or {@code null} + */ + public Class findDriver(int vendorId, int productId) { + final Pair pair = Pair.create(vendorId, productId); + return mProbeTable.get(pair); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java new file mode 100644 index 0000000..531fc0c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java @@ -0,0 +1,582 @@ +/* + * Ported to usb-serial-for-android by Felix Hädicke + * + * Based on the pyprolific driver written by Emmanuel Blot + * See https://github.com/eblot/pyftdi + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.serialport.util.MonotonicClock; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ProlificSerialDriver implements UsbSerialDriver { + + private final String TAG = ProlificSerialDriver.class.getSimpleName(); + + private final static int[] standardBaudRates = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, + 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, + 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN } + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + public ProlificSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new ProlificSerialPort(mDevice, 0); + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + class ProlificSerialPort extends CommonUsbSerialPort { + + private static final int USB_READ_TIMEOUT_MILLIS = 1000; + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + private static final int USB_RECIP_INTERFACE = 0x01; + + private static final int VENDOR_READ_REQUEST = 0x01; + private static final int VENDOR_WRITE_REQUEST = 0x01; + private static final int VENDOR_READ_HXN_REQUEST = 0x81; + private static final int VENDOR_WRITE_HXN_REQUEST = 0x80; + + private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; + private static final int VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR; + private static final int CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int WRITE_ENDPOINT = 0x02; + private static final int READ_ENDPOINT = 0x83; + private static final int INTERRUPT_ENDPOINT = 0x81; + + private static final int RESET_HXN_REQUEST = 0x07; + private static final int FLUSH_RX_REQUEST = 0x08; + private static final int FLUSH_TX_REQUEST = 0x09; + private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING + private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE + private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK + private static final int GET_CONTROL_HXN_REQUEST = 0x80; + private static final int GET_CONTROL_REQUEST = 0x87; + private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length + + /* RESET_HXN_REQUEST */ + private static final int RESET_HXN_RX_PIPE = 1; + private static final int RESET_HXN_TX_PIPE = 2; + + /* SET_CONTROL_REQUEST */ + private static final int CONTROL_DTR = 0x01; + private static final int CONTROL_RTS = 0x02; + + /* GET_CONTROL_REQUEST */ + private static final int GET_CONTROL_FLAG_CD = 0x02; + private static final int GET_CONTROL_FLAG_DSR = 0x04; + private static final int GET_CONTROL_FLAG_RI = 0x01; + private static final int GET_CONTROL_FLAG_CTS = 0x08; + + /* GET_CONTROL_HXN_REQUEST */ + private static final int GET_CONTROL_HXN_FLAG_CD = 0x40; + private static final int GET_CONTROL_HXN_FLAG_DSR = 0x20; + private static final int GET_CONTROL_HXN_FLAG_RI = 0x80; + private static final int GET_CONTROL_HXN_FLAG_CTS = 0x08; + + /* interrupt endpoint read */ + private static final int STATUS_FLAG_CD = 0x01; + private static final int STATUS_FLAG_DSR = 0x02; + private static final int STATUS_FLAG_RI = 0x08; + private static final int STATUS_FLAG_CTS = 0x80; + + private static final int STATUS_BUFFER_SIZE = 10; + private static final int STATUS_BYTE_IDX = 8; + + protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; + private UsbEndpoint mInterruptEndpoint; + private int mControlLinesValue = 0; + private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; + + private int mStatus = 0; + private volatile Thread mReadStatusThread = null; + private final Object mReadStatusThreadLock = new Object(); + private boolean mStopReadStatusThread = false; + private IOException mReadStatusException = null; + + + public ProlificSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return ProlificSerialDriver.this; + } + + private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException { + byte[] buffer = new byte[length]; + int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result)); + } + return buffer; + } + + private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException { + int length = (data == null) ? 0 : data.length; + int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result)); + } + } + + private byte[] vendorIn(int value, int index, int length) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST; + return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length); + } + + private void vendorOut(int value, int index, byte[] data) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST; + outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data); + } + + private void resetDevice() throws IOException { + purgeHwBuffers(true, true); + } + + private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { + outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data); + } + + private boolean testHxStatus() { + try { + inControlTransfer(VENDOR_IN_REQTYPE, VENDOR_READ_REQUEST, 0x8080, 0, 1); + return true; + } catch(IOException ignored) { + return false; + } + } + + private void doBlackMagic() throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + return; + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 0, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 1, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorOut(0, 1, null); + vendorOut(1, 0, null); + vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null); + } + + private void setControlLines(int newControlLinesValue) throws IOException { + ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); + mControlLinesValue = newControlLinesValue; + } + + private void readStatusThreadFunction() { + try { + while (!mStopReadStatusThread) { + byte[] buffer = new byte[STATUS_BUFFER_SIZE]; + long endTime = MonotonicClock.millis() + 500; + int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); + if(readBytesCount == -1 && MonotonicClock.millis() < endTime) + testConnection(); + if (readBytesCount > 0) { + if (readBytesCount != STATUS_BUFFER_SIZE) { + throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); + } else if(buffer[0] != (byte)STATUS_NOTIFICATION ) { + throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]); + } else { + mStatus = buffer[STATUS_BYTE_IDX] & 0xff; + } + } + } + } catch (IOException e) { + mReadStatusException = e; + } + //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); + } + + private int getStatus() throws IOException { + if ((mReadStatusThread == null) && (mReadStatusException == null)) { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread == null) { + mStatus = 0; + if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } else { + byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } + //Log.d(TAG, "start control line status thread " + mStatus); + mReadStatusThread = new Thread(this::readStatusThreadFunction); + mReadStatusThread.setDaemon(true); + mReadStatusThread.start(); + } + } + } + + /* throw and clear an exception which occured in the status read thread */ + IOException readStatusException = mReadStatusException; + if (mReadStatusException != null) { + mReadStatusException = null; + throw new IOException(readStatusException); + } + + return mStatus; + } + + private boolean testStatusFlag(int flag) throws IOException { + return ((getStatus() & flag) == flag); + } + + @Override + public void openInt(UsbDeviceConnection connection) throws IOException { + UsbInterface usbInterface = mDevice.getInterface(0); + + if (!connection.claimInterface(usbInterface, true)) { + throw new IOException("Error claiming Prolific interface 0"); + } + + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { + UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); + + switch (currentEndpoint.getAddress()) { + case READ_ENDPOINT: + mReadEndpoint = currentEndpoint; + break; + + case WRITE_ENDPOINT: + mWriteEndpoint = currentEndpoint; + break; + + case INTERRUPT_ENDPOINT: + mInterruptEndpoint = currentEndpoint; + break; + } + } + + byte[] rawDescriptors = connection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2]; + int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12]; + byte maxPacketSize0 = rawDescriptors[7]; + if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) { + mDeviceType = DeviceType.DEVICE_TYPE_01; + } else if(deviceVersion == 0x300 && usbVersion == 0x200) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TA + } else if(deviceVersion == 0x500) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TB + } else if(usbVersion == 0x200 && !testHxStatus()) { + mDeviceType = DeviceType.DEVICE_TYPE_HXN; + } else { + mDeviceType = DeviceType.DEVICE_TYPE_HX; + } + Log.d(TAG, String.format("usbVersion=%x, deviceVersion=%x, deviceClass=%d, packetSize=%d => deviceType=%s", + usbVersion, deviceVersion, mDevice.getDeviceClass(), maxPacketSize0, mDeviceType.name())); + resetDevice(); + doBlackMagic(); + setControlLines(mControlLinesValue); + } + + @Override + public void closeInt() { + try { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread != null) { + try { + mStopReadStatusThread = true; + mReadStatusThread.join(); + } catch (Exception e) { + Log.w(TAG, "An error occured while waiting for status read thread", e); + } + mStopReadStatusThread = false; + mReadStatusThread = null; + mReadStatusException = null; + } + } + resetDevice(); + } catch(Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(0)); + } catch(Exception ignored) {} + } + + private int filterBaudRate(int baudRate) { + if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) { + return baudRate & ~(1<<29); // for testing purposes accept without further checks + } + if (baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + return baudRate; + } + for(int br : standardBaudRates) { + if (br == baudRate) { + return baudRate; + } + } + /* + * Formula taken from Linux + FreeBSD. + * + * For TA+TB devices + * baudrate = baseline / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + * + * For other devices + * baudrate = baseline / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + * + */ + int baseline, mantissa, exponent, buf, effectiveBaudRate; + baseline = 12000000 * 32; + mantissa = baseline / baudRate; + if (mantissa == 0) { // > unrealistic 384 MBaud + throw new UnsupportedOperationException("Baud rate to high"); + } + exponent = 0; + if (mDeviceType == DeviceType.DEVICE_TYPE_T) { + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { // < 7 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + ((exponent & ~1) << 12) + ((exponent & 1) << 16) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> exponent; + } else { + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { // < 45.8 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + (exponent << 9) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> (exponent << 1); + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d", + baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent)); + return buf; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + baudRate = filterBaudRate(baudRate); + if ((mBaudRate == baudRate) && (mDataBits == dataBits) + && (mStopBits == stopBits) && (mParity == parity)) { + // Make sure no action is performed if there is nothing to change + return; + } + + byte[] lineRequestData = new byte[7]; + lineRequestData[0] = (byte) (baudRate & 0xff); + lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); + lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); + lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); + + switch (stopBits) { + case STOPBITS_1: + lineRequestData[4] = 0; + break; + case STOPBITS_1_5: + lineRequestData[4] = 1; + break; + case STOPBITS_2: + lineRequestData[4] = 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + switch (parity) { + case PARITY_NONE: + lineRequestData[5] = 0; + break; + case PARITY_ODD: + lineRequestData[5] = 1; + break; + case PARITY_EVEN: + lineRequestData[5] = 2; + break; + case PARITY_MARK: + lineRequestData[5] = 3; + break; + case PARITY_SPACE: + lineRequestData[5] = 4; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + lineRequestData[6] = (byte) dataBits; + + ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); + + resetDevice(); + + mBaudRate = baudRate; + mDataBits = dataBits; + mStopBits = stopBits; + mParity = parity; + } + + @Override + public boolean getCD() throws IOException { + return testStatusFlag(STATUS_FLAG_CD); + } + + @Override + public boolean getCTS() throws IOException { + return testStatusFlag(STATUS_FLAG_CTS); + } + + @Override + public boolean getDSR() throws IOException { + return testStatusFlag(STATUS_FLAG_DSR); + } + + @Override + public boolean getDTR() throws IOException { + return (mControlLinesValue & CONTROL_DTR) != 0; + } + + @Override + public void setDTR(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_DTR; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; + } + setControlLines(newControlLinesValue); + } + + @Override + public boolean getRI() throws IOException { + return testStatusFlag(STATUS_FLAG_RI); + } + + @Override + public boolean getRTS() throws IOException { + return (mControlLinesValue & CONTROL_RTS) != 0; + } + + @Override + public void setRTS(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_RTS; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; + } + setControlLines(newControlLinesValue); + } + + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS); + if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS); + if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR); + if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + int index = 0; + if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE; + if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE; + if(index != 0) + vendorOut(RESET_HXN_REQUEST, index, null); + } else { + if (purgeWriteBuffers) + vendorOut(FLUSH_RX_REQUEST, 0, null); + if (purgeReadBuffers) + vendorOut(FLUSH_TX_REQUEST, 0, null); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_PROLIFIC, + new int[] { + UsbId.PROLIFIC_PL2303, + UsbId.PROLIFIC_PL2303GC, + UsbId.PROLIFIC_PL2303GB, + UsbId.PROLIFIC_PL2303GT, + UsbId.PROLIFIC_PL2303GL, + UsbId.PROLIFIC_PL2303GE, + UsbId.PROLIFIC_PL2303GS, + }); + return supportedDevices; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java new file mode 100644 index 0000000..271ea51 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java @@ -0,0 +1,15 @@ +package com.bg7yoz.ft8cn.serialport; + +import java.io.InterruptedIOException; + +/** + * Signals that a timeout has occurred on serial write. + * Similar to SocketTimeoutException. + * + * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred + */ +public class SerialTimeoutException extends InterruptedIOException { + public SerialTimeoutException(String s) { + super(s); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java new file mode 100644 index 0000000..73ab2a2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java @@ -0,0 +1,95 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +/** + * Registry of USB vendor/product ID constants. + * + * Culled from various sources; see + * usb.ids for one listing. + * + * @author mike wakerly (opensource@hoho.com) + */ +public final class UsbId { + + public static final int VENDOR_FTDI = 0x0403; + public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_ZERO = 0x0; + public static final int FTDI_FT2232H = 0x6010; + public static final int FTDI_FT4232H = 0x6011; + public static final int FTDI_FT232H = 0x6014; + public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD + + public static final int VENDOR_ATMEL = 0x03EB; + public static final int ATMEL_LUFA_CDC_DEMO_APP = 0x2044; + + public static final int VENDOR_ARDUINO = 0x2341; + public static final int ARDUINO_UNO = 0x0001; + public static final int ARDUINO_MEGA_2560 = 0x0010; + public static final int ARDUINO_SERIAL_ADAPTER = 0x003b; + public static final int ARDUINO_MEGA_ADK = 0x003f; + public static final int ARDUINO_MEGA_2560_R3 = 0x0042; + public static final int ARDUINO_UNO_R3 = 0x0043; + public static final int ARDUINO_MEGA_ADK_R3 = 0x0044; + public static final int ARDUINO_SERIAL_ADAPTER_R3 = 0x0044; + public static final int ARDUINO_LEONARDO = 0x8036; + public static final int ARDUINO_MICRO = 0x8037; + + public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; + public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; + + public static final int CDC_WOLF_PID= 0xF001;// ST CDC WOLF RIG + + public static final int VENDOR_LEAFLABS = 0x1eaf; + public static final int LEAFLABS_MAPLE = 0x0004; + + public static final int VENDOR_SILABS = 0x10c4; + public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 + public static final int SILABS_CP2105 = 0xea70; + public static final int SILABS_CP2108 = 0xea71; + + public static final int VENDOR_PROLIFIC = 0x067b; + public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX + public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN + public static final int PROLIFIC_PL2303GB = 0x23b3; // " + public static final int PROLIFIC_PL2303GT = 0x23c3; // " + public static final int PROLIFIC_PL2303GL = 0x23d3; // " + public static final int PROLIFIC_PL2303GE = 0x23e3; // " + public static final int PROLIFIC_PL2303GS = 0x23f3; // " + + public static final int VENDOR_QINHENG = 0x1a86; + public static final int QINHENG_CH340 = 0x7523; + public static final int QINHENG_CH341A = 0x5523; + public static final int QINHENG_CH341K = 29986; + + + // at www.linux-usb.org/usb.ids listed for NXP/LPC1768, but all processors supported by ARM mbed DAPLink firmware report these ids + public static final int VENDOR_ARM = 0x0d28; + public static final int ARM_MBED = 0x0204; + + public static final int VENDOR_ST = 0x0483; + public static final int ST_CDC = 0x5740; + public static final int ST_CDC2 = 0xA34C; + public static final int ST_CDC3 = 0x5732; + + public static final int VENDOR_RASPBERRY_PI = 0x2e8a; + public static final int RASPBERRY_PI_PICO_MICROPYTHON = 0x0005; + + public static final int VENDOR_GUOHE1 = 0x5678; + public static final int PID_GUOHE1 = 0x6789; + + public static final int VENDOR_ICOM = 0x0c26; + public static final int IC_R30 = 0x002b; + public static final int IC_705 = 0x0036; + public static final int ICOM_USB_SERIAL_CONTROL = 0x0018; + public static final int XIEGU_X6100 = 0x55D2;//xiegu + + private UsbId() { + throw new IllegalAccessError("Non-instantiable class"); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java new file mode 100644 index 0000000..49b1690 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java @@ -0,0 +1,33 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbDevice; + +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialDriver { + + /** + * Returns the raw {@link UsbDevice} backing this port. + * + * @return the device + */ + UsbDevice getDevice(); + + /** + * Returns all available ports for this device. This list must have at least + * one entry. + * + * @return the ports + */ + List getPorts(); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java new file mode 100644 index 0000000..f294da3 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java @@ -0,0 +1,261 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; + +import androidx.annotation.IntDef; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.EnumSet; + +/** + * Interface for a single serial port. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialPort extends Closeable { + + /** 5 data bits. */ + int DATABITS_5 = 5; + /** 6 data bits. */ + int DATABITS_6 = 6; + /** 7 data bits. */ + int DATABITS_7 = 7; + /** 8 data bits. */ + int DATABITS_8 = 8; + + /** Values for setParameters(..., parity) */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) + @interface Parity {} + /** No parity. */ + int PARITY_NONE = 0; + /** Odd parity. */ + int PARITY_ODD = 1; + /** Even parity. */ + int PARITY_EVEN = 2; + /** Mark parity. */ + int PARITY_MARK = 3; + /** Space parity. */ + int PARITY_SPACE = 4; + + /** 1 stop bit. */ + int STOPBITS_1 = 1; + /** 1.5 stop bits. */ + int STOPBITS_1_5 = 3; + /** 2 stop bits. */ + int STOPBITS_2 = 2; + + /** Values for get[Supported]ControlLines() */ + enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } + + /** + * Returns the driver used by this port. + */ + UsbSerialDriver getDriver(); + + /** + * Returns the currently-bound USB device. + */ + UsbDevice getDevice(); + + /** + * Port number within driver. + */ + int getPortNumber(); + + /** + * Returns the write endpoint. + * @return write endpoint + */ + UsbEndpoint getWriteEndpoint(); + + /** + * Returns the read endpoint. + * @return read endpoint + */ + UsbEndpoint getReadEndpoint(); + + /** + * The serial number of the underlying UsbDeviceConnection, or {@code null}. + * + * @return value from {@link UsbDeviceConnection#getSerial()} + * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted + */ + String getSerial(); + + /** + * Opens and initializes the port. Upon success, caller must ensure that + * {@link #close()} is eventually called. + * + * @param connection an open device connection, acquired with + * {@link UsbManager#openDevice(UsbDevice)} + * @throws IOException on error opening or initializing the port. + */ + void open(UsbDeviceConnection connection) throws IOException; + + /** + * Closes the port and {@link UsbDeviceConnection} + * + * @throws IOException on error closing the port. + */ + void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeout the timeout for reading in milliseconds, 0 is infinite + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + int read(final byte[] dest, final int timeout) throws IOException; + + /** + * Writes as many bytes as possible from the source buffer. + * + * @param src the source byte buffer + * @param timeout the timeout for writing in milliseconds, 0 is infinite + * @throws SerialTimeoutException if timeout reached before sending all data. + * ex.bytesTransferred may contain bytes transferred + * @throws IOException if an error occurred during writing + */ + void write(final byte[] src, final int timeout) throws IOException; + + /** + * Sets various serial port parameters. + * + * @param baudRate baud rate as an integer, for example {@code 115200}. + * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. + * @throws IOException on error setting the port parameters + * @throws UnsupportedOperationException if values are not supported by a specific device + */ + void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDTR() throws IOException; + + /** + * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRTS() throws IOException; + + /** + * Sets the RTS (Request To Send) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setRTS(boolean value) throws IOException; + + /** + * Gets all control line values from the underlying UART, if supported. + * Requires less USB calls than calling getRTS() + ... + getRI() individually. + * + * @return EnumSet.contains(...) is {@code true} if set, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getControlLines() throws IOException; + + /** + * Gets all control line supported flags. + * + * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getSupportedControlLines() throws IOException; + + /** + * Purge non-transmitted output data and / or non-read input data. + * + * @param purgeWriteBuffers {@code true} to discard non-transmitted output data + * @param purgeReadBuffers {@code true} to discard non-read input data + * @throws IOException if an error occurred during flush + * @throws UnsupportedOperationException if not supported + */ + void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; + + /** + * send BREAK condition. + * + * @param value set/reset + */ + void setBreak(boolean value) throws IOException; + + /** + * Returns the current state of the connection. + */ + boolean isOpen(); + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java new file mode 100644 index 0000000..9189997 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java @@ -0,0 +1,92 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public class UsbSerialProber { + + private final ProbeTable mProbeTable; + + public UsbSerialProber(ProbeTable probeTable) { + mProbeTable = probeTable; + } + + public static UsbSerialProber getDefaultProber() { + return new UsbSerialProber(getDefaultProbeTable()); + } + + public static ProbeTable getDefaultProbeTable() { + final ProbeTable probeTable = new ProbeTable(); + probeTable.addDriver(CdcAcmSerialDriver.class); + probeTable.addDriver(Cp21xxSerialDriver.class); + probeTable.addDriver(FtdiSerialDriver.class); + probeTable.addDriver(ProlificSerialDriver.class); + probeTable.addDriver(Ch34xSerialDriver.class); + return probeTable; + } + + /** + * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} + * from the currently-attached {@link UsbDevice} hierarchy. This method does + * not require permission from the Android USB system, since it does not + * open any of the devices. + * + * @param usbManager usb manager + * @return a list, possibly empty, of all compatible drivers + */ + public List findAllDrivers(final UsbManager usbManager) { + final List result = new ArrayList<>(); + + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + final UsbSerialDriver driver = probeDevice(usbDevice); + if (driver != null) { + result.add(driver); + } + } + return result; + } + + /** + * Probes a single device for a compatible driver. + * + * @param usbDevice the usb device to probe + * @return a new {@link UsbSerialDriver} compatible with this device, or + * {@code null} if none available. + */ + public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { + final int vendorId = usbDevice.getVendorId(); + final int productId = usbDevice.getProductId(); + + final Class driverClass = + mProbeTable.findDriver(vendorId, productId); + if (driverClass != null) { + final UsbSerialDriver driver; + try { + final Constructor ctor = + driverClass.getConstructor(UsbDevice.class); + driver = ctor.newInstance(usbDevice); + } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return driver; + } + return null; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java new file mode 100644 index 0000000..1fd8688 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java @@ -0,0 +1,14 @@ +package com.bg7yoz.ft8cn.serialport.util; + +public final class MonotonicClock { + + private static final long NS_PER_MS = 1_000_000; + + private MonotonicClock() { + } + + public static long millis() { + return System.nanoTime() / NS_PER_MS; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java new file mode 100644 index 0000000..9c014d9 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java @@ -0,0 +1,257 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.bg7yoz.ft8cn.serialport.util; + +import android.os.Process; +import android.util.Log; + +import com.bg7yoz.ft8cn.serialport.UsbSerialPort; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Utility class which services a {@link ../UsbSerialPort} in its {@link #run()} method. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class SerialInputOutputManager implements Runnable { + + public enum State { + STOPPED, + RUNNING, + STOPPING + } + + public static boolean DEBUG = false; + + private static final String TAG = SerialInputOutputManager.class.getSimpleName(); + private static final int BUFSIZ = 4096; + + /** + * default read timeout is infinite, to avoid data loss with bulkTransfer API + */ + private int mReadTimeout = 0; + private int mWriteTimeout = 0; + + private final Object mReadBufferLock = new Object(); + private final Object mWriteBufferLock = new Object(); + + private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() + private ByteBuffer mWriteBuffer = ByteBuffer.allocate(BUFSIZ); + + private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; + private State mState = State.STOPPED; // Synchronized by 'this' + private Listener mListener; // Synchronized by 'this' + private final UsbSerialPort mSerialPort; + + public interface Listener { + /** + * Called when new incoming data is available. + */ + void onNewData(byte[] data); + + /** + * Called when {@link SerialInputOutputManager#run()} aborts due to an error. + */ + void onRunError(Exception e); + } + + public SerialInputOutputManager(UsbSerialPort serialPort) { + mSerialPort = serialPort; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { + mSerialPort = serialPort; + mListener = listener; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public synchronized void setListener(Listener listener) { + mListener = listener; + } + + public synchronized Listener getListener() { + return mListener; + } + + /** + * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss + * + * @param threadPriority see {@link Process#setThreadPriority(int)} + * */ + public void setThreadPriority(int threadPriority) { + if (mState != State.STOPPED) + throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); + mThreadPriority = threadPriority; + } + + /** + * read/write timeout + */ + public void setReadTimeout(int timeout) { + // when set if already running, read already blocks and the new value will not become effective now + if(mReadTimeout == 0 && timeout != 0 && mState != State.STOPPED) + throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); + mReadTimeout = timeout; + } + + public int getReadTimeout() { + return mReadTimeout; + } + + public void setWriteTimeout(int timeout) { + mWriteTimeout = timeout; + } + + public int getWriteTimeout() { + return mWriteTimeout; + } + + /** + * read/write buffer size + */ + public void setReadBufferSize(int bufferSize) { + if (getReadBufferSize() == bufferSize) + return; + synchronized (mReadBufferLock) { + mReadBuffer = ByteBuffer.allocate(bufferSize); + } + } + + public int getReadBufferSize() { + return mReadBuffer.capacity(); + } + + public void setWriteBufferSize(int bufferSize) { + if(getWriteBufferSize() == bufferSize) + return; + synchronized (mWriteBufferLock) { + ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); + if(mWriteBuffer.position() > 0) + newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); + mWriteBuffer = newWriteBuffer; + } + } + + public int getWriteBufferSize() { + return mWriteBuffer.capacity(); + } + + /** + * when using writeAsync, it is recommended to use readTimeout != 0, + * else the write will be delayed until read data is available + */ + public void writeAsync(byte[] data) { + synchronized (mWriteBufferLock) { + mWriteBuffer.put(data); + } + } + + /** + * start SerialInputOutputManager in separate thread + */ + public void start() { + if(mState != State.STOPPED) + throw new IllegalStateException("already started"); + new Thread(this, this.getClass().getSimpleName()).start(); + } + + /** + * stop SerialInputOutputManager thread + * + * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to + * interrupt blocking read + */ + public synchronized void stop() { + if (getState() == State.RUNNING) { + Log.i(TAG, "Stop requested"); + mState = State.STOPPING; + } + } + + public synchronized State getState() { + return mState; + } + + /** + * Continuously services the read and write buffers until {@link #stop()} is + * called, or until a driver exception is raised. + */ + @Override + public void run() { + synchronized (this) { + if (getState() != State.STOPPED) { + throw new IllegalStateException("Already running"); + } + mState = State.RUNNING; + } + Log.i(TAG, "Running ..."); + try { + if(mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) + Process.setThreadPriority(mThreadPriority); + while (true) { + if (getState() != State.RUNNING) { + Log.i(TAG, "Stopping mState=" + getState()); + break; + } + step(); + } + } catch (Exception e) { + Log.w(TAG, "Run ending due to exception: " + e.getMessage(), e); + final Listener listener = getListener(); + if (listener != null) { + listener.onRunError(e); + } + } finally { + synchronized (this) { + mState = State.STOPPED; + Log.i(TAG, "Stopped"); + } + } + } + + private void step() throws IOException { + // Handle incoming data. + byte[] buffer; + synchronized (mReadBufferLock) { + buffer = mReadBuffer.array(); + } + int len = mSerialPort.read(buffer, mReadTimeout); + if (len > 0) { + if (DEBUG) { + Log.d(TAG, "Read data len=" + len); + } + final Listener listener = getListener(); + if (listener != null) { + final byte[] data = new byte[len]; + System.arraycopy(buffer, 0, data, 0, len); + listener.onNewData(data); + } + } + + // Handle outgoing data. + buffer = null; + synchronized (mWriteBufferLock) { + len = mWriteBuffer.position(); + if (len > 0) { + buffer = new byte[len]; + mWriteBuffer.rewind(); + mWriteBuffer.get(buffer, 0, len); + mWriteBuffer.clear(); + } + } + if (buffer != null) { + if (DEBUG) { + Log.d(TAG, "Writing data len=" + len); + } + mSerialPort.write(buffer, mWriteTimeout); + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java new file mode 100644 index 0000000..1637c92 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java @@ -0,0 +1,41 @@ +package com.bg7yoz.ft8cn.spectrum; +/** + * 用于瀑布图的音频接收。以一个FT8符号为颗粒度。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.wave.HamRecorder; +import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; + +public class SpectrumListener { + private static final String TAG = "SpectrumListener"; + private HamRecorder hamRecorder; + + private float[] dataBuffer=new float[0]; + public MutableLiveData mutableDataBuffer = new MutableLiveData<>(); + + + private final OnGetVoiceDataDone onGetVoiceDataDone=new OnGetVoiceDataDone() { + @Override + public void onGetDone(float[] data) { + mutableDataBuffer.postValue(data); + } + }; + + public SpectrumListener(HamRecorder hamRecorder) { + this.hamRecorder = hamRecorder; + doReceiveData(); + } + + + private void doReceiveData(){ + hamRecorder.getVoiceData(160,false,onGetVoiceDataDone); + } + + public float[] getDataBuffer() { + return dataBuffer; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java new file mode 100644 index 0000000..813b458 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java @@ -0,0 +1,14 @@ +package com.bg7yoz.ft8cn.timer; +/** + * UtcTimer类的一个接口,用于UtcTimer的回调。 + * UtcTimer是一个动作触发器,在一个时钟周期到来时触发动作,触发动作的回调函数是DoOnSecTimer。 + * UtcTimer在以一个固定的频率循环(目前默认时100毫秒),在每一个频率下的回调函数是doHeartBeatTimer。 + * 注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 + * + * @author BG7YOZ + * @date 2022.5.6 + */ +public interface OnUtcTimer { + void doHeartBeatTimer(long utc);//心跳回调,在触发器每一个循环时触发,心跳的只处理简单事务,不要过多占用CPU,防止线程叠加 + void doOnSecTimer(long utc);//当指定的时间间隔时触发 +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java new file mode 100644 index 0000000..85eab93 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java @@ -0,0 +1,301 @@ +package com.bg7yoz.ft8cn.timer; +/** + * UtcTimer类,用于实现FT8在各通联周期开始时触发的动作。FT8的通联因为需要时钟同步,以UTC时间为基准,每15秒一个周期(FT4为7.5秒)。 + * 该类采用Timer和TimerTask来实现定时触发动作。 + * 由于FT8需要时钟同步(精度为秒),在每一个周期开始触发动作,所以,目前以100毫秒为心跳,检测是否处于周期(对UTC时间以周期的秒数取模)的开始, + * 如果是,则回调doHeartBeatTimer函数,为防止重复动作,触发后会等待1秒钟后再进入新的心跳周期(因为是以秒数取模)。 + * 注意!!为防止回调动作占用时间过长,影响下一个动作的触发,所以,回调都是以多线程的方式调用,在使用时要注意线程安全。 + *

+ * @author BG7YOZ + * @date 2022.5.7 + */ + +import android.annotation.SuppressLint; + +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import org.apache.commons.net.ntp.NTPUDPClient; +import org.apache.commons.net.ntp.TimeInfo; + +import java.io.IOException; +import java.net.InetAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + + +public class UtcTimer { + private final int sec; + private final boolean doOnce; + private final OnUtcTimer onUtcTimer; + + + private long utc; + public static int delay = 0;//时钟总的延时,(毫秒) + private boolean running = false;//用来判断是否触发周期的动作 + + private final Timer secTimer = new Timer(); + private final Timer heartBeatTimer = new Timer(); + private int time_sec = 0;//时间的偏移量; + private final ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); + private final Runnable doSomething = new Runnable() { + @Override + public void run() { + onUtcTimer.doOnSecTimer(utc); + } + }; + private final ExecutorService heartBeatThreadPool = Executors.newCachedThreadPool(); + private final Runnable doHeartBeat = new Runnable() { + @Override + public void run() { + onUtcTimer.doHeartBeatTimer(utc); + } + }; + + /** + * 类方法。获得UTC时间的字符串表示结果。 + * + * @param time 时间。 + * @return String 以字符串方式显示UTC时间。 + */ + @SuppressLint("DefaultLocale") + public static String getTimeStr(long time) { + long curtime = time / 1000; + long hour = ((curtime) / (60 * 60)) % 24;//小时 + long sec = (curtime) % 60;//秒 + long min = ((curtime) % 3600) / 60;//分 + return String.format("UTC : %02d:%02d:%02d", hour, min, sec); + } + + /** + * 以HHMMSS格式显示UTC时间 + * + * @param time + * @return + */ + @SuppressLint("DefaultLocale") + public static String getTimeHHMMSS(long time) { + long curtime = time / 1000; + long hour = ((curtime) / (60 * 60)) % 24;//小时 + long sec = (curtime) % 60;//秒 + long min = ((curtime) % 3600) / 60;//分 + return String.format("%02d%02d%02d", hour, min, sec); + } + + public static String getYYYYMMDD(long time) { + @SuppressLint("SimpleDateFormat") + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + return simpleDateFormat.format(new Date(time)); + } + + public static String getDatetimeStr(long time) { + @SuppressLint("SimpleDateFormat") + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + return simpleDateFormat.format(new Date(time)); + } + + public static String getDatetimeYYYYMMDD_HHMMSS(long time) { + @SuppressLint("SimpleDateFormat") + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss"); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + return simpleDateFormat.format(new Date(time)); + } + + /** + * 时钟触发器的构建方法。需要确定时钟的周期,周期一般是15秒或7.5秒,因为周期的参数是int,所以参数的单位是十分之一秒。 + * 由于心跳频率较快(暂时定为100毫秒),心跳的动作越简练越好,要在下一个心跳开始之前处理完,防止造成线程叠加,影响性能。 + * 心跳动作不会因周期动作不触发(running==false)而影响,只要UtcTimer的实例存在,心跳动作就运行(方便显示时钟数据)。 + * 该触发器需要调用delete函数彻底停止(心跳动作也停止了)。 + * + * @param sec 时钟的周期,单位是十分之一秒,如:15秒,值150,7.5秒,值75。 + * @param doOnce 是否只触发一次。 + * @param onUtcTimer 回调函数,包括心跳回调,和周期起始触发动作的回调。 + */ + public UtcTimer(int sec, boolean doOnce, OnUtcTimer onUtcTimer) { + this.sec = sec; + this.doOnce = doOnce; + this.onUtcTimer = onUtcTimer; + + //初始化Timer的任务。 + //TimerTask timerTask = initTask(); + //执行timer,延时0执行,周期100毫秒 + + secTimer.schedule(secTask(), 0, 10); + heartBeatTimer.schedule(heartBeatTask(), 0, 1000); + } + + /** + * 定义时钟触发的动作。 + * 时钟触发器的构建方法。需要确定时钟的周期,周期一般是15秒或7.5秒,因为周期的参数是int,所以参数的单位是十分之一秒。 + * 由于心跳频率较快(暂时定为100毫秒),心跳的动作越简练越好,要在下一个心跳开始之前处理完,防止造成线程叠加,影响性能。 + * 心跳动作不会因周期动作不触发(running==false)而影响,只要UtcTimer的实例存在,心跳动作就运行(方便显示时钟数据)。 + * + * @return TimerTask 返回动作的实例。 + */ + + + private TimerTask heartBeatTask() { + return new TimerTask() { + @Override + public void run() { + //心跳动作 + doHeartBeatEvent(onUtcTimer); + } + }; + } + + private TimerTask secTask() { + return new TimerTask() { + + + @Override + public void run() { + + try { + utc = getSystemTime();//获取当前的UTC时间 + //utc/100是取十分之一秒为单位,所以取模应该是600,而非60,切记! + //running是判断是否需要触发周期动作。 + //+80是因为触发后一些动作影响,补偿的时间 + //time_sec是时间的偏移量 + if (running && (((utc - time_sec) / 100) % 600) % sec == 0) { + //周期动作 + //注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 + cachedThreadPool.execute(doSomething);//用线程池的方式调用,减少系统消耗 + //thread.run(); + + //如果只执行一次触发动作 + if (doOnce) { + running = false; + return; + } + + //等待1秒钟,防止重复触发动作。 + Thread.sleep(1000); + } + + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + } + + /** + * 触发心跳时的动作。由Timer调用,写此函数是方便阅读。动作是在新创建的线程中执行。 + * + * @param onUtcTimer 触发时钟的回调函数。 + */ + private void doHeartBeatEvent(OnUtcTimer onUtcTimer) { + //心跳动作 + heartBeatThreadPool.execute(doHeartBeat); +// new Thread(new Runnable() { +// @Override +// public void run() { +// //注意!!!! doHeartBeatTimer不要执行耗时的操作,一定要在心跳间隔内完成,否则可能会造成线程的积压,影响性能。 +// onUtcTimer.doHeartBeatTimer(utc); +// } +// }).start(); + } + + + public void stop() { + running = false; + } + + public void start() { + running = true; + } + + public boolean isRunning() { + return running; + } + + public void delete() { + secTimer.cancel(); + heartBeatTimer.cancel(); + } + + /** + * 设置时间偏移量,正值是向后偏移 + * + * @param time_sec 向前的偏移量 + */ + public void setTime_sec(int time_sec) { + this.time_sec = time_sec; + } + + /** + * 获取时间偏移 + * + * @return 时间偏移值(毫秒) + */ + public int getTime_sec() { + return time_sec; + } + + public long getUtc() { + return utc; + } + + /** + * 根据UTC时间计算时序 + * + * @param utc UTC时间 + * @return 时序:0,1 + */ + public static int sequential(long utc) { + return (int) ((((utc) / 1000) / 15) % 2); + } + + public static int getNowSequential() { + return sequential(getSystemTime()); + } + + public static long getSystemTime() { + return delay + System.currentTimeMillis(); + } + + /** + * 使用微软的时间服务器同步时间 + */ + public static void syncTime(AfterSyncTime afterSyncTime) { + new Thread(new Runnable() { + @Override + public void run() { + NTPUDPClient timeClient = new NTPUDPClient(); + InetAddress inetAddress = null; + TimeInfo timeInfo = null; + try { + inetAddress = InetAddress.getByName("time.windows.com"); + timeInfo = timeClient.getTime(inetAddress); + long serverTime = timeInfo.getMessage().getTransmitTimeStamp().getTime(); + int trueDelay = (int) ((serverTime - System.currentTimeMillis())); + UtcTimer.delay = trueDelay % 15000;//延迟的周期 + if (afterSyncTime != null) { + afterSyncTime.doAfterSyncTimer(trueDelay); + } + } catch (IOException e) { + if (afterSyncTime != null) { + afterSyncTime.syncFailed(e); + } + } + + //long localDeviceTime = timeInfo.getReturnTime(); + + } + }).start(); + } + + public interface AfterSyncTime { + void doAfterSyncTimer(int secTime); + + void syncFailed(IOException e); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java new file mode 100644 index 0000000..44925c4 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java @@ -0,0 +1,54 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 频段列表界面 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.OperationBand; + +public class BandsSpinnerAdapter extends BaseAdapter { + private final Context mContext; + //private final OperationBand operationBand; + public BandsSpinnerAdapter(Context context) { + //operationBand= + OperationBand.getInstance(context); + mContext=context; + } + + @Override + public int getCount() { + return OperationBand.bandList.size(); + } + + @Override + public Object getItem(int i) { + return OperationBand.bandList.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.operation_band_spinner_item, null); + if (view!=null){ + TextView textView=view.findViewById(R.id.operationBandItemTextView); + textView.setText(OperationBand.getBandInfo(i)); + } + return view; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java new file mode 100644 index 0000000..bf48f65 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java @@ -0,0 +1,63 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 波特率列表界面 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.R; + + +public class BauRateSpinnerAdapter extends BaseAdapter { + private final Context mContext; + private final int[] bauRates= {4800,9600,14400,19200,38400,43000,56000,57600,115200}; + public BauRateSpinnerAdapter(Context context) { + mContext=context; + } + + @Override + public int getCount() { + return bauRates.length; + } + + @Override + public Object getItem(int i) { + return bauRates[i]; + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.bau_rate_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.bauRateItemTextView); + textView.setText(String.valueOf(bauRates[i])); + } + return view; + } + public int getPosition(int i){ + for (int j = 0; j < bauRates.length; j++) { + if (bauRates[j]==i){ + return j; + } + } + return 2; + } + public int getValue(int position){ + return bauRates[position]; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java new file mode 100644 index 0000000..ddfb0f9 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java @@ -0,0 +1,426 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 消息列表Adapter。使用此Adapter有解码界面、呼叫界面、网格追踪界面。 + * 不同周期背景不同。为了区分,共有4种背景颜色。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Paint; +import android.opengl.Visibility; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; + +public class CallingListAdapter extends RecyclerView.Adapter { + public enum ShowMode{CALLING_LIST,MY_CALLING,TRACKER} + private static final String TAG = "CallingListAdapter"; + private final MainViewModel mainViewModel; + private final ArrayList ft8MessageArrayList; + private final Context context; + + //private boolean isCallingList = true; + private final ShowMode showMode; + private View.OnClickListener onItemClickListener; + + private final View.OnCreateContextMenuListener menuListener=new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { + + //view.setTag(ft8Message);//把消息对象传递给上一级界面 + int postion= (int) view.getTag(); + if (postion==-1) return; + if (postion>ft8MessageArrayList.size()-1) return; + Ft8Message ft8Message=ft8MessageArrayList.get(postion); + + //添加菜单的参数i1:组,i2:id值,i3:显示顺序 + if (!ft8Message.getCallsignTo().contains("...")//目标不能是自己 + && !ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + && !(ft8Message.i3==0&&ft8Message.n3==0)) { + if (!ft8Message.checkIsCQ()) { + if (showMode==ShowMode.CALLING_LIST) {//在消息列表中就可以显示这个菜单了 + contextMenu.add(0, 0, 0, String.format( + GeneralVariables.getStringFromResource(R.string.tracking_receiver) + , ft8Message.getCallsignTo(), ft8Message.toWhere)) + .setActionView(view); + } + if (!mainViewModel.ft8TransmitSignal.isSynFrequency()) {//如果同频率的话,会与发送者同频,会影响发送者!!! + contextMenu.add(0, 1, 0, String.format( + GeneralVariables.getStringFromResource(R.string.calling_receiver) + , ft8Message.getCallsignTo(), ft8Message.toWhere)) + .setActionView(view); + } + //说明是对我呼叫,加上回复菜单 + if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign)) { + contextMenu.add(0, 4, 0, String.format( + GeneralVariables.getStringFromResource(R.string.reply_to) + , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) + .setActionView(view); + + } + if (showMode!=ShowMode.TRACKER) { + contextMenu.add(0, 5, 0 + , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) + , ft8Message.getCallsignTo())).setActionView(view); + } + + } + } + + if (!ft8Message.getCallsignFrom().contains("...") + && !ft8Message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !(ft8Message.i3==0&&ft8Message.n3==0)) { + if (showMode==ShowMode.CALLING_LIST) {//在消息列表中就可以显示这个菜单了 + contextMenu.add(1, 2, 0, String.format( + GeneralVariables.getStringFromResource(R.string.tracking) + , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) + .setActionView(view); + } + contextMenu.add(1, 3, 0, String.format( + GeneralVariables.getStringFromResource(R.string.calling) + , ft8Message.getCallsignFrom(), ft8Message.fromWhere)) + .setActionView(view); + if (showMode!=ShowMode.TRACKER) { + contextMenu.add(1, 6, 0 + , String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s) + , ft8Message.getCallsignFrom())).setActionView(view); + } + } + + } + }; + + + + public CallingListAdapter(Context context, MainViewModel mainViewModel + , ArrayList messages, ShowMode showMode) { + this.mainViewModel = mainViewModel; + this.context = context; + //this.isCallingList = isCallingList; + this.showMode=showMode; + ft8MessageArrayList = messages; + } + + @NonNull + @Override + public CallingListItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.call_list_holder_item, parent, false); + return new CallingListItemHolder(view,onItemClickListener,menuListener); + } + + /** + * 删除消息 + * + * @param position 在列表中的位置 + */ + public void deleteMessage(int position) { + if (position >= 0) { + ft8MessageArrayList.remove(position); + } + } + + public Ft8Message getMessageByPosition(int position){ + if (ft8MessageArrayList==null) return null; + if (position<0) return null; + if (position>ft8MessageArrayList.size()-1) return null; + return ft8MessageArrayList.get(position); + } + + /** + * 通过holder获取消息 + * + * @param holder holder + * @return ft8message + */ + public Ft8Message getMessageByViewHolder(RecyclerView.ViewHolder holder) { + if (holder.getAdapterPosition() == -1) { + return null; + } + return ft8MessageArrayList.get(holder.getAdapterPosition()); + } + + @SuppressLint("ResourceAsColor") + @Override + public void onBindViewHolder(@NonNull CallingListItemHolder holder, int position) { + holder.callListHolderConstraintLayout.setTag(position);//设置layout的tag,为了识别消息的定位 + holder.ft8Message = ft8MessageArrayList.get(position); + holder.showMode = showMode;//确定是消息列表还是关注消息的列表 + holder.isSyncFreq = mainViewModel.ft8TransmitSignal.isSynFrequency();//如果同频发射,就不显示呼叫接收者 + + holder.callingUtcTextView.setText(UtcTimer.getTimeHHMMSS(holder.ft8Message.utcTime)); + //时序,包括颜色, + holder.callingListSequenceTextView.setText(holder.ft8Message.getSequence() == 0 ? "0" : "1"); + holder.isWeakSignalImageView.setVisibility(holder.ft8Message.isWeakSignal ? View.VISIBLE:View.INVISIBLE); + + if (showMode==ShowMode.MY_CALLING) {//在呼叫界面 + holder.callingListSequenceTextView.setTextColor(context.getColor(R.color.follow_call_text_color)); + } + + //根据1分钟内的4个时序区分颜色 + switch (holder.ft8Message.getSequence4()) { + case 0: + holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style); + break; + case 1: + holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style); + break; + case 2: + holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_2_style); + break; + case 3: + holder.callListHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_3_style); + break; + } + + holder.callingListIdBTextView.setText(holder.ft8Message.getdB()); + //时间偏移,如果超过1.0秒,-0.05秒,红色提示 + holder.callListDtTextView.setText(holder.ft8Message.getDt()); + if (holder.ft8Message.time_sec > 1.0f || holder.ft8Message.time_sec < -0.05) { + holder.callListDtTextView.setTextColor(context.getResources().getColor( + R.color.message_in_my_call_text_color)); + } else { + holder.callListDtTextView.setTextColor(context.getResources().getColor( + R.color.text_view_color)); + } + + + holder.callingListFreqTextView.setText(holder.ft8Message.getFreq_hz()); + + //查是不是通联过的呼号,获取是否存在holder.otherBandIsQso中 + setQueryHolderQSL_Callsign(holder); + + //是否有与我呼号有关的消息 + if (holder.ft8Message.inMyCall()) { + holder.callListMessageTextView.setTextColor(context.getResources().getColor( + R.color.message_in_my_call_text_color)); + } else if (holder.otherBandIsQso) { + //设置在别的波段通联过的消息颜色 + holder.callListMessageTextView.setTextColor(context.getResources().getColor( + R.color.fromcall_is_qso_text_color)); + } else { + holder.callListMessageTextView.setTextColor(context.getResources().getColor( + R.color.message_text_color)); + } + + + holder.callListMessageTextView.setText(holder.ft8Message.getMessageText(true)); + + //载波频率 + holder.bandItemTextView.setText(BaseRigOperation.getFrequencyStr(holder.ft8Message.band)); + //计算距离 + holder.callingListDistTextView.setText(MaidenheadGrid.getDistStr( + GeneralVariables.getMyMaidenheadGrid() + , holder.ft8Message.getMaidenheadGrid(mainViewModel.databaseOpr))); + holder.callingListCallsignToTextView.setText("");//被呼叫者 + holder.callingListCallsignFromTextView.setText("");//呼叫者 + + //消息类型 + holder.callingListCommandIInfoTextView.setText(holder.ft8Message.getCommandInfo()); + if (holder.ft8Message.i3 == 1 || holder.ft8Message.i3 == 2) { + holder.callingListCommandIInfoTextView.setTextColor(context.getResources().getColor( + R.color.text_view_color)); + } else { + holder.callingListCommandIInfoTextView.setTextColor(context.getResources().getColor( + R.color.message_in_my_call_text_color)); + } + + //设置是否CQ的颜色 + if (holder.ft8Message.checkIsCQ()) { + holder.callListMessageTextView.setBackgroundResource(R.color.textview_cq_color); + holder.ft8Message.toWhere = ""; + } else { + holder.callListMessageTextView.setBackgroundResource(R.color.textview_none_color); + } + + + if (holder.ft8Message.fromWhere != null) { + holder.callingListCallsignFromTextView.setText(holder.ft8Message.fromWhere); + } else { + holder.callingListCallsignFromTextView.setText(""); + } + + if (holder.ft8Message.toWhere != null) { + holder.callingListCallsignToTextView.setText(holder.ft8Message.toWhere); + } else { + holder.callingListCallsignToTextView.setText(""); + } + + //给没有通联过的分区打标记 + setToDxcc(holder); + setFromDxcc(holder); + + + //查询呼号归属地,为防止占用太多运算资源,当from为空是再做查询的工作 +// if (holder.ft8Message.fromWhere == null) { +// setQueryHolderCallsign(holder);//查询呼号归属地 +// } + + if (holder.ft8Message.freq_hz <= 0.01f) {//这是发射 + //holder.callingListSequenceTextView.setVisibility(View.GONE); + holder.callingListIdBTextView.setVisibility(View.GONE); + holder.callListDtTextView.setVisibility(View.GONE); + holder.callingListFreqTextView.setText("TX"); + holder.bandItemTextView.setVisibility(View.GONE); + holder.callingListDistTextView.setVisibility(View.GONE); + holder.callingListCommandIInfoTextView.setVisibility(View.GONE); + holder.callingUtcTextView.setVisibility(View.GONE); + holder.callingListCallsignToTextView.setVisibility(View.GONE); + holder.callingListCallsignFromTextView.setVisibility(View.GONE); + holder.dxccToImageView.setVisibility(View.GONE); + holder.ituToImageView.setVisibility(View.GONE); + holder.cqToImageView.setVisibility(View.GONE); + holder.dxccFromImageView.setVisibility(View.GONE); + holder.ituFromImageView.setVisibility(View.GONE); + holder.cqFromImageView.setVisibility(View.GONE); + } else { + //holder.callingListSequenceTextView.setVisibility(View.VISIBLE); + holder.callingListIdBTextView.setVisibility(View.VISIBLE); + holder.callListDtTextView.setVisibility(View.VISIBLE); + holder.bandItemTextView.setVisibility(View.VISIBLE); + holder.callingListDistTextView.setVisibility(View.VISIBLE); + holder.callingListCommandIInfoTextView.setVisibility(View.VISIBLE); + holder.callingUtcTextView.setVisibility(View.VISIBLE); + holder.callingListCallsignToTextView.setVisibility(View.VISIBLE); + holder.callingListCallsignFromTextView.setVisibility(View.VISIBLE); + } + } + + private void setFromDxcc(@NonNull CallingListItemHolder holder) { + + if (holder.ft8Message.fromDxcc && holder.ft8Message.freq_hz > 0.01f) { + holder.dxccFromImageView.setVisibility(View.VISIBLE); + } else { + holder.dxccFromImageView.setVisibility(View.GONE); + } + + if (holder.ft8Message.fromCq && holder.ft8Message.freq_hz > 0.01f) { + holder.cqFromImageView.setVisibility(View.VISIBLE); + } else { + holder.cqFromImageView.setVisibility(View.GONE); + } + + if (holder.ft8Message.fromItu && holder.ft8Message.freq_hz > 0.01f) { + holder.ituFromImageView.setVisibility(View.VISIBLE); + } else { + holder.ituFromImageView.setVisibility(View.GONE); + } + } + + private void setToDxcc(@NonNull CallingListItemHolder holder) { + if (holder.ft8Message.toDxcc && holder.ft8Message.freq_hz > 0.01f) { + holder.dxccToImageView.setVisibility(View.VISIBLE); + } else { + holder.dxccToImageView.setVisibility(View.GONE); + } + + if (holder.ft8Message.toCq && holder.ft8Message.freq_hz > 0.01f) { + holder.cqToImageView.setVisibility(View.VISIBLE); + } else { + holder.cqToImageView.setVisibility(View.GONE); + } + + if (holder.ft8Message.toItu && holder.ft8Message.freq_hz > 0.01f) { + holder.ituToImageView.setVisibility(View.VISIBLE); + } else { + holder.ituToImageView.setVisibility(View.GONE); + } + } + + //检查是不是通联过的呼号 + private void setQueryHolderQSL_Callsign(@NonNull CallingListItemHolder holder) { + //查是不是在本波段内通联成功过的呼号 + if (GeneralVariables.checkQSLCallsign(holder.ft8Message.getCallsignFrom())) {//如果在数据库中,划线 + holder.callListMessageTextView.setPaintFlags( + holder.callListMessageTextView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else {//如果不在数据库中,去掉划线 + holder.callListMessageTextView.setPaintFlags( + holder.callListMessageTextView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); + } + holder.otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(holder.ft8Message.getCallsignFrom()); + } + + @Override + public int getItemCount() { + return ft8MessageArrayList.size(); + } + + public void setOnItemClickListener(View.OnClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + static class CallingListItemHolder extends RecyclerView.ViewHolder { + private static final String TAG = "CallingListItemHolder"; + ConstraintLayout callListHolderConstraintLayout; + TextView callingListIdBTextView, callListDtTextView, callingListFreqTextView, + callListMessageTextView, callingListDistTextView, callingListSequenceTextView, + callingListCallsignFromTextView, callingListCallsignToTextView + , callingListCommandIInfoTextView, + bandItemTextView, callingUtcTextView; + ImageView dxccToImageView, ituToImageView, cqToImageView, dxccFromImageView + , ituFromImageView, cqFromImageView,isWeakSignalImageView; + public Ft8Message ft8Message; + //boolean showFollow; + ShowMode showMode; + boolean isSyncFreq; + boolean otherBandIsQso = false; + + + public CallingListItemHolder(@NonNull View itemView, View.OnClickListener listener + ,View.OnCreateContextMenuListener menuListener) { + super(itemView); + callListHolderConstraintLayout = itemView.findViewById(R.id.callListHolderConstraintLayout); + callingListIdBTextView = itemView.findViewById(R.id.callingListIdBTextView); + callListDtTextView = itemView.findViewById(R.id.callListDtTextView); + callingListFreqTextView = itemView.findViewById(R.id.callingListFreqTextView); + callListMessageTextView = itemView.findViewById(R.id.callListMessageTextView); + callingListDistTextView = itemView.findViewById(R.id.callingListDistTextView); + callingListSequenceTextView = itemView.findViewById(R.id.callingListSequenceTextView); + callingListCallsignFromTextView = itemView.findViewById(R.id.callingListCallsignFromTextView); + callingListCallsignToTextView = itemView.findViewById(R.id.callToItemTextView); + callingListCommandIInfoTextView = itemView.findViewById(R.id.callingListCommandIInfoTextView); + bandItemTextView = itemView.findViewById(R.id.bandItemTextView); + callingUtcTextView = itemView.findViewById(R.id.callingUtcTextView); + + dxccToImageView = itemView.findViewById(R.id.dxccToImageView); + ituToImageView = itemView.findViewById(R.id.ituToImageView); + cqToImageView = itemView.findViewById(R.id.cqToImageView); + dxccFromImageView = itemView.findViewById(R.id.dxccFromImageView); + ituFromImageView = itemView.findViewById(R.id.ituFromImageView); + cqFromImageView = itemView.findViewById(R.id.cqFromImageView); + isWeakSignalImageView=itemView.findViewById(R.id.isWeakSignalImageView); + + dxccToImageView.setVisibility(View.GONE); + ituToImageView.setVisibility(View.GONE); + cqToImageView.setVisibility(View.GONE); + dxccFromImageView.setVisibility(View.GONE); + ituFromImageView.setVisibility(View.GONE); + cqFromImageView.setVisibility(View.GONE); + itemView.setTag(-1); + itemView.setOnClickListener(listener); + itemView.setOnCreateContextMenuListener(menuListener); + + } + + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java new file mode 100644 index 0000000..d561c86 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListFragment.java @@ -0,0 +1,416 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 解码界面 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.databinding.FragmentCallingListBinding; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; + +public class CallingListFragment extends Fragment { + private static final String TAG = "CallingListFragment"; + + private FragmentCallingListBinding binding; + private RecyclerView callListRecyclerView; + private CallingListAdapter callingListAdapter; + private MainViewModel mainViewModel; + + + @SuppressLint("NotifyDataSetChanged") + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + mainViewModel = MainViewModel.getInstance(this); + binding = FragmentCallingListBinding.inflate(inflater, container, false); + callListRecyclerView = binding.callingListRecyclerView; + + callingListAdapter = new CallingListAdapter(this.getContext(), mainViewModel + , mainViewModel.ft8Messages, CallingListAdapter.ShowMode.CALLING_LIST); + callListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + callListRecyclerView.setAdapter(callingListAdapter); + callingListAdapter.notifyDataSetChanged(); + callListRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); + + + requireActivity().registerForContextMenu(callListRecyclerView); + + //当横屏时显示频谱图 + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + assert binding.spectrumView != null; + binding.spectrumView.run(mainViewModel, this); + } + //设置呼号滑动,用于快速呼叫 + initRecyclerViewAction(); + + //监听按钮 + binding.timerImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mainViewModel.hamRecorder.isRunning()) { + mainViewModel.hamRecorder.stopRecord(); + mainViewModel.ft8TransmitSignal.setActivated(false); + } else { + mainViewModel.hamRecorder.startRecord(); + } + } + }); + //清除按钮 + binding.clearCallingListImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mainViewModel.clearFt8MessageList(); + callingListAdapter.notifyDataSetChanged(); + mainViewModel.mutable_Decoded_Counter.setValue(0); + } + }); + //观察解码数量 + mainViewModel.mutable_Decoded_Counter.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Integer integer) { + binding.decoderCounterTextView.setText( + String.format(GeneralVariables.getStringFromResource(R.string.message_count_count) + , mainViewModel.currentDecodeCount, mainViewModel.ft8Messages.size())); + + + } + }); + + mainViewModel.mutableFt8MessageList.observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(ArrayList messages) { + callingListAdapter.notifyDataSetChanged(); + //当列表下部稍微多出一些,自动上移 + if (callListRecyclerView.computeVerticalScrollRange() + - callListRecyclerView.computeVerticalScrollExtent() + - callListRecyclerView.computeVerticalScrollOffset() < 500) { + callListRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); + } + } + }); + + //观察UTC时间 + mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Long aLong) { + binding.timerTextView.setText(UtcTimer.getTimeStr(aLong)); + } + }); + + //观察时间偏移 + mainViewModel.mutableTimerOffset.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Float aFloat) { + binding.timeOffsetTextView.setText(String.format( + getString(R.string.average_offset_seconds), aFloat)); + } + }); + + //显示梅登海德网格 + GeneralVariables.mutableMyMaidenheadGrid.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String s) { + binding.maidenheadTextView.setText(String.format( + getString(R.string.my_grid), s)); + } + }); + + //观察是否处于解码状态 + mainViewModel.mutableIsDecoding.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + binding.isDecodingTextView.setText(getString(R.string.decoding)); + } + } + }); + + //观察解码的时长 + mainViewModel.ft8SignalListener.decodeTimeSec.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Long aLong) { + binding.isDecodingTextView.setText(String.format( + getString(R.string.decoding_takes_milliseconds), aLong)); + } + }); + + //以闪烁动画的方式显示录音状态 + mainViewModel.mutableIsRecording.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_red_48); + binding.timerImageButton.setAnimation(AnimationUtils.loadAnimation(getContext() + , R.anim.view_blink)); + } else { + if (mainViewModel.hamRecorder.isRunning()) { + binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_48); + } else { + binding.timerImageButton.setImageResource(R.drawable.ic_baseline_mic_off_48); + } + binding.timerImageButton.setAnimation(null); + } + } + }); + + return binding.getRoot(); + } + + /** + * 设置列表滑动动作 + */ + private void initRecyclerViewAction() { + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG + , ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder + , @NonNull RecyclerView.ViewHolder target) { + return false; + } + + //@RequiresApi(api = Build.VERSION_CODES.N) + @SuppressLint("NotifyDataSetChanged") + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + if (direction == ItemTouchHelper.START) {//呼叫 + Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); + if (message != null) { + //呼叫的目标不能是自己 + if (!message.getCallsignFrom().equals("<...>") + && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !(message.i3 == 0 && message.n3 == 0)) { + doCallNow(message); + } else { + callingListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); + } + } + } + if (direction == ItemTouchHelper.END) {//删除 + callingListAdapter.deleteMessage(viewHolder.getAdapterPosition()); + callingListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); + } + + + } + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView + , @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY + , int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + //制作呼叫背景的图标显示 + final Drawable callIcon = ContextCompat.getDrawable(requireActivity() + , R.drawable.ic_baseline_send_red_48); + final Drawable delIcon = ContextCompat.getDrawable(requireActivity() + , R.drawable.log_item_delete_icon); + final Drawable background = new ColorDrawable(Color.LTGRAY); + Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder); + if (message == null) { + return; + } + if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 + return; + } + Drawable icon; + if (dX > 0) { + icon = delIcon; + } else { + icon = callIcon; + } + View itemView = viewHolder.itemView; + int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + int iconLeft, iconRight, iconTop, iconBottom; + int backTop, backBottom, backLeft, backRight; + backTop = itemView.getTop(); + backBottom = itemView.getBottom(); + iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + iconBottom = iconTop + icon.getIntrinsicHeight(); + if (dX > 0) { + backLeft = itemView.getLeft(); + backRight = itemView.getLeft() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconLeft = itemView.getLeft() + iconMargin; + iconRight = iconLeft + icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else if (dX < 0) { + backRight = itemView.getRight(); + backLeft = itemView.getRight() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconRight = itemView.getRight() - iconMargin; + iconLeft = iconRight - icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else { + background.setBounds(0, 0, 0, 0); + icon.setBounds(0, 0, 0, 0); + } + background.draw(c); + icon.draw(c); + + } + }).attachToRecyclerView(binding.callingListRecyclerView); + } + + /** + * 马上对发起者呼叫 + * + * @param message 消息 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + private boolean doCallNow(Ft8Message message) { + + mainViewModel.addFollowCallsign(message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() + , 1, message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + navigateToMyCallFragment();//跳转到发射界面 + return true; + } + + /** + * 跳转到发射界面 + */ + private void navigateToMyCallFragment() { + NavController navController = Navigation.findNavController(requireActivity() + , R.id.fragmentContainerView); + navController.navigate(R.id.action_menu_nav_calling_list_to_menu_nav_mycalling);//跳转到发射界面 + } + + /** + * 菜单选项 + * + * @param item 菜单 + * @return 是否 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + + //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); + int position = (int) item.getActionView().getTag(); + Ft8Message ft8Message = callingListAdapter.getMessageByPosition(position); + if (ft8Message == null) return super.onContextItemSelected(item); + ; + switch (item.getItemId()) { + case 0: + Log.d(TAG, "关注:" + ft8Message.getCallsignTo()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignTo()); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + break; + case 1://时序与发送者相反!!! + Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignTo()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + //呼叫被呼叫对象 + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() + , 1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + + navigateToMyCallFragment();//跳转到发射界面 + break; + case 2: + Log.d(TAG, "关注:" + ft8Message.getCallsignFrom()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + break; + case 3: + Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); + doCallNow(ft8Message); + break; + + case 4://回复 + Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() + , -1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + navigateToMyCallFragment();//跳转到发射界面 + break; + case 5://to 的QRZ + showQrzFragment(ft8Message.getCallsignTo()); + break; + case 6://from 的QRZ + showQrzFragment(ft8Message.getCallsignFrom()); + break; + + } + + return super.onContextItemSelected(item); + } + + /** + * 显示QRZ查询界面 + * + * @param callsign 呼号 + */ + private void showQrzFragment(String callsign) { + NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); + assert navHostFragment != null;//断言不为空 + Bundle bundle = new Bundle(); + bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); + navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java new file mode 100644 index 0000000..86ec2ed --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java @@ -0,0 +1,234 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 清除缓存的对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + +public class ClearCacheDataDialog extends Dialog { + public static enum CACHE_MODE {FOLLOW_DATA, SWL_MSG,SWL_QSO} + + private static final String TAG = "HelpDialog"; + private final Context context; + private final Activity activity; + private ImageView upImageView; + private ImageView downImageView; + private ScrollView scrollView; + private TextView cacheHelpMessage; + private TextView appNameTextView; + private TextView buildVersionTextView; + private CACHE_MODE cache_mode; + private DatabaseOpr db; + private final Timer timer = new Timer(); + + private TimerTask timeEvent() { + return new TimerTask() { + @Override + public void run() { + //心跳动作 + //doHeartBeatEvent(onUtcTimer); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + setImageVisible(); + } + }); + + } + }; + } + + + public ClearCacheDataDialog(@NonNull Context context, Activity activity, DatabaseOpr db, CACHE_MODE cache_mode) { + super(context, R.style.HelpDialog); + this.context = context; + this.activity = activity; + this.cache_mode = cache_mode; + this.db=db; + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.clear_cache_dialog_layout); + cacheHelpMessage = (TextView) findViewById(R.id.cacheHelpMessage); + appNameTextView = (TextView) findViewById(R.id.appNameTextView); + buildVersionTextView = (TextView) findViewById(R.id.buildVersionTextView); + //cacheHelpMessage.setText(msg); + upImageView = (ImageView) findViewById(R.id.scrollUpImageView); + downImageView = (ImageView) findViewById(R.id.scrollDownImageView); + scrollView = (ScrollView) findViewById(R.id.helpScrollView); + upImageView.setVisibility(View.INVISIBLE); + downImageView.setVisibility(View.INVISIBLE); + appNameTextView.setText(GeneralVariables.getStringFromResource(R.string.app_name)); + buildVersionTextView.setText(BuildConfig.VERSION_NAME); + + Button cancelButton=(Button) findViewById(R.id.cancelClearButton); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dismiss(); + } + }); + + StringBuilder msg = new StringBuilder(); + if (cache_mode == CACHE_MODE.FOLLOW_DATA) { + msg.append(GeneralVariables.getStringFromResource(R.string.html_tracking_callsign)); + for (int i = 0; i < GeneralVariables.followCallsign.size(); i++) { + msg.append("\n" + GeneralVariables.followCallsign.get(i)); + } + cacheHelpMessage.setText(msg.toString()); + } + //解码的消息 + if (cache_mode==CACHE_MODE.SWL_MSG){ + db.getMessageLogTotal(new OnAfterQueryFollowCallsigns() { + @Override + public void doOnAfterQueryFollowCallsigns(ArrayList callsigns) { + StringBuilder msg=new StringBuilder(); + msg.append(GeneralVariables.getStringFromResource(R.string.log_statistics_decode_msg)); + for (int i = 0; i callsigns) { + StringBuilder msg=new StringBuilder(); + msg.append(GeneralVariables.getStringFromResource(R.string.log_statistics_swl_qso)); + for (int i = 0; i height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.9); + } else { + params.width = (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + + } + + + public String getTextFromAssets(String fileName) { + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream = assetManager.open(fileName); + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + inputStream.close(); + + return new String(bytes); + + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java new file mode 100644 index 0000000..470feb5 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java @@ -0,0 +1,164 @@ +package com.bg7yoz.ft8cn.ui; + +import static android.graphics.Bitmap.Config.ARGB_8888; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +/** + * 柱状频谱图。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class ColumnarView extends View { + private static final String TAG = "ColumnarView"; + //每一个能量柱的宽度 + private int width; + //每一个能量柱之间的间距 + private final int spacing = 1; + //能量块高度 + private final int blockHeight = 5; + //能量块下将速度 + private int blockSpeed = 5; + //能量块与能量柱之间的距离 + private final int distance = 2; + + private boolean drawblock = false; + private final Paint paint = new Paint(); + private final List newData = new ArrayList<>(); + private final List blockData = new ArrayList<>(); + + private Bitmap lastBitMap=null; + private Canvas _canvas; + private Paint linePaint; + private int touch_x = -1; + private Paint touchPaint; + private int freq_hz=-1; + + public void setBlockSpeed(int blockSpeed) { + this.blockSpeed = blockSpeed; + } + + public ColumnarView(Context context) { + super(context); + + } + + public ColumnarView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ColumnarView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setShowBlock(boolean showBlock) { + drawblock = showBlock; + } + + public void setWaveData(int[] data) { + if (data == null) { + return; + } + if (data.length <= 0) { + return; + } + width = getWidth() / (data.length / 2);// 960/2=480,480比较合理,906无法显示了。 + if (drawblock) {//是否显示能量块 + if (newData.size() > 0) { + if (blockData.size() == 0 || newData.size() != blockData.size()) { + blockData.clear(); + for (int i = 0; i < data.length / 2; i++) { + Rect blockRect = new Rect(); + blockRect.top =getHeight()- blockHeight; + blockRect.bottom = getHeight(); + blockData.add(blockRect); + } + } + for (int i = 0; i < blockData.size(); i++) { + blockData.get(i).left = newData.get(i).left; + blockData.get(i).right = newData.get(i).right; + if (newData.get(i).top < blockData.get(i).top) { + blockData.get(i).top = newData.get(i).top - blockHeight - distance; + } else { + blockData.get(i).top = blockData.get(i).top + blockSpeed; + } + blockData.get(i).bottom = blockData.get(i).top + blockHeight; + } + } + } + newData.clear(); + float rateHeight = 0.95f * getHeight() / 256;//0.95是比率,柱形的最大高度不超过95% + for (int i = 0; i < data.length / 2; i++) { + Rect colRect = new Rect(); + if (newData.size() == 0) { + colRect.left = 0; + } else { + colRect.left = i * getWidth() / (data.length / 2); + } + colRect.top = getHeight() - Math.round(Math.max(data[i], data[i + 1]) * rateHeight); + colRect.right = colRect.left + width - spacing; + colRect.bottom = getHeight(); + newData.add(colRect); + } + } + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setClickable(true); + super.onSizeChanged(w, h, oldw, oldh); + + lastBitMap= Bitmap.createBitmap(w,h, ARGB_8888); + _canvas=new Canvas(lastBitMap); + LinearGradient linearGradient=new LinearGradient(0f, 0f, 0f, getHeight(), + new int[]{0xff00ffff,0xff00ffff, Color.BLUE} + , new float[]{0f, 0.6f, 1f}, Shader.TileMode.CLAMP); + paint.setShader(linearGradient); + linePaint = new Paint(); + linePaint.setColor(0xff990000); + touchPaint = new Paint(); + touchPaint.setColor(0xff00ffff); + touchPaint.setStrokeWidth(2); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + _canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + for (int i = 0; i < newData.size(); i++) { + _canvas.drawRect(newData.get(i), paint); + } + if (drawblock) { + for (int i = 0; i < blockData.size(); i++) { + _canvas.drawRect(blockData.get(i), paint); + } + } + canvas.drawBitmap(lastBitMap,0,0,null); + if (touch_x>0) { + //计算频率 + freq_hz = Math.round(3000f * (float) touch_x / (float) getWidth()); + canvas.drawLine(touch_x, 0, touch_x, getHeight(), touchPaint); + } + invalidate(); + } + public void setTouch_x(int touch_x) { + this.touch_x = touch_x; + } + + public int getFreq_hz() { + return freq_hz; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java new file mode 100644 index 0000000..9e69fa1 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java @@ -0,0 +1,1235 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 设置界面。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CompoundButton; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; + +import com.bg7yoz.ft8cn.FAQActivity; +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.database.OperationBand; +import com.bg7yoz.ft8cn.database.RigNameList; +import com.bg7yoz.ft8cn.databinding.FragmentConfigBinding; +import com.bg7yoz.ft8cn.ft8signal.FT8Package; +import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; +import com.bg7yoz.ft8cn.rigs.InstructionSet; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.io.IOException; + +/** + * A simple {@link Fragment} subclass. + * create an instance of this fragment. + */ +public class ConfigFragment extends Fragment { + private static final String TAG = "ConfigFragment"; + private MainViewModel mainViewModel; + private FragmentConfigBinding binding; + private BandsSpinnerAdapter bandsSpinnerAdapter; + private BauRateSpinnerAdapter bauRateSpinnerAdapter; + private RigNameSpinnerAdapter rigNameSpinnerAdapter; + private LaunchSupervisionSpinnerAdapter launchSupervisionSpinnerAdapter; + private PttDelaySpinnerAdapter pttDelaySpinnerAdapter; + private NoReplyLimitSpinnerAdapter noReplyLimitSpinnerAdapter; + //private SerialPortSpinnerAdapter serialPortSpinnerAdapter; + + public ConfigFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + //我的网格位置 + private final TextWatcher onGridEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + //String s = ""; + StringBuilder s=new StringBuilder(); + for (int j = 0; j < binding.inputMyGridEdit.getText().length(); j++) { + if (j < 2) { + //s = s + Character.toUpperCase(binding.inputMyGridEdit.getText().charAt(j)); + s.append(Character.toUpperCase(binding.inputMyGridEdit.getText().charAt(j))); + } else { + //s = s + Character.toLowerCase(binding.inputMyGridEdit.getText().charAt(j)); + s.append(Character.toLowerCase(binding.inputMyGridEdit.getText().charAt(j))); + } + } + writeConfig("grid", s.toString()); + GeneralVariables.setMyMaidenheadGrid(s.toString()); + } + }; + //我的呼号 + private final TextWatcher onMyCallEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + writeConfig("callsign", editable.toString().toUpperCase().trim()); + String callsign = editable.toString().toUpperCase().trim(); + if (callsign.length() > 0) { + Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); + Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); + Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); + } + GeneralVariables.myCallsign = (editable.toString().toUpperCase().trim()); + } + }; + //发射频率 + private final TextWatcher onFreqEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + setfreq(editable.toString()); + } + }; + //发射延迟时间 + private final TextWatcher onTransDelayEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + int transDelay = 1000; + if (editable.toString().matches("^\\d{1,4}$")) { + transDelay = Integer.parseInt(editable.toString()); + } + GeneralVariables.transmitDelay = transDelay; + mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay); + writeConfig("transDelay", Integer.toString(transDelay)); + } + }; + + //排除的呼号前缀 + private final TextWatcher onExcludedCallsigns=new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + GeneralVariables.addExcludedCallsigns(editable.toString()); + writeConfig("excludedCallsigns", GeneralVariables.getExcludeCallsigns()); + } + }; + + //修饰符 + private final TextWatcher onModifierEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (editable.toString().toUpperCase().trim().matches("[0-9]{3}|[A-Z]{1,4}") + ||editable.toString().trim().length()==0){ + binding.modifierEdit.setTextColor(requireContext().getColor(R.color.text_view_color)); + GeneralVariables.toModifier=editable.toString().toUpperCase().trim(); + writeConfig("toModifier", GeneralVariables.toModifier); + }else{ + binding.modifierEdit.setTextColor(requireContext().getColor(R.color.text_view_error_color)); + } + } + }; + + //CI-V地址 + private final TextWatcher onCIVAddressEditorChanged = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (editable.toString().length() < 2) { + return; + } + String s = "0x" + editable.toString(); + if (s.matches("\\b0[xX][0-9a-fA-F]+\\b")) {//匹配十六进制 + String temp = editable.toString().substring(0, 2).toUpperCase(); + writeConfig("civ", temp); + GeneralVariables.civAddress = Integer.parseInt(temp, 16); + mainViewModel.setCivAddress(); + } + } + }; + + + @SuppressLint("DefaultLocale") + private void setfreq(String sFreq) { + float freq; + try { + freq = Float.parseFloat(sFreq); + if (freq < 100) { + freq = 100; + } + if (freq > 2900) { + freq = 2900; + } + } catch (Exception e + ) { + freq = 1000; + } + + + writeConfig("freq", String.format("%.0f", freq)); + GeneralVariables.setBaseFrequency(freq); + } + + @SuppressLint("SetTextI18n") + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mainViewModel = MainViewModel.getInstance(this); + binding = FragmentConfigBinding.inflate(inflater, container, false); + + //只对中国开方问题收集 +// if (GeneralVariables.isChina) { +// binding.faqButton.setVisibility(View.VISIBLE); +// } else { +// binding.faqButton.setVisibility(View.GONE); +// } + + + //设置时间偏移 + setUtcTimeOffsetSpinner(); + + //设置PTT延时 + setPttDelaySpinner(); + + //设置操作频段 + setBandsSpinner(); + + //设置波特率列表 + setBauRateSpinner(); + + //设置电台名称,参数列表 + setRigNameSpinner(); + + //设置解码模式 + setDecodeMode(); + + //设置音频输出的位数 + setAudioOutputBitsMode(); + + //设置音频输出采样率 + setAudioOutputRateMode(); + + //设置控制模式 VOX CAT + setControlMode(); + + //设置连线的方式 + setConnectMode(); + + //设置发射监管列表 + setLaunchSupervision(); + + //设置帮助对话框 + setHelpDialog(); + + //设置无回应次数中断 + setNoReplyLimitSpinner(); + + //显示滚动箭头 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setScrollImageVisible(); + } + }, 1000); + binding.scrollView3.setOnScrollChangeListener(new View.OnScrollChangeListener() { + @Override + public void onScrollChange(View view, int i, int i1, int i2, int i3) { + setScrollImageVisible(); + } + }); + + //FAQ按钮的onClick + binding.faqButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(requireContext(), FAQActivity.class); + startActivity(intent); + } + }); + + //梅登海德网格 + binding.inputMyGridEdit.removeTextChangedListener(onGridEditorChanged); + binding.inputMyGridEdit.setText(GeneralVariables.getMyMaidenheadGrid()); + binding.inputMyGridEdit.addTextChangedListener(onGridEditorChanged); + + //我的呼号 + binding.inputMycallEdit.removeTextChangedListener(onMyCallEditorChanged); + binding.inputMycallEdit.setText(GeneralVariables.myCallsign); + binding.inputMycallEdit.addTextChangedListener(onMyCallEditorChanged); + + //修饰符 + binding.modifierEdit.removeTextChangedListener(onModifierEditorChanged); + binding.modifierEdit.setText(GeneralVariables.toModifier); + binding.modifierEdit.addTextChangedListener(onModifierEditorChanged); + + //发射频率 + binding.inputFreqEditor.removeTextChangedListener(onFreqEditorChanged); + binding.inputFreqEditor.setText(GeneralVariables.getBaseFrequencyStr()); + binding.inputFreqEditor.addTextChangedListener(onFreqEditorChanged); + + + + //CIV地址 + binding.civAddressEdit.removeTextChangedListener(onCIVAddressEditorChanged); + binding.civAddressEdit.setText(GeneralVariables.getCivAddressStr()); + binding.civAddressEdit.addTextChangedListener(onCIVAddressEditorChanged); + + //发射延迟 + binding.inputTransDelayEdit.removeTextChangedListener(onTransDelayEditorChanged); + binding.inputTransDelayEdit.setText(GeneralVariables.getTransmitDelayStr()); + binding.inputTransDelayEdit.addTextChangedListener(onTransDelayEditorChanged); + + binding.excludedCallsignEdit.removeTextChangedListener(onExcludedCallsigns); + binding.excludedCallsignEdit.setText(GeneralVariables.getExcludeCallsigns()); + binding.excludedCallsignEdit.addTextChangedListener(onExcludedCallsigns); + + + //设置同频发射开关 + binding.synFrequencySwitch.setOnCheckedChangeListener(null); + binding.synFrequencySwitch.setChecked(GeneralVariables.synFrequency); + setSyncFreqText();//设置开关的文本 + binding.synFrequencySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + if (binding.synFrequencySwitch.isChecked()) { + mainViewModel.databaseOpr.writeConfig("synFreq", "1", null); + } else { + mainViewModel.databaseOpr.writeConfig("synFreq", "0", null); + setfreq(binding.inputFreqEditor.getText().toString()); + } + GeneralVariables.synFrequency = binding.synFrequencySwitch.isChecked(); + setSyncFreqText(); + binding.inputFreqEditor.setEnabled(!binding.synFrequencySwitch.isChecked()); + } + }); + + //设置PTT延迟 + binding.pttDelayOffsetSpinner.setOnItemSelectedListener(null); + binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); + binding.pttDelayOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.pttDelay = i * 10; + writeConfig("pttDelay", String.valueOf(GeneralVariables.pttDelay)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + + //获取操作的波段 + binding.operationBandSpinner.setOnItemSelectedListener(null); + binding.operationBandSpinner.setSelection(GeneralVariables.bandListIndex); + binding.operationBandSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.bandListIndex = i; + GeneralVariables.band = OperationBand.getBandFreq(i);//把当前的频段保存下来 + + mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 + writeConfig("bandFreq", String.valueOf(GeneralVariables.band)); + if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + //如果在CAT、RTS模式下,修改电台的频率 + mainViewModel.setOperationBand(); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + + //获取电台型号 + binding.rigNameSpinner.setOnItemSelectedListener(null); + binding.rigNameSpinner.setSelection(GeneralVariables.modelNo); + new Handler().postDelayed(new Runnable() {//延迟2秒修改OnItemSelectedListener + @Override + public void run() { + binding.rigNameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.modelNo = i; + writeConfig("model", String.valueOf(i)); + setAddrAndBauRate(rigNameSpinnerAdapter.getRigName(i)); + + //指令集 + GeneralVariables.instructionSet = rigNameSpinnerAdapter.getRigName(i).instructionSet; + writeConfig("instruction", String.valueOf(GeneralVariables.instructionSet)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + } + }, 2000); + + + //获取波特率 + binding.baudRateSpinner.setOnItemSelectedListener(null); + binding.baudRateSpinner.setSelection(bauRateSpinnerAdapter.getPosition( + GeneralVariables.baudRate)); + binding.baudRateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.baudRate = bauRateSpinnerAdapter.getValue(i); + writeConfig("baudRate", String.valueOf(GeneralVariables.baudRate)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + + //设置发射监管 + binding.launchSupervisionSpinner.setOnItemSelectedListener(null); + binding.launchSupervisionSpinner.setSelection(launchSupervisionSpinnerAdapter + .getPosition(GeneralVariables.launchSupervision)); + binding.launchSupervisionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.launchSupervision = LaunchSupervisionSpinnerAdapter.getTimeOut(i); + writeConfig("launchSupervision", String.valueOf(GeneralVariables.launchSupervision)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + //设置无回应中断 + binding.noResponseCountSpinner.setOnItemSelectedListener(null); + binding.noResponseCountSpinner.setSelection(GeneralVariables.noReplyLimit); + binding.noResponseCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.noReplyLimit = i; + writeConfig("noReplyLimit", String.valueOf(GeneralVariables.noReplyLimit)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + + //设置自动关注CQ + binding.followCQSwitch.setOnCheckedChangeListener(null); + binding.followCQSwitch.setChecked(GeneralVariables.autoFollowCQ); + setAutoFollowCQText(); + binding.followCQSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + GeneralVariables.autoFollowCQ = binding.followCQSwitch.isChecked(); + if (binding.followCQSwitch.isChecked()) { + mainViewModel.databaseOpr.writeConfig("autoFollowCQ", "1", null); + } else { + mainViewModel.databaseOpr.writeConfig("autoFollowCQ", "0", null); + } + setAutoFollowCQText(); + } + }); + + + + //设置自动呼叫关注的呼号 + binding.autoCallfollowSwitch.setOnCheckedChangeListener(null); + binding.autoCallfollowSwitch.setChecked(GeneralVariables.autoCallFollow); + setAutoCallFollow(); + binding.autoCallfollowSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + GeneralVariables.autoCallFollow = binding.autoCallfollowSwitch.isChecked(); + if (binding.autoCallfollowSwitch.isChecked()) { + mainViewModel.databaseOpr.writeConfig("autoCallFollow", "1", null); + } else { + mainViewModel.databaseOpr.writeConfig("autoCallFollow", "0", null); + } + setAutoCallFollow(); + } + }); + + //设置保存SWL选项 + binding.saveSWLSwitch.setOnCheckedChangeListener(null); + binding.saveSWLSwitch.setChecked(GeneralVariables.saveSWLMessage); + setSaveSwl(); + binding.saveSWLSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + GeneralVariables.saveSWLMessage = binding.saveSWLSwitch.isChecked(); + if (binding.saveSWLSwitch.isChecked()) { + mainViewModel.databaseOpr.writeConfig("saveSWL", "1", null); + } else { + mainViewModel.databaseOpr.writeConfig("saveSWL", "0", null); + } + setSaveSwl(); + } + }); + + //设置保存SWL选项 + binding.saveSWLQSOSwitch.setOnCheckedChangeListener(null); + binding.saveSWLQSOSwitch.setChecked(GeneralVariables.saveSWLMessage); + setSaveSwlQSO(); + binding.saveSWLQSOSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + GeneralVariables.saveSWL_QSO = binding.saveSWLQSOSwitch.isChecked(); + if (binding.saveSWLQSOSwitch.isChecked()) { + mainViewModel.databaseOpr.writeConfig("saveSWLQSO", "1", null); + } else { + mainViewModel.databaseOpr.writeConfig("saveSWLQSO", "0", null); + } + setSaveSwlQSO(); + } + }); + + + //获取梅登海德网格 + binding.configGetGridImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String grid = MaidenheadGrid.getMyMaidenheadGrid(getContext()); + if (!grid.equals("")) { + binding.inputMyGridEdit.setText(grid); + } + } + }); + + + return binding.getRoot(); + } + + /** + * 设置地址和波特率,指令集 + * + * @param rigName 电台型号 + */ + private void setAddrAndBauRate(RigNameList.RigName rigName) { + //mainViewModel.setCivAddress(rigName.address); + GeneralVariables.civAddress = rigName.address; + mainViewModel.setCivAddress(); + GeneralVariables.baudRate = rigName.bauRate; + binding.civAddressEdit.setText(String.format("%X", rigName.address)); + binding.baudRateSpinner.setSelection( + bauRateSpinnerAdapter.getPosition(rigName.bauRate)); + } + + + /** + * 设置同频发射开关的显示文本 + */ + private void setSyncFreqText() { + if (binding.synFrequencySwitch.isChecked()) { + binding.synFrequencySwitch.setText(getString(R.string.freq_syn)); + } else { + binding.synFrequencySwitch.setText(getString(R.string.freq_asyn)); + } + } + + /** + * 设置自动关注CQ开关的文本 + */ + private void setAutoFollowCQText() { + if (binding.followCQSwitch.isChecked()) { + binding.followCQSwitch.setText(getString(R.string.auto_follow_cq)); + } else { + binding.followCQSwitch.setText(getString(R.string.not_concerned_about_CQ)); + } + } + + //设置自动呼叫关注的呼号 + private void setAutoCallFollow() { + if (binding.autoCallfollowSwitch.isChecked()) { + binding.autoCallfollowSwitch.setText(getString(R.string.automatic_call_following)); + } else { + binding.autoCallfollowSwitch.setText(getString(R.string.do_not_call_the_following_callsign)); + } + } + private void setSaveSwl() { + if (binding.saveSWLSwitch.isChecked()) { + binding.saveSWLSwitch.setText(getString(R.string.config_save_swl)); + } else { + binding.saveSWLSwitch.setText(getString(R.string.config_donot_save_swl)); + } + } + private void setSaveSwlQSO() { + if (binding.saveSWLQSOSwitch.isChecked()) { + binding.saveSWLQSOSwitch.setText(getString(R.string.config_save_swl_qso)); + } else { + binding.saveSWLQSOSwitch.setText(getString(R.string.config_donot_save_swl_qso)); + } + } + /** + * 设置UTC时间偏移的spinner + */ + private void setUtcTimeOffsetSpinner() { + UtcOffsetSpinnerAdapter adapter = new UtcOffsetSpinnerAdapter(requireContext()); + + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + binding.utcTimeOffsetSpinner.setAdapter(adapter); + adapter.notifyDataSetChanged(); + binding.utcTimeOffsetSpinner.setSelection((UtcTimer.delay / 100 + 75) / 5); + } + }); + binding.utcTimeOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + UtcTimer.delay = i * 500 - 7500;//设置延迟 + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + } + + /** + * 设置操作频段的spinner + */ + private void setBandsSpinner() { + GeneralVariables.mutableBandChange.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer integer) { + binding.operationBandSpinner.setSelection(integer); + } + }); + + + bandsSpinnerAdapter = new BandsSpinnerAdapter(requireContext()); + binding.operationBandSpinner.setAdapter(bandsSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + bandsSpinnerAdapter.notifyDataSetChanged(); + } + }); + + } + + /** + * 设置波特率列表 + */ + private void setBauRateSpinner() { + bauRateSpinnerAdapter = new BauRateSpinnerAdapter(requireContext()); + binding.baudRateSpinner.setAdapter(bauRateSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + bauRateSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + + /** + * 设置无回应次数中断 + */ + private void setNoReplyLimitSpinner() { + noReplyLimitSpinnerAdapter = new NoReplyLimitSpinnerAdapter(requireContext()); + binding.noResponseCountSpinner.setAdapter(noReplyLimitSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + noReplyLimitSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + + /** + * 设置发射监管列表 + */ + private void setLaunchSupervision() { + launchSupervisionSpinnerAdapter = new LaunchSupervisionSpinnerAdapter(requireContext()); + binding.launchSupervisionSpinner.setAdapter(launchSupervisionSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + launchSupervisionSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + + /** + * 设置电台名称,参数列表 + */ + private void setRigNameSpinner() { + rigNameSpinnerAdapter = new RigNameSpinnerAdapter(requireContext()); + binding.rigNameSpinner.setAdapter(rigNameSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + rigNameSpinnerAdapter.notifyDataSetChanged(); + } + }); + + } + + /** + * 设置PTT延时 + */ + private void setPttDelaySpinner() { + pttDelaySpinnerAdapter = new PttDelaySpinnerAdapter(requireContext()); + binding.pttDelayOffsetSpinner.setAdapter(pttDelaySpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + pttDelaySpinnerAdapter.notifyDataSetChanged(); + binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); + } + }); + } + + + private void setDecodeMode() { + binding.decodeModeRadioGroup.clearCheck(); + binding.fastDecodeRadioButton.setChecked(!GeneralVariables.deepDecodeMode); + binding.deepDecodeRadioButton.setChecked(GeneralVariables.deepDecodeMode); + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.decodeModeRadioGroup.getCheckedRadioButtonId(); + GeneralVariables.deepDecodeMode= buttonId ==binding.deepDecodeRadioButton.getId(); + writeConfig("deepMode", GeneralVariables.deepDecodeMode? "1" : "0"); + } + }; + + binding.fastDecodeRadioButton.setOnClickListener(listener); + binding.deepDecodeRadioButton.setOnClickListener(listener); + + } + + + /** + * 设置音频输出的位数 + */ + private void setAudioOutputBitsMode() { + //binding.controlModeRadioGroup.setOnCheckedChangeListener(null); + binding.audioBitsRadioGroup.clearCheck(); + binding.audio32BitsRadioButton.setChecked(GeneralVariables.audioOutput32Bit); + binding.audio16BitsRadioButton.setChecked(!GeneralVariables.audioOutput32Bit); + + + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.audioBitsRadioGroup.getCheckedRadioButtonId(); + GeneralVariables.audioOutput32Bit= buttonId ==binding.audio32BitsRadioButton.getId(); + writeConfig("audioBits", GeneralVariables.audioOutput32Bit? "1" : "0"); + } + }; + + binding.audio32BitsRadioButton.setOnClickListener(listener); + binding.audio16BitsRadioButton.setOnClickListener(listener); + + } + + /** + * 输出音频的采样率设置 + */ + private void setAudioOutputRateMode() { + binding.audioRateRadioGroup.clearCheck(); + binding.audio12kRadioButton.setChecked(GeneralVariables.audioSampleRate==12000); + binding.audio24kRadioButton.setChecked(GeneralVariables.audioSampleRate==24000); + binding.audio48kRadioButton.setChecked(GeneralVariables.audioSampleRate==48000); + + + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (binding.audio12kRadioButton.isChecked()) GeneralVariables.audioSampleRate=12000; + if (binding.audio24kRadioButton.isChecked()) GeneralVariables.audioSampleRate=24000; + if (binding.audio48kRadioButton.isChecked()) GeneralVariables.audioSampleRate=48000; + writeConfig("audioRate", String.valueOf(GeneralVariables.audioSampleRate)); + } + }; + + binding.audio12kRadioButton.setOnClickListener(listener); + binding.audio24kRadioButton.setOnClickListener(listener); + binding.audio48kRadioButton.setOnClickListener(listener); + + } + + + + /** + * 设置控制模式VOX CAT + */ + private void setControlMode() { + //binding.controlModeRadioGroup.setOnCheckedChangeListener(null); + binding.controlModeRadioGroup.clearCheck(); + + switch (GeneralVariables.controlMode) { + case ControlMode.CAT: + case ConnectMode.NETWORK: + binding.ctrCATradioButton.setChecked(true); + break; + case ControlMode.RTS: + binding.ctrRTSradioButton.setChecked(true); + break; + case ControlMode.DTR: + binding.ctrDTRradioButton.setChecked(true); + break; + default: + binding.ctrVOXradioButton.setChecked(true); + } + + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.controlModeRadioGroup.getCheckedRadioButtonId(); + + if (buttonId == binding.ctrVOXradioButton.getId()) { + GeneralVariables.controlMode = ControlMode.VOX; + } else if (buttonId == binding.ctrCATradioButton.getId()) {//CAT模式 + GeneralVariables.controlMode = ControlMode.CAT; + } else if (buttonId == binding.ctrRTSradioButton.getId()) {//RTS模式 + GeneralVariables.controlMode = ControlMode.RTS; + } else if (buttonId == binding.ctrDTRradioButton.getId()) {//RTS模式 + GeneralVariables.controlMode = ControlMode.DTR; + } + mainViewModel.setControlMode();//通知一下电台控制模式改变 + //无论CAT还是RTS,CI-V指令还是有效的,都是串口 + if (GeneralVariables.controlMode == ControlMode.CAT + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + if (!mainViewModel.isRigConnected()) { + mainViewModel.getUsbDevice(); + } else { + mainViewModel.setOperationBand(); + } + } + writeConfig("ctrMode", String.valueOf(GeneralVariables.controlMode)); + setConnectMode(); + } + }; + + binding.ctrCATradioButton.setOnClickListener(listener); + binding.ctrVOXradioButton.setOnClickListener(listener); + binding.ctrRTSradioButton.setOnClickListener(listener); + binding.ctrDTRradioButton.setOnClickListener(listener); + } + + /** + * 设置连线的方式,可以是USB,也可以是BLUE_TOOTH + */ + private void setConnectMode() { + if (GeneralVariables.controlMode == ControlMode.CAT + //&& BluetoothConstants.checkBluetoothIsOpen() + ) { + //此处要改成VISIBLE + binding.connectModeLayout.setVisibility(View.VISIBLE); + } else { + binding.connectModeLayout.setVisibility(View.GONE); + } + binding.connectModeRadioGroup.clearCheck(); + switch (GeneralVariables.connectMode) { + case ConnectMode.USB_CABLE: + binding.cableConnectRadioButton.setChecked(true); + break; + case ConnectMode.BLUE_TOOTH: + binding.bluetoothConnectRadioButton.setChecked(true); + break; + case ConnectMode.NETWORK: + binding.networkConnectRadioButton.setChecked(true); + break; + } + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.connectModeRadioGroup.getCheckedRadioButtonId(); + if (buttonId == binding.cableConnectRadioButton.getId()) { + GeneralVariables.connectMode = ConnectMode.USB_CABLE; + } else if (buttonId == binding.bluetoothConnectRadioButton.getId()) { + GeneralVariables.connectMode = ConnectMode.BLUE_TOOTH; + }else if (buttonId==binding.networkConnectRadioButton.getId()){ + GeneralVariables.connectMode=ConnectMode.NETWORK; + } + //------显示蓝牙列表,并选择,然后建立蓝牙连接 + if (GeneralVariables.connectMode == ConnectMode.BLUE_TOOTH) { + //根据安卓12,要判断一下蓝牙权限: + new SelectBluetoothDialog(requireContext(), mainViewModel).show(); + } + + //-----显示网络上的电台,目前是flex电台,------------------- + if (GeneralVariables.connectMode==ConnectMode.NETWORK){ + //打开网络电台列表对话框 + if (GeneralVariables.instructionSet== InstructionSet.FLEX_NETWORK) { + new SelectFlexRadioDialog(requireContext(), mainViewModel).show(); + }else if(GeneralVariables.instructionSet== InstructionSet.ICOM) { + new LoginIcomRadioDialog(requireContext(), mainViewModel).show(); + }else { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.only_flex_supported)); + } + } + + } + }; + binding.cableConnectRadioButton.setOnClickListener(listener); + binding.bluetoothConnectRadioButton.setOnClickListener(listener); + binding.networkConnectRadioButton.setOnClickListener(listener); + } + + + /** + * 把配置信息写到数据库 + * + * @param KeyName 关键词 + * @param Value 值 + */ + private void writeConfig(String KeyName, String Value) { + mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); + } + + private void setHelpDialog() { + //呼号帮助 + binding.callsignHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.callsign_help) + , true).show(); + } + }); + //梅登海德网格的帮助 + binding.maidenGridImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.maidenhead_help) + , true).show(); + } + }); + //发射频率的帮助 + binding.frequencyImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.frequency_help) + , true).show(); + } + }); + //延迟发射帮助 + binding.transDelayImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.transDelay_help) + , true).show(); + } + }); + //时间偏移帮助 + binding.timeOffsetImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.timeoffset_help) + , true).show(); + } + }); + //PTT延时帮助 + binding.pttDelayImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.pttdelay_help) + , true).show(); + } + }); + //设置ABOUT + binding.aboutButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity(), "readme.txt", true).show(); + } + }); + //设置操作频段 + binding.operationHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.operationBand_help) + , true).show(); + } + }); + //设置操作模式 + binding.controlModeHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.controlMode_help) + , true).show(); + } + }); + //设置CI-V地址和波特率帮助 + binding.baudRateHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.civ_help) + , true).show(); + } + }); + //电台型号列表 + binding.rigNameHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.rig_model_help) + , true).show(); + } + }); + //发射监管 + binding.launchSupervisionImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.launch_supervision_help) + , true).show(); + } + }); + //无回应次数 + binding.noResponseCountButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.no_response_help) + , true).show(); + } + }); + //自动呼叫 + binding.autoFollowCountButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.auto_follow_help) + , true).show(); + } + }); + //连接模式 + binding.connectModeHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.connectMode_help) + , true).show(); + } + }); + //排除选项 + binding.excludedHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.excludeCallsign_help) + , true).show(); + } + }); + + binding.swlHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.swlMode_help) + , true).show(); + } + }); + + //解码模式 + binding.decodeModeHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(),requireActivity() + ,GeneralVariables.getStringFromResource(R.string.deep_mode_help) + ,true).show(); + } + }); + + //音频输出帮助 + binding.audioOutputImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.audio_output_help) + , true).show(); + } + }); + + //清除缓存 + binding.clearCacheHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.clear_cache_data_help) + , true).show(); + } + }); + binding.clearFollowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new ClearCacheDataDialog(requireContext(), requireActivity() + ,mainViewModel.databaseOpr + ,ClearCacheDataDialog.CACHE_MODE.FOLLOW_DATA).show(); + } + }); + binding.clearLogCacheButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new ClearCacheDataDialog(requireContext(), requireActivity() + ,mainViewModel.databaseOpr + ,ClearCacheDataDialog.CACHE_MODE.SWL_MSG).show(); + } + }); + binding.clearSWlQsoButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new ClearCacheDataDialog(requireContext(), requireActivity() + ,mainViewModel.databaseOpr + ,ClearCacheDataDialog.CACHE_MODE.SWL_QSO).show(); + } + }); + + binding.synTImeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + UtcTimer.syncTime(new UtcTimer.AfterSyncTime() { + @Override + public void doAfterSyncTimer(int secTime) { + setUtcTimeOffsetSpinner(); + if (secTime>100) {//正数时慢了 + ToastMessage.show(String.format(GeneralVariables + .getStringFromResource(R.string.utc_time_sync_delay_slow), secTime)); + }else if (secTime<-100){ + ToastMessage.show(String.format(GeneralVariables + .getStringFromResource(R.string.utc_time_sync_delay_faster), -secTime)); + }else { + ToastMessage.show(GeneralVariables + .getStringFromResource(R.string.config_clock_is_accurate)); + } + } + + @Override + public void syncFailed(IOException e) { + ToastMessage.show(e.getMessage()); + } + }); + + } + }); + + } + + /** + * 设置界面的上下滚动的图标 + */ + private void setScrollImageVisible() { + + if (binding.scrollView3.getScrollY() == 0) { + binding.configScrollUpImageView.setVisibility(View.GONE); + } else { + binding.configScrollUpImageView.setVisibility(View.VISIBLE); + } + + if (binding.scrollView3.getHeight() + binding.scrollView3.getScrollY() + < binding.scrollLinearLayout.getMeasuredHeight()) { + binding.configScrollDownImageView.setVisibility(View.VISIBLE); + } else { + binding.configScrollDownImageView.setVisibility(View.GONE); + } + } + + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java new file mode 100644 index 0000000..917bc4f --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java @@ -0,0 +1,95 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 日志查询的过滤对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.RadioButton; + +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; + +public class FilterDialog extends Dialog { + private static final String TAG = "FilterDialog"; + + private MainViewModel mainViewModel; + private RadioButton filterAllButton,filterIsQslButton,filterNoneQslButton; + + public FilterDialog(Context context,MainViewModel mainViewModel) { + super(context, R.style.HelpDialog); + this.mainViewModel=mainViewModel; + + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.filter_dialog_layout); + filterAllButton= (RadioButton) findViewById(R.id.filterAllRadioButton); + filterIsQslButton= (RadioButton) findViewById(R.id.filterIsQSLRadioButton); + filterNoneQslButton= (RadioButton) findViewById(R.id.filterNoneQSLRadioButton); + + View.OnClickListener onClickListener=new View.OnClickListener() { + @Override + public void onClick(View view) { + //Log.e(TAG, "onClick: ---------------->" ); + if (filterAllButton.isChecked()){ + mainViewModel.queryFilter=0; + mainViewModel.mutableQueryFilter.postValue(0); + } + if (filterIsQslButton.isChecked()){ + mainViewModel.queryFilter=1; + mainViewModel.mutableQueryFilter.postValue(1); + } + if (filterNoneQslButton.isChecked()){ + mainViewModel.queryFilter=2; + mainViewModel.mutableQueryFilter.postValue(2); + } + FilterDialog.this.dismiss(); + } + }; + filterAllButton.setOnClickListener(onClickListener); + filterIsQslButton.setOnClickListener(onClickListener); + filterNoneQslButton.setOnClickListener(onClickListener); + } + + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); + params.height = (int) (height * 0.6); + if (width>height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.6); + }else { + params.width= (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + switch (mainViewModel.queryFilter){ + case 1: + filterIsQslButton.setChecked(true);//只显示QSL的 + break; + case 2: + filterNoneQslButton.setChecked(true);//只显示没有QSL的 + break; + default: + filterAllButton.setChecked(true);//显示全部 + } + } + + + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java new file mode 100644 index 0000000..eca1512 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java @@ -0,0 +1,193 @@ +package com.bg7yoz.ft8cn.ui; +/** + * flexRadio仪表的自定义控件。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; + +import androidx.annotation.Nullable; + +public class FlexMeterRulerView extends View { + private static final String TAG = "FlexMeterRulerView"; + + private String label="S/Po"; + private String unit="dBm"; + private String valueLabel="5dBm"; + private float lowVal=-150f; + private float highVal=-72f; + private float maxVal=10f; + private int normalCount=9; + private int highCount=3; + private String[] normalLabels=new String[]{"0","1","2","3","4","5","6","7","8","9"}; + private String[] highLabels=new String[]{"20","40","50"}; + private float value=5f; + + + private final int labelDp=12;//标签字体大小dp + private Rect rulerRect = new Rect(); + private Rect valueRect =new Rect(); + private Paint fontPaint = new Paint(); + private Paint rulerPaint = new Paint(); + private Paint valuePaint = new Paint(); + private Paint labelPaint=new Paint(); + private int rulerWidth = getWidth(); + private int labelWidth=dpToPixel(40);//标签宽度 + private int valueWidth=dpToPixel(65);//值标签宽度 + + @SuppressLint("DefaultLocale") + public void setValue(float value) { + valueLabel=String.format("%.1f%s",value,unit); + if (value>maxVal) { + this.value = maxVal; + }else if(value() { + @Override + public void onChanged(FlexMeterList meters) { + //binding.flexInfoTextView.setText(String.format("%f",meters.alcVal)); + binding.sMeterRulerView.setValue(meters.sMeterVal); + binding.alcMeterRulerView.setValue(meters.alcVal); + binding.swrMeterRulerView.setValue(meters.swrVal); + binding.powerMeterRulerView.setValue(meters.pwrVal); + binding.tempMeterRulerView.setValue(meters.tempCVal); + + binding.flexInfoTextView.setText(meters.getMeters()); + } + }); + } + + + binding.sMeterRulerView.initVal(-150f, -72f, 10f, 9, 3); + binding.sMeterRulerView.initLabels("S.Po", "dBm" + , new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + , new String[]{"20", "40", ""}); + + binding.swrMeterRulerView.initVal(1f, 3f, 20f, 4, 4); + binding.swrMeterRulerView.initLabels("SWR", "" + , new String[]{"1", "1.5", "2", "2.5", "3"} + , new String[]{"", "", "", "∞"}); + + binding.alcMeterRulerView.initVal(-150f, 0f, 20f, 3, 3); + binding.alcMeterRulerView.initLabels("ALC", "dBm" + , new String[]{"", "", ""} + , new String[]{"", "", ""}); + + binding.powerMeterRulerView.initVal(-0f, 50f, 100f, 5, 5); + binding.powerMeterRulerView.initLabels("PWR", "W" + , new String[]{"0", "10", "20", "30", "40", "50"} + , new String[]{"60", "70", "80", "90", "100"}); + + binding.tempMeterRulerView.initVal(-0f, 80f, 100f, 8, 2); + binding.tempMeterRulerView.initLabels("Temp", "°c" + , new String[]{"0", "10", "20", "30", "40", "50", "60", "70", "80"} + , new String[]{"90", "100"}); + + + binding.maxPowerProgress.setValueColor(getContext().getColor(R.color.power_progress_value)); + binding.tunePowerProgress.setValueColor(getContext().getColor(R.color.power_progress_value)); + binding.maxPowerProgress.setRadarColor(getContext().getColor(R.color.power_progress_radar_value)); + binding.tunePowerProgress.setRadarColor(getContext().getColor(R.color.power_progress_radar_value)); + + binding.maxPowerProgress.setAlarmValue(0.51f); + + + binding.maxPowerProgress.setPercent(((float) connector.maxRfPower) / 100f); + binding.maxPowerSeekBar.setProgress(connector.maxRfPower); + binding.maxTxPowerTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), connector.maxRfPower)); + + binding.tunePowerProgress.setPercent(((float) connector.maxTunePower) / 100f); + binding.tunePowerSeekBar.setProgress(connector.maxTunePower); + binding.tunePowerProgress.setAlarmValue((connector.maxTunePower / 100f) + 0.01f); + binding.tunePowerTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_tune_power), connector.maxTunePower)); + + binding.maxPowerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + binding.maxPowerProgress.setPercent(i * 1.0f / 100); + connector.maxRfPower = i; + connector.setMaxRfPower(i); + binding.maxTxPowerTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), i)); + + binding.tunePowerProgress.setAlarmValue((i / 100f) + 0.02f); + setTuneProgress(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + binding.tunePowerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + connector.maxTunePower=i; + binding.tunePowerProgress.setPercent(seekBar.getProgress() * 1.0f / 100); + binding.tunePowerTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_tune_power) + , connector.maxTunePower)); + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + setTuneProgress(); + } + }); + + + binding.autStartButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + connector.startATU(); + } + }); + binding.pttOnButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + connector.tuneOnOff(true); + } + }); + binding.pttOffButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + connector.tuneOnOff(false); + } + }); + + return binding.getRoot(); + } + + private void setTuneProgress() { + //binding.tunePowerSeekBar.setProgress(connector.maxRfPower); + if (connector.maxTunePower>connector.maxRfPower) { + connector.maxTunePower =connector.maxRfPower; + } + binding.tunePowerTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_tune_power) + , connector.maxTunePower)); + binding.tunePowerProgress.setPercent(connector.maxTunePower * 1.0f / 100); + + binding.tunePowerSeekBar.setProgress(connector.maxTunePower); + connector.setMaxTunePower(connector.maxTunePower); + + mainViewModel.databaseOpr.writeConfig("flexMaxRfPower" + ,String.valueOf(connector.maxRfPower),null); + mainViewModel.databaseOpr.writeConfig("flexMaxTunePower" + ,String.valueOf(connector.maxTunePower),null); + } + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java new file mode 100644 index 0000000..7fc6f0c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java @@ -0,0 +1,150 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 快速切换频率的对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.database.OperationBand; + +public class FreqDialog extends Dialog { + private static final String TAG = "FreqDialog"; + + private MainViewModel mainViewModel; + private RecyclerView freqRecyclerView; + private FreqAdapter freqAdapter; + //private BandsSpinnerAdapter bandsSpinnerAdapter; + + + public FreqDialog(Context context, MainViewModel mainViewModel) { + super(context, R.style.HelpDialog); + this.mainViewModel=mainViewModel; + + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.freq_dialog_layout); + freqRecyclerView=(RecyclerView) findViewById(R.id.freqDialogRecyclerView); + freqRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + + freqAdapter = new FreqAdapter(); + freqRecyclerView.setAdapter(freqAdapter); + + freqRecyclerView.scrollToPosition(OperationBand.getIndexByFreq(GeneralVariables.band)); +// +// View.OnClickListener onClickListener=new View.OnClickListener() { +// @Override +// public void onClick(View view) { +// FreqDialog.this.dismiss(); +// } +// }; + + } + + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); + params.height = (int) (height * 0.6); + if (width>height) { + params.width = (int) (width * 0.5); + params.height = (int) (height * 0.6); + }else { + params.width= (int) (width * 0.6); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + + } + + public class FreqAdapter extends RecyclerView.Adapter{ + + + @NonNull + @Override + public FreqHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.operation_band_dialog_item, parent, false); + final FreqHolder freqHolder=new FreqHolder(view); + return freqHolder; + } + + @Override + public void onBindViewHolder(@NonNull FreqHolder holder, int position) { + holder.band=OperationBand.getBandFreq(position); + //holder.index=position; + holder.operationDialogBandItemTextView.setText(OperationBand.getBandInfo(position)); + if (holder.band==GeneralVariables.band){ + holder.operationDialogBandConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_3_style); + }else { + holder.operationDialogBandConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_style); + } + holder.operationDialogBandConstraintLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(holder.band); + GeneralVariables.band = holder.band; + + mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 + mainViewModel.databaseOpr.writeConfig("bandFreq" + , String.valueOf(GeneralVariables.band) + , null); + if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + //如果在CAT、RTS模式下,修改电台的频率 + mainViewModel.setOperationBand(); + } + dismiss(); + } + }); + + + //OperationBand.bandList.get(i) + } + + @Override + public int getItemCount() { + return OperationBand.bandList.size(); + } + + class FreqHolder extends RecyclerView.ViewHolder{ + long band; + + TextView operationDialogBandItemTextView; + ConstraintLayout operationDialogBandConstraintLayout; + public FreqHolder(@NonNull View itemView) { + super(itemView); + operationDialogBandItemTextView=itemView.findViewById(R.id.operationDialogBandItemTextView); + operationDialogBandConstraintLayout=itemView.findViewById(R.id.operationDialogBandConstraintLayout); + } + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java new file mode 100644 index 0000000..21443e9 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java @@ -0,0 +1,78 @@ +package com.bg7yoz.ft8cn.ui; +/** + * FT8通联的6步列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ft8transmit.FunctionOfTransmit; + +public class FunctionOrderSpinnerAdapter extends BaseAdapter { + private Context mContext; + private MainViewModel mainViewModel; + + public FunctionOrderSpinnerAdapter(Context context, MainViewModel mainViewModel) { + this.mainViewModel = mainViewModel; + mContext = context; + } + + @Override + public int getCount() { + return mainViewModel.ft8TransmitSignal.functionList.size(); + } + + @Override + public Object getItem(int i) { + return mainViewModel.ft8TransmitSignal.functionList.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater = LayoutInflater.from(mContext); + FunctionOfTransmit function; + function = mainViewModel.ft8TransmitSignal.functionList.get(i); + + view = _LayoutInflater.inflate(R.layout.function_order_spinner_item, null); + if (view != null) { + TextView messageTextView = (TextView) view.findViewById(R.id.functionOrderItemTextView); + messageTextView.setText(function.getFunctionMessage()); + TextView numTextView = (TextView) view.findViewById(R.id.functionNumItemTextView); + numTextView.setText(String.valueOf(function.getFunctionOrder())); + +// ImageView completedImageView = (ImageView) view.findViewById(R.id.functionCompletedImageView); + + ImageView currentImageView=(ImageView) view.findViewById(R.id.currentOrderImageView); + if (function.isCurrentOrder()){ + currentImageView.setVisibility(View.VISIBLE); + }else { + currentImageView.setVisibility(View.INVISIBLE); + } + +// if (function.isCompleted()) { +// completedImageView.setVisibility(View.VISIBLE); +// } else { +// completedImageView.setVisibility(View.GONE); +// } + + } + return view; + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java new file mode 100644 index 0000000..24bc1bf --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java @@ -0,0 +1,176 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 帮助信息的对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Timer; +import java.util.TimerTask; + +public class HelpDialog extends Dialog { + private static final String TAG = "HelpDialog"; + private final Context context; + private final Activity activity; + private final String msg; + private ImageView upImageView; + private ImageView downImageView; + private ScrollView scrollView; + private TextView messageTextView; + private TextView appNameTextView; + private TextView buildVersionTextView; + private final Timer timer=new Timer(); + private TimerTask timeEvent() { + return new TimerTask() { + @Override + public void run() { + //心跳动作 + //doHeartBeatEvent(onUtcTimer); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + setImageVisible(); + } + }); + + } + }; + } + + + + + public HelpDialog(@NonNull Context context,Activity activity,String str, boolean fromFile) { + super(context, R.style.HelpDialog); + this.context = context; + this.activity=activity; + if (fromFile) { + msg = getTextFromAssets(str); + } else { + msg = str; + } + + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.help_dialog_layout); + messageTextView = (TextView) findViewById(R.id.helpMessage); + appNameTextView = (TextView) findViewById(R.id.appNameTextView); + buildVersionTextView = (TextView) findViewById(R.id.buildVersionTextView); + messageTextView.setText(msg); + upImageView = (ImageView) findViewById(R.id.scrollUpImageView); + downImageView = (ImageView) findViewById(R.id.scrollDownImageView); + scrollView = (ScrollView) findViewById(R.id.helpScrollView); + upImageView.setVisibility(View.INVISIBLE); + downImageView.setVisibility(View.INVISIBLE); + appNameTextView.setText(GeneralVariables.getStringFromResource(R.string.app_name)); + buildVersionTextView.setText(BuildConfig.VERSION_NAME); + + scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { + @Override + public void onScrollChange(View view, int i, int i1, int i2, int i3) { + Log.d(TAG, String.format("onCreate: getMeasuredHeight:%d, getHeight:%d scroll height:%d" + , messageTextView.getMeasuredHeight(), messageTextView.getHeight(),scrollView.getHeight())); + + + } + }); + + Button getNewButton=(Button) findViewById(R.id.getNewVersionButton); + getNewButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + jumpUriToBrowser(context,"https://github.com/N0BOY/FT8CN/releases"); + } + }); + + timer.schedule(timeEvent(), 10, 500); + } + + private void jumpUriToBrowser(Context context,String url){ + Intent intent=new Intent(); + intent.setAction(Intent.ACTION_VIEW); + Uri uri=Uri.parse(url); + intent.setData(uri); + context.startActivity(intent); + } + + private void setImageVisible(){ + if (scrollView.getScrollY() == 0) { + upImageView.setVisibility(View.INVISIBLE); + } else { + upImageView.setVisibility(View.VISIBLE); + } + + if (scrollView.getMeasuredHeight() <= messageTextView.getMeasuredHeight()-scrollView.getScrollY()) { + downImageView.setVisibility(View.VISIBLE); + } else { + downImageView.setVisibility(View.INVISIBLE); + } + } + + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height=getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width=getWindow().getWindowManager().getDefaultDisplay().getWidth(); + params.height = (int) (height * 0.6); + if (width>height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.9); + }else { + params.width= (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + + } + + + + public String getTextFromAssets(String fileName) { + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream = assetManager.open(fileName); + byte[] bytes = new byte[inputStream.available()]; + inputStream.read(bytes); + inputStream.close(); + + return new String(bytes); + + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java new file mode 100644 index 0000000..f05a4b2 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java @@ -0,0 +1,81 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 发射监管的列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.util.ArrayList; +import java.util.List; + +public class LaunchSupervisionSpinnerAdapter extends BaseAdapter { + private final List timeOutList=new ArrayList<>(); + private final Context mContext; + + public LaunchSupervisionSpinnerAdapter(Context context) { + mContext=context; + timeOutList.add(0); + for (int i = 1; i <= 10; i++) { + timeOutList.add(i*10-5); + } + } + public static int getTimeOut(int index){ + if (index==0) return 0; + return ((index+1) * 10-5) * 60 * 1000; + } + + @Override + public int getCount() { + return timeOutList.size(); + } + + @Override + public Object getItem(int i) { + return timeOutList.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.launch_supervision_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.timeOutTextView); + if (i==0){ + textView.setText( + GeneralVariables.getStringFromResource(R.string.launch_supervision_ignore)); + }else { + textView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.minutes), timeOutList.get(i))); + } + } + return view; + } + + public int getPosition(int timeOut){ + if (timeOut==0){ + return 0; + }else if (timeOut<5*60*1000) { + return 1; + }else { + return ((timeOut-5*60*1000)/60/1000/10); + } + + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java new file mode 100644 index 0000000..ad1ab12 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java @@ -0,0 +1,545 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 通联纪录的主界面。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import static android.widget.AbsListView.OnScrollListener.SCROLL_STATE_IDLE; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.databinding.FragmentLogBinding; +import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity; +import com.bg7yoz.ft8cn.html.LogHttpServer; +import com.bg7yoz.ft8cn.log.LogCallsignAdapter; +import com.bg7yoz.ft8cn.log.LogQSLAdapter; +import com.bg7yoz.ft8cn.log.OnQueryQSLCallsign; +import com.bg7yoz.ft8cn.log.OnQueryQSLRecordCallsign; +import com.bg7yoz.ft8cn.log.QSLCallsignRecord; +import com.bg7yoz.ft8cn.log.QSLRecordStr; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * A simple {@link Fragment} subclass. + * Use the {@link LogFragment#newInstance} factory method to + * create an instance of this fragment. + */ +public class LogFragment extends Fragment { + private static final String TAG = "LogFragment"; + private FragmentLogBinding binding; + private MainViewModel mainViewModel; + + private LogCallsignAdapter logCallsignAdapter; + private LogQSLAdapter logQSLAdapter; + private boolean loading = false;//防止滑动触发多次查询 + private int lastItemPosition; + + + public LogFragment() { + // Required empty public constructor + } + + + public static LogFragment newInstance(String param1, String param2) { + LogFragment fragment = new LogFragment(); + + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mainViewModel = MainViewModel.getInstance(this); + + } + + @SuppressLint({"DefaultLocale", "NotifyDataSetChanged"}) + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentLogBinding.inflate(getLayoutInflater()); + + logCallsignAdapter = new LogCallsignAdapter(requireContext(), mainViewModel); + logQSLAdapter = new LogQSLAdapter(requireContext(), mainViewModel); + binding.logRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + + + setShowStyle();//设置显模式 + + + initRecyclerViewAction();//设置列表滑动动作 + + + //设置显示统计页面按钮 + binding.countImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showCountFragment(); + } + }); + + binding.inputMycallEdit.setText(mainViewModel.queryKey); + queryByCallsign(mainViewModel.queryKey, 0); + + mainViewModel.mutableQueryFilter.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer integer) { + queryByCallsign(mainViewModel.queryKey, 0); + } + }); + + //输入条件监听 + binding.inputMycallEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + mainViewModel.queryKey = editable.toString(); + queryByCallsign(mainViewModel.queryKey, 0); + } + }); + + //过滤条件按钮 + binding.filterImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new FilterDialog(requireContext(), mainViewModel).show(); + } + }); + + //导出按钮 + binding.exportImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (getLocalIp()==null) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.export_null) + ,false).show(); + }else { + new HelpDialog(requireContext(), requireActivity() + , String.format(GeneralVariables.getStringFromResource(R.string.export_info) + , getLocalIp(), LogHttpServer.DEFAULT_PORT) + , false).show(); + } + + } + }); + + binding.logViewStyleimageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mainViewModel.logListShowCallsign = !mainViewModel.logListShowCallsign; + setShowStyle(); + queryByCallsign(binding.inputMycallEdit.getText().toString(), 0);//偏移量0,就是重新查询 + } + }); + + //定位按钮的动作 + binding.locationInMapImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); + intent.putExtra("qslAll", mainViewModel.queryKey); + intent.putExtra("queryFilter", mainViewModel.queryFilter); + startActivity(intent); + } + }); + + return binding.getRoot(); + } + + + /** + * 弹出菜单选项 + * + * @param item item + * @return item + */ + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + int position = (Integer) item.getActionView().getTag(); + if (!mainViewModel.logListShowCallsign) { + switch (item.getItemId()) { + case 0: + logQSLAdapter.setRecordIsQSL(position, false); + logQSLAdapter.notifyItemChanged(position); + break; + case 1: + logQSLAdapter.setRecordIsQSL(position, true); + logQSLAdapter.notifyItemChanged(position); + break; + case 2: + showQrzFragment(logQSLAdapter.getRecord(position).getCall()); + break; + case 3: + Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); + //ArrayList records=new ArrayList<>(); + //records.add(logQSLAdapter.getRecord(position)); + intent.putExtra("qslList", logQSLAdapter.getRecord(position)); + startActivity(intent); + break; + + } + } else { + if (item.getItemId() == 2) { + showQrzFragment(logCallsignAdapter.getRecord(position).getCallsign()); + } + } + + return super.onContextItemSelected(item); + } + + private boolean itemIsOnScreen(View view) { + if (view != null) { + int width = view.getWidth(); + int height = view.getHeight(); + Rect rect = new Rect(0, 0, width, height); + return view.getLocalVisibleRect(rect); + } + return false; + } + + private void loadQueryData(RecyclerView recyclerView) { +// if ((!loading)&&(recyclerView.computeVerticalScrollRange() +// - recyclerView.computeVerticalScrollExtent() +// - recyclerView.computeVerticalScrollOffset() < 100)) { + if ((!loading)) { + //ToastMessage.show("查询"); + if (mainViewModel.logListShowCallsign) { + queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); + } else { + queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); + } + } + } + + /** + * 设置列表滑动动作 + */ + private void initRecyclerViewAction() { + + binding.logRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + int itemCount; + if (mainViewModel.logListShowCallsign) { + itemCount = logCallsignAdapter.getItemCount(); + } else { + itemCount = logQSLAdapter.getItemCount(); + } + if (newState == SCROLL_STATE_IDLE && + lastItemPosition == itemCount) { + loadQueryData(recyclerView); + + } + } + + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + LinearLayoutManager manager = (LinearLayoutManager) layoutManager; + int firstVisibleItem = manager.findFirstVisibleItemPosition(); + int l = manager.findLastCompletelyVisibleItemPosition(); + lastItemPosition = firstVisibleItem + (l - firstVisibleItem) + 1; + + } + +// if (dy>0){//列表向上移动 +// loadQueryData(recyclerView); +// } + //当列表上滑,接近底部时,开始查询 +// if ((!loading)&&(dy>0)&&(recyclerView.computeVerticalScrollRange() +// - recyclerView.computeVerticalScrollExtent() +// - recyclerView.computeVerticalScrollOffset() < 100)) { +// ToastMessage.show("查询"); +// if (mainViewModel.logListShowCallsign){ +// queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); +// }else { +// queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); +// } +// } + } + }); + + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG + , ItemTouchHelper.END | ItemTouchHelper.START) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView + , @NonNull RecyclerView.ViewHolder viewHolder + , @NonNull RecyclerView.ViewHolder target) { + return false; + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + if (direction == ItemTouchHelper.END) { + if (mainViewModel.logListShowCallsign) {//此时是显示QSL呼号日志 + //logCallsignAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); + } else { + //做一个是否删除确认对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + builder.setIcon(null); + builder.setTitle(GeneralVariables.getStringFromResource(R.string.delete_confirmation)); + builder.setMessage(GeneralVariables.getStringFromResource(R.string.are_you_sure_delete)); + builder.setPositiveButton(GeneralVariables.getStringFromResource(R.string.ok_confirmed) + , new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + logQSLAdapter.deleteRecord(viewHolder.getAdapterPosition());//删除日志 + logQSLAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + logQSLAdapter.notifyDataSetChanged(); + } + }); + builder.setNegativeButton(GeneralVariables.getStringFromResource(R.string.cancel) + , new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + logQSLAdapter.notifyDataSetChanged(); + } + }).show(); + + } + } + + if (direction == ItemTouchHelper.START) { + if (mainViewModel.logListShowCallsign) {//修改手工确认 + //logCallsignAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); + } else { + logQSLAdapter.setRecordIsQSL(viewHolder.getAdapterPosition() + , !logQSLAdapter.getRecord(viewHolder.getAdapterPosition()).isQSL); + logQSLAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); + } + } + } + + //判断列表格式,呼号列表 + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView + , @NonNull RecyclerView.ViewHolder viewHolder) { + int swipeFlag; + if (mainViewModel.logListShowCallsign) { + swipeFlag = 0; + } else { + swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; + } + return makeMovementFlags(0, swipeFlag); + } + + //制作删除背景的图标显示 + Drawable delIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.log_item_delete_icon); + Drawable qslIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.ic_baseline_library_add_check_24); + Drawable background = new ColorDrawable(Color.LTGRAY); + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView + , @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY + , int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + Drawable icon; + View itemView = viewHolder.itemView; + if (dX > 0) { + icon = delIcon; + } else { + icon = qslIcon; + } + + int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + int iconLeft, iconRight, iconTop, iconBottom; + int backTop, backBottom, backLeft, backRight; + backTop = itemView.getTop(); + backBottom = itemView.getBottom(); + iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + iconBottom = iconTop + icon.getIntrinsicHeight(); + if (dX > 0) { + backLeft = itemView.getLeft(); + backRight = itemView.getLeft() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconLeft = itemView.getLeft() + iconMargin; + iconRight = iconLeft + icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else if (dX < 0) { + backRight = itemView.getRight(); + backLeft = itemView.getRight() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconRight = itemView.getRight() - iconMargin; + iconLeft = iconRight - icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else { + background.setBounds(0, 0, 0, 0); + icon.setBounds(0, 0, 0, 0); + } + background.draw(c); + icon.draw(c); + + } + }).attachToRecyclerView(binding.logRecyclerView); + } + + + /** + * 设置显示模式。通联的呼号和日志两种表现方式 + */ + @SuppressLint("NotifyDataSetChanged") + private void setShowStyle() { + + if (mainViewModel.logListShowCallsign) { + binding.logViewStyleimageButton.setImageResource(R.drawable.ic_baseline_assignment_ind_24); + binding.logRecyclerView.setAdapter(logCallsignAdapter); + logCallsignAdapter.notifyDataSetChanged(); + binding.locationInMapImageButton.setVisibility(View.GONE);//隐藏定位按钮 + } else { + binding.logViewStyleimageButton.setImageResource(R.drawable.ic_baseline_assignment_24); + binding.logRecyclerView.setAdapter(logQSLAdapter); + logQSLAdapter.notifyDataSetChanged(); + binding.locationInMapImageButton.setVisibility(View.VISIBLE);//显示定位按钮 + } + + } + + /** + * 查询日志 + * + * @param callsign 呼号 + */ + private void queryByCallsign(String callsign, int offset) { + loading = true;//开始读数据 + //分两种查询 + if (mainViewModel.logListShowCallsign) { + if (offset == 0) {//说明是新增记录 + logCallsignAdapter.clearRecords();//清空记录 + } + + mainViewModel.databaseOpr.getQSLCallsignsByCallsign(false, offset, callsign, mainViewModel.queryFilter + , new OnQueryQSLCallsign() { + @Override + public void afterQuery(ArrayList records) { + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + logCallsignAdapter.setQSLCallsignList(records); + loading = false; + } + }); + } + }); + } else { + if (offset == 0) {//说明是新增记录 + logQSLAdapter.clearRecords(); + } + mainViewModel.databaseOpr.getQSLRecordByCallsign(false, offset, callsign, mainViewModel.queryFilter + , new OnQueryQSLRecordCallsign() { + @Override + public void afterQuery(ArrayList records) { + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + logQSLAdapter.setQSLList(records); + loading = false; + } + }); + } + }); + + } + } + + + /** + * 显示统计页面 + */ + private void showCountFragment() { + //用于Fragment的导航。 + NavHostFragment navHostFragment = (NavHostFragment) requireActivity() + .getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); + assert navHostFragment != null;//断言不为空 + navHostFragment.getNavController().navigate(R.id.countFragment); + ; + + } + + /** + * 显示QRZ查询界面 + * + * @param callsign 呼号 + */ + private void showQrzFragment(String callsign) { + NavHostFragment navHostFragment = (NavHostFragment) requireActivity() + .getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); + assert navHostFragment != null;//断言不为空 + Bundle bundle = new Bundle(); + bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); + navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); + } + + + /** + * 获取本机IP地址 + * + * @return IP 地址 + */ + @Nullable + private String getLocalIp() { + WifiManager wifiManager = (WifiManager) requireContext().getApplicationContext() + .getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + int ipAddress = wifiInfo.getIpAddress(); + if (ipAddress == 0) { + return null; + } + return ((ipAddress & 0xff) + "." + (ipAddress >> 8 & 0xff) + "." + (ipAddress >> 16 & 0xff) + + "." + (ipAddress >> 24 & 0xff)); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java new file mode 100644 index 0000000..3535871 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java @@ -0,0 +1,210 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 网络模式登录ICOM的对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.PasswordTransformationMethod; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.icom.IComWifiRig; + +public class LoginIcomRadioDialog extends Dialog { + private static final String TAG = "LoginIcomRadioDialog"; + private final MainViewModel mainViewModel; + private EditText inputIcomAddressEdit; + private EditText inputIcomPortEdit; + private EditText inputIcomUserNameEdit; + private EditText inputIcomPasswordEdit; + private Button icomLoginButton; + private boolean passVisible=false; + + + public LoginIcomRadioDialog(@NonNull Context context, MainViewModel mainViewModel) { + super(context); + this.mainViewModel = mainViewModel; + } + + @SuppressLint("NotifyDataSetChanged") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.login_icom_dialog_layout); + inputIcomAddressEdit = (EditText) findViewById(R.id.inputIcomAddressEdit); + inputIcomPortEdit = (EditText) findViewById(R.id.inputIcomPortEdit); + inputIcomUserNameEdit = (EditText) findViewById(R.id.inputIcomUserNameEdit); + inputIcomPasswordEdit = (EditText) findViewById(R.id.inputIcomPasswordEdit); + icomLoginButton = (Button) findViewById(R.id.icomLoginButton); + ImageButton showPassImageButton = (ImageButton) findViewById(R.id.showPassImageButton); + + inputIcomAddressEdit.setText(GeneralVariables.icomIp); + inputIcomPortEdit.setText(String.valueOf(GeneralVariables.icomUdpPort)); + inputIcomUserNameEdit.setText(GeneralVariables.icomUserName); + inputIcomPasswordEdit.setText(GeneralVariables.icomPassword); + checkInput(); + icomLoginButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.connect_icom_ip) + ,inputIcomAddressEdit.getText())); + + //IComWifiRig iComWifiRig=; + mainViewModel.connectIComWifiRig(GeneralVariables.getMainContext() + ,new IComWifiRig(GeneralVariables.icomIp + ,GeneralVariables.icomUdpPort + ,GeneralVariables.icomUserName + ,GeneralVariables.icomPassword)); + dismiss(); + } + }); + + + inputIcomAddressEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + checkInput(); + GeneralVariables.icomIp=inputIcomAddressEdit.getText().toString().trim(); + writeConfig("icomIp", inputIcomAddressEdit.getText().toString().trim()); + } + }); + + inputIcomPortEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + checkInput(); + if (GeneralVariables.isInteger(inputIcomPortEdit.getText().toString().trim())) { + writeConfig("icomPort", inputIcomPortEdit.getText().toString().trim()); + GeneralVariables.icomUdpPort=Integer.parseInt(inputIcomPortEdit.getText().toString().trim()); + } + } + }); + inputIcomUserNameEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + checkInput(); + writeConfig("icomUserName", inputIcomUserNameEdit.getText().toString().trim()); + GeneralVariables.icomUserName=inputIcomUserNameEdit.getText().toString().trim(); + } + }); + inputIcomPasswordEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + checkInput(); + writeConfig("icomPassword", inputIcomPasswordEdit.getText().toString()); + GeneralVariables.icomPassword=inputIcomPasswordEdit.getText().toString(); + } + }); + + showPassImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + passVisible=!passVisible; + if (passVisible) { + inputIcomPasswordEdit.setTransformationMethod( + HideReturnsTransformationMethod.getInstance()); + }else { + inputIcomPasswordEdit.setTransformationMethod( + PasswordTransformationMethod.getInstance()); + } + } + }); + } + + + public void checkInput() { + icomLoginButton.setEnabled(!inputIcomAddressEdit.getText().toString().isEmpty() + && !inputIcomPortEdit.getText().toString().isEmpty() + && !inputIcomUserNameEdit.getText().toString().isEmpty() + && !inputIcomPasswordEdit.getText().toString().isEmpty() + && GeneralVariables.isInteger(inputIcomPortEdit.getText().toString().trim()) + ); + } + + /** + * 把配置信息写到数据库 + * + * @param KeyName 关键词 + * @param Value 值 + */ + private void writeConfig(String KeyName, String Value) { + mainViewModel.databaseOpr.writeConfig(KeyName, Value, null); + } + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); +// params.height = (int) (height * 0.6); + if (width > height) { + params.width = (int) (width * 0.6); + //params.height = (int) (height * 0.6); + } else { + params.width = (int) (width * 0.8); + //params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java new file mode 100644 index 0000000..0b237ed --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java @@ -0,0 +1,502 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 呼叫界面。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.AdapterView; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.databinding.FragmentMyCallingBinding; +import com.bg7yoz.ft8cn.ft8transmit.FunctionOfTransmit; +import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; + + +public class MyCallingFragment extends Fragment { + private static final String TAG = "MyCallingFragment"; + private FragmentMyCallingBinding binding; + private MainViewModel mainViewModel; + + private RecyclerView transmitRecycleView; + + private CallingListAdapter transmitCallListAdapter; + + private FunctionOrderSpinnerAdapter functionOrderSpinnerAdapter; + + + static { + System.loadLibrary("ft8cn"); + } + + + /** + * 马上对发起者呼叫 + * + * @param message 消息 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + private void doCallNow(Ft8Message message) { + mainViewModel.addFollowCallsign(message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(message.getFromCallTransmitCallsign() + , 1, message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + + + /** + * 菜单选项 + * + * @param item 菜单 + * @return 是否选择 + */ + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public boolean onContextItemSelected(@NonNull MenuItem item) { + //Ft8Message ft8Message = (Ft8Message) item.getActionView().getTag(); + + int position = (int) item.getActionView().getTag(); + Ft8Message ft8Message = transmitCallListAdapter.getMessageByPosition(position); + if (ft8Message == null) return super.onContextItemSelected(item); + ; + + GeneralVariables.resetLaunchSupervision();//复位自动监管 + switch (item.getItemId()) { + case 1://时序与发送者相反!!! + Log.d(TAG, "呼叫:" + ft8Message.getCallsignTo()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + } + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getToCallTransmitCallsign() + , 1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + break; + + case 3: + Log.d(TAG, "呼叫:" + ft8Message.getCallsignFrom()); + doCallNow(ft8Message); + //if (!mainViewModel.ft8TransmitSignal.isActivated()) { + // mainViewModel.ft8TransmitSignal.setActivated(true); + // } + // mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() + // , 1, ft8Message.extraInfo); + //mainViewModel.ft8TransmitSignal.transmitNow(); + break; + + case 4://回复 + Log.d(TAG, "回复:" + ft8Message.getCallsignFrom()); + mainViewModel.addFollowCallsign(ft8Message.getCallsignFrom()); + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.setActivated(true); + GeneralVariables.transmitMessages.add(ft8Message);//把消息添加到关注列表中 + } + //呼叫发启者 + mainViewModel.ft8TransmitSignal.setTransmit(ft8Message.getFromCallTransmitCallsign() + , -1, ft8Message.extraInfo); + mainViewModel.ft8TransmitSignal.transmitNow(); + break; + + case 5://to 的QRZ + showQrzFragment(ft8Message.getCallsignTo()); + break; + case 6://from 的QRZ + showQrzFragment(ft8Message.getCallsignFrom()); + break; + + } + + return super.onContextItemSelected(item); + } + + /** + * 查询QRZ信息 + * + * @param callsign 呼号 + */ + private void showQrzFragment(String callsign) { + NavHostFragment navHostFragment = (NavHostFragment) requireActivity().getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView); + assert navHostFragment != null;//断言不为空 + Bundle bundle = new Bundle(); + bundle.putString(QRZ_Fragment.CALLSIGN_PARAM, callsign); + navHostFragment.getNavController().navigate(R.id.QRZ_Fragment, bundle); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mainViewModel = MainViewModel.getInstance(this); + binding = FragmentMyCallingBinding.inflate(inflater, container, false); + + //当横屏时显示频谱图 + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + binding.messageSpectrumView.run(mainViewModel, this); + } + + + //发射消息的列表 + functionOrderSpinnerAdapter = new FunctionOrderSpinnerAdapter(requireContext(), mainViewModel); + binding.functionOrderSpinner.setAdapter(functionOrderSpinnerAdapter); + functionOrderSpinnerAdapter.notifyDataSetChanged(); + + + //关注的消息列表 + transmitRecycleView = binding.transmitRecycleView; + transmitCallListAdapter = new CallingListAdapter(this.getContext(), mainViewModel + , GeneralVariables.transmitMessages, CallingListAdapter.ShowMode.MY_CALLING); + transmitRecycleView.setLayoutManager(new LinearLayoutManager(requireContext())); + transmitRecycleView.setAdapter(transmitCallListAdapter); + + + transmitCallListAdapter.notifyDataSetChanged(); + + + //设置消息列表滑动,用于快速呼叫 + initRecyclerViewAction(); + //菜单 + requireActivity().registerForContextMenu(transmitRecycleView); + + //显示UTC时间 + mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Long aLong) { + binding.timerTextView.setText(UtcTimer.getTimeStr(aLong)); + } + }); + //显示发射频率 + GeneralVariables.mutableBaseFrequency.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Float aFloat) { + binding.baseFrequencyTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.sound_frequency_is), aFloat)); + } + }); + + + //观察发射状态按钮的变化 + Observer transmittingObserver = new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (mainViewModel.ft8TransmitSignal.isTransmitting()) { + binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_send_red_48); + binding.setTransmitImageButton.setAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.view_blink)); + } else { + //录音对象也要处于启动状态才可以有发射的状态 + if (mainViewModel.ft8TransmitSignal.isActivated() && mainViewModel.hamRecorder.isRunning()) { + binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_send_white_48); + } else { + binding.setTransmitImageButton.setImageResource(R.drawable.ic_baseline_cancel_schedule_send_off); + } + binding.setTransmitImageButton.setAnimation(null); + } + + //暂停播放按键 + if (mainViewModel.ft8TransmitSignal.isTransmitting()) { + binding.pauseTransmittingImageButton.setImageResource(R.drawable.ic_baseline_pause_circle_outline_24); + binding.pauseTransmittingImageButton.setVisibility(View.VISIBLE); + } else { + binding.pauseTransmittingImageButton.setVisibility(View.GONE); + binding.pauseTransmittingImageButton.setImageResource(R.drawable.ic_baseline_pause_disable_circle_outline_24); + } + } + }; + //显示发射状态 + mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(getViewLifecycleOwner(), transmittingObserver); + mainViewModel.ft8TransmitSignal.mutableIsActivated.observe(getViewLifecycleOwner(), transmittingObserver); + + //暂停按钮 + binding.pauseTransmittingImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mainViewModel.ft8TransmitSignal.setTransmitting(false); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + }); + + //监视命令程序 + mainViewModel.ft8TransmitSignal.mutableFunctions.observe(getViewLifecycleOwner() + , new Observer>() { + @Override + public void onChanged(ArrayList functionOfTransmits) { + functionOrderSpinnerAdapter.notifyDataSetChanged(); + } + }); + + //观察指令序号的变化 + mainViewModel.ft8TransmitSignal.mutableFunctionOrder.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer integer) { + if (mainViewModel.ft8TransmitSignal.functionList.size() < 6) { + binding.functionOrderSpinner.setSelection(0); + } else { + binding.functionOrderSpinner.setSelection(integer - 1); + } + } + }); + + //设置当指令序号被选择的事件 + binding.functionOrderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + if (mainViewModel.ft8TransmitSignal.functionList.size() > 1) { + mainViewModel.ft8TransmitSignal.setCurrentFunctionOrder(i + 1); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + + //显示当前目标呼号 + mainViewModel.ft8TransmitSignal.mutableToCallsign.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(TransmitCallsign transmitCallsign) { + if (GeneralVariables.toModifier!=null) { + binding.toCallsignTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.target_callsign) + , transmitCallsign.callsign+" "+GeneralVariables.toModifier)); + }else { + binding.toCallsignTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.target_callsign) + , transmitCallsign.callsign)); + } + } + }); + + //显示当前发射的时序 + mainViewModel.ft8TransmitSignal.mutableSequential.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Integer integer) { + binding.transmittingSequentialTextView.setText( + String.format(GeneralVariables.getStringFromResource(R.string.transmission_sequence) + , integer)); + } + }); + + //设置发射按钮 + binding.setTransmitImageButton.setOnClickListener(new View.OnClickListener() { + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onClick(View view) { + //如果 + if (!mainViewModel.ft8TransmitSignal.isActivated()) { + mainViewModel.ft8TransmitSignal.restTransmitting(); + } + mainViewModel.ft8TransmitSignal.setActivated(!mainViewModel.ft8TransmitSignal.isActivated()); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + }); + + //观察传输消息列表的变化 + //mainViewModel.mutableTransmitMessages.observe(getViewLifecycleOwner(), new Observer>() { + mainViewModel.mutableTransmitMessagesCount.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Integer count) { + binding.decoderCounterTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.message_count) + , GeneralVariables.transmitMessages.size())); + //if (count == 0) { + transmitCallListAdapter.notifyDataSetChanged(); + //} else { + // transmitCallListAdapter.notifyItemInserted( + // GeneralVariables.transmitMessages.size() - count); + //} + + //当列表下部稍微多出一些,自动上移 + if (transmitRecycleView.computeVerticalScrollRange() + - transmitRecycleView.computeVerticalScrollExtent() + - transmitRecycleView.computeVerticalScrollOffset() < 300) { + transmitRecycleView.scrollToPosition(transmitCallListAdapter.getItemCount() - 1); + } + } + }); + + //清除传输消息列表 + binding.clearMycallListImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mainViewModel.clearTransmittingMessage(); + } + }); + + //复位到CQ按键 + binding.resetToCQImageView.setOnClickListener(new View.OnClickListener() { + //@RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onClick(View view) { + mainViewModel.ft8TransmitSignal.resetToCQ(); + GeneralVariables.resetLaunchSupervision();//复位自动监管 + } + }); + //自由文本输入框的限定操作 + binding.transFreeTextEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + mainViewModel.ft8TransmitSignal.setFreeText(editable.toString().toUpperCase()); + } + }); + binding.resetToCQImageView.setLongClickable(true); + binding.resetToCQImageView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + mainViewModel.setTransmitIsFreeText(!mainViewModel.getTransitIsFreeText()); + showFreeTextEdit(); + return true; + } + }); + + showFreeTextEdit(); + return binding.getRoot(); + } + + private void showFreeTextEdit() { + if (mainViewModel.getTransitIsFreeText()) { + binding.transFreeTextEdit.setVisibility(View.VISIBLE); + binding.functionOrderSpinner.setVisibility(View.GONE); + } else { + binding.transFreeTextEdit.setVisibility(View.GONE); + binding.functionOrderSpinner.setVisibility(View.VISIBLE); + } + } + + /** + * 设置列表滑动动作 + */ + private void initRecyclerViewAction() { + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.ANIMATION_TYPE_DRAG + , ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder + , @NonNull RecyclerView.ViewHolder target) { + return false; + } + + //@RequiresApi(api = Build.VERSION_CODES.N) + @SuppressLint("NotifyDataSetChanged") + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + if (direction == ItemTouchHelper.START) { + Ft8Message message = transmitCallListAdapter.getMessageByViewHolder(viewHolder); + if (message != null) { + //呼叫的目标不能是自己 + if (!message.getCallsignFrom().equals("<...>") + && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !(message.i3 == 0 && message.n3 == 0)) { + doCallNow(message); + } + } + transmitCallListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); + } + if (direction == ItemTouchHelper.END) {//删除 + transmitCallListAdapter.deleteMessage(viewHolder.getAdapterPosition()); + transmitCallListAdapter.notifyItemRemoved(viewHolder.getAdapterPosition()); + } + } + + + @Override + public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + //制作呼叫背景的图标显示 + Drawable callIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.ic_baseline_send_red_48); + Drawable delIcon = ContextCompat.getDrawable(requireActivity(), R.drawable.log_item_delete_icon); + Drawable background = new ColorDrawable(Color.LTGRAY); + Ft8Message message = transmitCallListAdapter.getMessageByViewHolder(viewHolder); + if (message == null) { + return; + } + if (message.getCallsignFrom().equals("<...>")) {//如果属于不能呼叫的消息,就不显示图标 + return; + } + Drawable icon; + if (dX > 0) { + icon = delIcon; + } else { + icon = callIcon; + } + View itemView = viewHolder.itemView; + int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + int iconLeft, iconRight, iconTop, iconBottom; + int backTop, backBottom, backLeft, backRight; + backTop = itemView.getTop(); + backBottom = itemView.getBottom(); + iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2; + iconBottom = iconTop + icon.getIntrinsicHeight(); + if (dX > 0) { + backLeft = itemView.getLeft(); + backRight = itemView.getLeft() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconLeft = itemView.getLeft() + iconMargin; + iconRight = iconLeft + icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else if (dX < 0) { + backRight = itemView.getRight(); + backLeft = itemView.getRight() + (int) dX; + background.setBounds(backLeft, backTop, backRight, backBottom); + iconRight = itemView.getRight() - iconMargin; + iconLeft = iconRight - icon.getIntrinsicWidth(); + icon.setBounds(iconLeft, iconTop, iconRight, iconBottom); + } else { + background.setBounds(0, 0, 0, 0); + icon.setBounds(0, 0, 0, 0); + } + background.draw(c); + icon.draw(c); + + } + }).attachToRecyclerView(binding.transmitRecycleView); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java new file mode 100644 index 0000000..8c13f5d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java @@ -0,0 +1,64 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 未回答时间列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.util.ArrayList; +import java.util.List; + +public class NoReplyLimitSpinnerAdapter extends BaseAdapter { + private final List noReplyCount=new ArrayList<>(); + private final Context mContext; + + public NoReplyLimitSpinnerAdapter(Context context) { + mContext=context; + for (int i = 0; i <= 30; i++) { + noReplyCount.add(i); + } + } + + @Override + public int getCount() { + return noReplyCount.size(); + } + + @Override + public Object getItem(int i) { + return noReplyCount.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.no_reply_limit_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.noReplyLimitCountItemTextView); + if (i==0){ + textView.setText(GeneralVariables.getStringFromResource(R.string.ignore)); + } + else { + textView.setText(String.format(GeneralVariables.getStringFromResource(R.string.times), i)); + } + } + return view; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java new file mode 100644 index 0000000..36ef0eb --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java @@ -0,0 +1,60 @@ +package com.bg7yoz.ft8cn.ui; +/** + * PTT延迟发射列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + +import java.util.ArrayList; +import java.util.List; + +public class PttDelaySpinnerAdapter extends BaseAdapter { + private final List delayTime=new ArrayList<>(); + private final Context mContext; + + public PttDelaySpinnerAdapter(Context context) { + mContext=context; + for (int i = 0; i < 20; i++) { + delayTime.add(i*10); + } + } + + @Override + public int getCount() { + return delayTime.size(); + } + + @Override + public Object getItem(int i) { + return delayTime.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.ptt_delay_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.pttDelayItemTextView); + textView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.milliseconds),delayTime.get(i))); + } + return view; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java new file mode 100644 index 0000000..021f512 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java @@ -0,0 +1,68 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 查QRZ的web View。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.fragment.app.Fragment; + +import com.bg7yoz.ft8cn.databinding.FragmentQrzBinding; + + +public class QRZ_Fragment extends Fragment { + private FragmentQrzBinding binding; + + public static final String CALLSIGN_PARAM = "callsign"; + + private String qrzParam; + + + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + qrzParam = getArguments().getString(CALLSIGN_PARAM); + } + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding=FragmentQrzBinding.inflate(inflater, container, false); + binding.qrzWebView.getSettings().setJavaScriptEnabled(true); + //binding.qrzWebView.getSettings().setDomStorageEnabled(true); // 这个要加上 + binding.qrzWebView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); + binding.qrzWebView.getSettings().setUseWideViewPort(true); + + binding.qrzWebView.getSettings().setLoadWithOverviewMode(true); + binding.qrzWebView.getSettings().setSupportZoom(true); + binding.qrzWebView.getSettings().setBuiltInZoomControls(true); + String url = String.format("https://www.qrz.com/db/%s",qrzParam); + WebViewClient webViewClient = new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + super.shouldOverrideUrlLoading(view, url); + view.loadUrl(url); + return true; + } + }; + binding.qrzWebView.setWebViewClient(webViewClient); + + + binding.qrzWebView.loadUrl(url); + return binding.getRoot(); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java new file mode 100644 index 0000000..00e6080 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java @@ -0,0 +1,65 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 电台型号列表。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.RigNameList; + +public class RigNameSpinnerAdapter extends BaseAdapter { + private final Context mContext; + private final RigNameList rigNameList; + public RigNameSpinnerAdapter(Context context) { + rigNameList=RigNameList.getInstance(context); + mContext=context; + } + + public RigNameList.RigName getRigName(int index){ + return rigNameList.getRigNameByIndex(index); + } + @Override + public int getCount() { + return rigNameList.rigList.size(); + } + + @Override + public Object getItem(int i) { + return rigNameList.rigList.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams", "UseCompatLoadingForDrawables"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.rig_name_spinner_item, null); + if (view!=null){ + TextView textView=view.findViewById(R.id.rigNameItemTextView); + ImageView imageView=view.findViewById(R.id.rigLogoImageView); + if (rigNameList.getRigNameInfo(i).contains("GUOHE")){ + imageView.setImageDrawable(mContext.getDrawable(R.drawable.guohe_logo)); + imageView.setVisibility(View.VISIBLE); + }else { + imageView.setVisibility(View.GONE); + imageView.setImageDrawable(null); + } + textView.setText(rigNameList.getRigNameInfo(i)); + } + return view; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java new file mode 100644 index 0000000..bda7898 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java @@ -0,0 +1,123 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 频率标尺,自定义控件。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; + +import androidx.annotation.Nullable; + +public class RulerFrequencyView extends View { + private static final String TAG = "RulerFrequencyView"; + private int rulerWidth = getWidth(); + private int freq = 1000; + + public RulerFrequencyView(Context context) { + super(context); + } + + public RulerFrequencyView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public RulerFrequencyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onDraw(Canvas canvas) { + //Log.d(TAG, String.format("onDraw: rulerWidth:%d,getWidth:%d", rulerWidth, getWidth())); + drawRuler(canvas); + super.onDraw(canvas); + } + + /** + * 把dp值转换为像素点 + * + * @param dp dp值 + * @return 像素点 + */ + private int dpToPixel(int dp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp + , getResources().getDisplayMetrics()); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + //Log.d(TAG, String.format("onSizeChanged: rulerWidth:%d,getWidth:%d", w, getWidth())); + rulerWidth = w; + super.onSizeChanged(w, h, oldw, oldh); + } + + @SuppressLint({"DefaultLocale", "ResourceAsColor"}) + public void drawRuler(Canvas canvas) { + int top = 1; + //rulerWidth=getRight(); + int width_rate = Math.round((float) rulerWidth / 30f); + int lineWidth = (int) (getResources().getDisplayMetrics().density); + int lineHeight = (int) (2 * getResources().getDisplayMetrics().density); + Rect rect = new Rect(); + Paint paint = new Paint(); + paint.setColor(0xff00ffff); + for (int i = 0; i <= 300; i++) { + if (i % 1 == 0) { + rect.top = top; + rect.left = Math.round((float) i * width_rate); + rect.right = rect.left + lineWidth; + if (i % 5 == 0) { + rect.bottom = top + lineHeight * 3; + Paint fontPaint = new Paint(); + fontPaint.setTextSize(dpToPixel(8)); + fontPaint.setColor(0xff00ffff); + fontPaint.setAntiAlias(true); + fontPaint.setDither(true); + if (i == 0) { + fontPaint.setTextAlign(Paint.Align.LEFT); + } else if (i == 300) { + fontPaint.setTextAlign(Paint.Align.RIGHT); + } else { + fontPaint.setTextAlign(Paint.Align.CENTER); + } + canvas.drawText(String.format("%dHz", i * 100), rect.left + , rect.bottom + 8 * getResources().getDisplayMetrics().density + , fontPaint); + + } else { + rect.bottom = top + lineHeight; + } + canvas.drawRect(rect, paint); + + } + } + //主线 + rect.top = 1; + rect.left = 0; + rect.right = rulerWidth; + rect.bottom = (int) (rect.top + 2*getResources().getDisplayMetrics().density); + canvas.drawRect(rect, paint); + + //当前频率范围标记。红色块 + Rect mark = new Rect(); + paint.setColor(0xffff0000); + mark.top = 1; + mark.left = width_rate * (freq - 50) / 100; + mark.right = width_rate * (freq + 50) / 100; + mark.bottom = (int) (mark.top + 3*getResources().getDisplayMetrics().density); + canvas.drawRect(mark, paint); + + } + + public void setFreq(int freq) { + this.freq = freq; + this.postInvalidate(); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java new file mode 100644 index 0000000..c3a2dbe --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java @@ -0,0 +1,220 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 蓝牙设备选择对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.bluetooth.BluetoothConstants; + +import java.util.ArrayList; + +public class SelectBluetoothDialog extends Dialog { + class BluetoothDeviceInfo { + BluetoothDevice device; + boolean isSPP; + boolean isHeadSet; + + public BluetoothDeviceInfo(BluetoothDevice device, boolean isSPP,boolean isHeadSet) { + this.device = device; + this.isSPP = isSPP; + this.isHeadSet=isHeadSet; + } + } + + private MainViewModel mainViewModel; + private BluetoothAdapter bluetoothAdapter; + private final ArrayList devices = new ArrayList<>(); + private RecyclerView devicesRecyclerView; + private BluetoothDevicesAdapter blueToothListAdapter; + + private ImageView upImage; + private ImageView downImage; + + + public SelectBluetoothDialog(@NonNull Context context, MainViewModel mainViewModel) { + super(context); + this.mainViewModel = mainViewModel; + + } + + + + @SuppressLint({"MissingPermission", "NotifyDataSetChanged"}) + private void getBluetoothDevice() { + devices.clear(); + if (bluetoothAdapter == null) { + return; + } + for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { + if (BluetoothConstants.checkIsSpp(device)){//spp设备放前面 + devices.add(0,new BluetoothDeviceInfo(device,true,BluetoothConstants.checkIsHeadSet(device))); + continue; + } + if (BluetoothConstants.checkIsHeadSet(device)){//headset设备放后面 + devices.add(new BluetoothDeviceInfo(device, false,BluetoothConstants.checkIsHeadSet(device))); + continue; + } + } + + blueToothListAdapter.notifyDataSetChanged(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.select_bluetooth_dialog_layout); + devicesRecyclerView = (RecyclerView) findViewById(R.id.bluetoothListRecyclerView); + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + blueToothListAdapter = new BluetoothDevicesAdapter(); + devicesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + devicesRecyclerView.setAdapter(blueToothListAdapter); + upImage=(ImageView) findViewById(R.id.bluetoothScrollUpImageView); + downImage=(ImageView)findViewById(R.id.bluetoothScrollDownImageView); + getBluetoothDevice(); + + //显示滚动箭头 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setScrollImageVisible(); + } + }, 1000); + devicesRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + setScrollImageVisible(); + } + }); + + } + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); +// params.height = (int) (height * 0.6); + if (width > height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.6); + } else { + params.width = (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + + } + /** + * 设置界面的上下滚动的图标 + */ + private void setScrollImageVisible() { + + if (devicesRecyclerView.canScrollVertically(1)) { + upImage.setVisibility(View.VISIBLE); + } else { + upImage.setVisibility(View.GONE); + } + + if (devicesRecyclerView.canScrollVertically(-1)) { + downImage.setVisibility(View.VISIBLE); + } else { + downImage.setVisibility(View.GONE); + } + } + + class BluetoothDevicesAdapter extends RecyclerView.Adapter { + + @NonNull + @Override + public BluetoothHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.bluetooth_device_list_item, parent, false); + final BluetoothDevicesAdapter.BluetoothHolder holder = new BluetoothDevicesAdapter.BluetoothHolder(view); + return holder; + } + + @SuppressLint("MissingPermission") + @Override + public void onBindViewHolder(@NonNull BluetoothHolder holder, int position) { + holder.device = devices.get(position); + holder.bluetoothNameTextView.setText(holder.device.device.getName()); + if (holder.device.isSPP){ + holder.bluetoothNameTextView.setTextColor(getContext().getResources().getColor( + R.color.bluetooth_device_enable_color)); + }else { + holder.bluetoothNameTextView.setTextColor(getContext().getResources().getColor( + R.color.bluetooth_device_disable_color)); + } + if (BluetoothConstants.checkIsHeadSet(holder.device.device)){ + holder.headsetImageView.setVisibility(View.VISIBLE); + }else { + holder.headsetImageView.setVisibility(View.GONE); + } + if (BluetoothConstants.checkIsSpp(holder.device.device)){ + holder.sppDeviceImageView.setVisibility(View.VISIBLE); + }else { + holder.sppDeviceImageView.setVisibility(View.GONE); + } + holder.bluetoothAddressTextView.setText(holder.device.device.getAddress()); + + holder.bluetoothListConstraintLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.select_bluetooth_device) + ,holder.device.device.getName())); + mainViewModel.connectBluetoothRig(GeneralVariables.getMainContext(), holder.device.device); + + dismiss(); + } + }); + } + + @Override + public int getItemCount() { + return devices.size(); + } + + class BluetoothHolder extends RecyclerView.ViewHolder { + public BluetoothDeviceInfo device; + TextView bluetoothNameTextView, bluetoothAddressTextView; + ConstraintLayout bluetoothListConstraintLayout; + ImageView headsetImageView,sppDeviceImageView; + public BluetoothHolder(@NonNull View itemView) { + super(itemView); + bluetoothNameTextView = itemView.findViewById(R.id.bluetoothNameTextView); + bluetoothAddressTextView = itemView.findViewById(R.id.bluetoothAddressTextView); + bluetoothListConstraintLayout = itemView.findViewById(R.id.bluetoothListConstraintLayout); + headsetImageView = itemView.findViewById(R.id.headsetImageView); + sppDeviceImageView = itemView.findViewById(R.id.sppDeviceImageView); + } + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java new file mode 100644 index 0000000..209f185 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java @@ -0,0 +1,244 @@ +package com.bg7yoz.ft8cn.ui; +/** + * FlexRadio选择对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.FlexRadioFactory; + +public class SelectFlexRadioDialog extends Dialog { + private static final String TAG="SelectFlexRadioDialog"; + private final MainViewModel mainViewModel; + private RecyclerView flexRecyclerView; + private ImageView upImage; + private ImageView downImage; + private FlexRadioFactory flexRadioFactory; + private FlexRadioAdapter flexRadioAdapter; + private ImageButton connectFlexImageButton; + private EditText inputFlexAddressEdit; + + + + public SelectFlexRadioDialog(@NonNull Context context, MainViewModel mainViewModel){ + super(context); + this.mainViewModel = mainViewModel; + } + + @SuppressLint("NotifyDataSetChanged") + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setContentView(R.layout.select_flex_dialog_layout); + flexRecyclerView=(RecyclerView) findViewById(R.id.flexRadioListRecyclerView); + flexRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + upImage=(ImageView) findViewById(R.id.flexRadioScrollUpImageView); + downImage=(ImageView)findViewById(R.id.flexRadioScrollDownImageView); + inputFlexAddressEdit=(EditText)findViewById(R.id.inputFlexAddressEdit); + connectFlexImageButton=(ImageButton) findViewById(R.id.connectFlexImageButton); + connectFlexImageButton.setEnabled(false); + connectFlexImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.connect_flex_ip) + ,inputFlexAddressEdit.getText())); + FlexRadio flexRadio=new FlexRadio(); + Log.e(TAG, "onClick: "+inputFlexAddressEdit.getText().toString()); + flexRadio.setIp(inputFlexAddressEdit.getText().toString()); + flexRadio.setModel("FlexRadio"); + mainViewModel.connectFlexRadioRig(GeneralVariables.getMainContext(),flexRadio); + + dismiss(); + } + }); + + inputFlexAddressEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + connectFlexImageButton.setEnabled(!inputFlexAddressEdit.getText().toString().isEmpty()); + } + }); + + + + + flexRadioAdapter=new FlexRadioAdapter(); + flexRecyclerView.setAdapter(flexRadioAdapter); + + flexRadioFactory = FlexRadioFactory.getInstance(); + + + flexRadioFactory.setOnFlexRadioEvents(new FlexRadioFactory.OnFlexRadioEvents() { + + @Override + public void OnFlexRadioAdded(FlexRadio flexRadio) { + flexRecyclerView.post(new Runnable() { + @Override + public void run() { + flexRadioAdapter.notifyDataSetChanged(); + } + }); + } + + @Override + public void OnFlexRadioInvalid(FlexRadio flexRadio) { + flexRecyclerView.post(new Runnable() { + @Override + public void run() { + flexRadioAdapter.notifyDataSetChanged(); + } + }); + } + }); + + + + //显示滚动箭头 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setScrollImageVisible(); + } + }, 1000); + flexRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + setScrollImageVisible(); + } + }); + } + + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); +// params.height = (int) (height * 0.6); + if (width > height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.6); + } else { + params.width = (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + } + + /** + * 设置界面的上下滚动的图标 + */ + private void setScrollImageVisible() { + + if (flexRecyclerView.canScrollVertically(1)) { + upImage.setVisibility(View.VISIBLE); + } else { + upImage.setVisibility(View.GONE); + } + + if (flexRecyclerView.canScrollVertically(-1)) { + downImage.setVisibility(View.VISIBLE); + } else { + downImage.setVisibility(View.GONE); + } + } + + class FlexRadioAdapter extends RecyclerView.Adapter{ + + + @NonNull + @Override + public FlexViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.flex_device_list_item, parent, false); + final FlexRadioAdapter.FlexViewHolder holder = new FlexRadioAdapter.FlexViewHolder(view); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull FlexViewHolder holder, int position) { + holder.flexRadio=flexRadioFactory.flexRadios.get(position); + holder.flexRadioIpTextView.setText(holder.flexRadio.getIp()); + holder.flexRadioSerialNumTextView.setText(holder.flexRadio.getSerial()); + holder.flexRadioNameTextView.setText(holder.flexRadio.getModel()); + + holder.flexRadioListConstraintLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.select_flex_device) + ,holder.flexRadio.getModel())); + + //此处添加连接flex电台的动作 + //mainViewModel.connectBluetoothRig(GeneralVariables.getMainContext(), holder.device.device); + mainViewModel.connectFlexRadioRig(GeneralVariables.getMainContext(),holder.flexRadio); + dismiss(); + } + }); + + } + + @Override + public int getItemCount() { + return flexRadioFactory.flexRadios.size(); + } + + + + class FlexViewHolder extends RecyclerView.ViewHolder{ + public FlexRadio flexRadio; + TextView flexRadioNameTextView,flexRadioIpTextView,flexRadioSerialNumTextView; + ConstraintLayout flexRadioListConstraintLayout; + public FlexViewHolder(@NonNull View itemView) { + super(itemView); + flexRadioNameTextView=itemView.findViewById(R.id.flexRadioNameTextView); + flexRadioIpTextView=itemView.findViewById(R.id.flexRadioIpTextView); + flexRadioSerialNumTextView=itemView.findViewById(R.id.flexRadioSerialNumTextView); + flexRadioListConstraintLayout=itemView.findViewById(R.id.flexRadioListConstraintLayout); + } + + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java new file mode 100644 index 0000000..d3c0e60 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java @@ -0,0 +1,113 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 设置信号输出强度的对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.Observer; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; + +public class SetVolumeDialog extends Dialog { + private static final String TAG = "SetVolumeDialog"; + private TextView volumeValueMessage; + private SeekBar volumeSeekBar; + private final MainViewModel mainViewModel; + private VolumeProgress volumeProgress; + + public SetVolumeDialog(@NonNull Context context, MainViewModel mainViewModel) { + super(context); + this.mainViewModel = mainViewModel; + } + + + @SuppressLint({"NotifyDataSetChanged", "MissingInflatedId"}) + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.set_volume_dialog); + volumeValueMessage = (TextView) findViewById(R.id.volumeValueMessage); + volumeSeekBar = (SeekBar) findViewById(R.id.volumeSeekBar); + volumeProgress=(VolumeProgress) findViewById(R.id.volumeProgress); + volumeProgress.setAlarmValue(1.1f); + volumeProgress.setValueColor(getContext().getColor(R.color.volume_progress_value));//白色 + setVolumeText(GeneralVariables.volumePercent); + volumeSeekBar.setProgress((int) (GeneralVariables.volumePercent*100)); + + GeneralVariables.mutableVolumePercent.observeForever(new Observer() { + @Override + public void onChanged(Float aFloat) { + setVolumeText(aFloat); + } + }); + + + volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + GeneralVariables.volumePercent=i/100f; + GeneralVariables.mutableVolumePercent.postValue(i/100f); + mainViewModel.databaseOpr.writeConfig("volumeValue",String.valueOf(i),null); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + } + + private void setVolumeText(float vol){ + volumeValueMessage.setText(String.format( + GeneralVariables.getStringFromResource(R.string.volume_percent) + , vol*100f)); + volumeProgress.setPercent(vol); + + } + + /** + * 把配置信息写到数据库 + * + * @param Value 值 + */ + private void writeConfig(String Value) { + mainViewModel.databaseOpr.writeConfig("volumeValue", Value, null); + } + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); + if (width > height) { + params.width = (int) (width * 0.7); + //params.height = (int) (height * 0.6); + } else { + params.width = (int) (width * 0.95); + //params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java new file mode 100644 index 0000000..2498bb5 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java @@ -0,0 +1,219 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 频谱图的主界面。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import static android.view.MotionEvent.ACTION_UP; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.databinding.FragmentSpectrumBinding; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +/** + * A simple {@link Fragment} subclass. + * create an instance of this fragment. + */ +public class SpectrumFragment extends Fragment { + private static final String TAG = "SpectrumFragment"; + private FragmentSpectrumBinding binding; + private MainViewModel mainViewModel; + + + private int frequencyLineTimeOut = 0;//画频率线的时间量 + + + static { + System.loadLibrary("ft8cn"); + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mainViewModel = MainViewModel.getInstance(this); + binding = FragmentSpectrumBinding.inflate(inflater, container, false); + binding.columnarView.setShowBlock(true); + binding.deNoiseSwitch.setChecked(mainViewModel.deNoise);//噪声抑制 + binding.waterfallView.setDrawMessage(false); + setDeNoiseSwitchState(); + setMarkMessageSwitchState(); + + binding.rulerFrequencyView.setFreq(Math.round(GeneralVariables.getBaseFrequency())); + mainViewModel.currentMessages=null; + + + //原始频谱开关 + binding.deNoiseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mainViewModel.deNoise = b; + setDeNoiseSwitchState(); + mainViewModel.currentMessages=null; + } + }); + //标记消息开关 + binding.showMessageSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mainViewModel.markMessage = b; + setMarkMessageSwitchState(); + } + }); + + //当声音变化,画频谱 + mainViewModel.spectrumListener.mutableDataBuffer.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(float[] floats) { + drawSpectrum(floats); + } + }); + + + + //观察解码的时长 + mainViewModel.ft8SignalListener.decodeTimeSec.observe(getViewLifecycleOwner(), new Observer() { + @SuppressLint("DefaultLocale") + @Override + public void onChanged(Long aLong) { + binding.decodeDurationTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.decoding_takes_milliseconds), aLong)); + } + }); + //观察解码的变化 + mainViewModel.mutableIsDecoding.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + binding.waterfallView.setDrawMessage(!aBoolean);//false说明解码完毕 + } + }); + + + //显示UTC时间 + mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Long aLong) { + binding.timersTextView.setText(UtcTimer.getTimeStr(aLong)); + binding.freqBandTextView.setText(GeneralVariables.getBandString()); + } + }); + + + //触摸频谱时的动作 + View.OnTouchListener touchListener = new View.OnTouchListener() { + @SuppressLint("DefaultLocale") + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + + frequencyLineTimeOut = 60;//显示频率线的时长:60*0.16 + + binding.waterfallView.setTouch_x(Math.round(motionEvent.getX())); + binding.columnarView.setTouch_x(Math.round(motionEvent.getX())); + + + + if (!mainViewModel.ft8TransmitSignal.isSynFrequency() + && (binding.waterfallView.getFreq_hz() > 0) + && (motionEvent.getAction() == ACTION_UP) + ) {//如果时异频发射 + mainViewModel.databaseOpr.writeConfig("freq", + String.valueOf(binding.waterfallView.getFreq_hz()), + null); + mainViewModel.ft8TransmitSignal.setBaseFrequency( + (float) binding.waterfallView.getFreq_hz()); + + binding.rulerFrequencyView.setFreq(binding.waterfallView.getFreq_hz()); + + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.sound_frequency_is_set_to) + , binding.waterfallView.getFreq_hz()),true); + } + }); + } + return false; + } + }; + + binding.waterfallView.setOnTouchListener(touchListener); + binding.columnarView.setOnTouchListener(touchListener); + + return binding.getRoot(); + } + + + + public void drawSpectrum(float[] buffer) { + if (buffer.length <= 0) { + return; + } + int[] fft = new int[buffer.length / 2]; + if (mainViewModel.deNoise) { + getFFTDataFloat(buffer, fft); + } else { + getFFTDataRawFloat(buffer, fft); + } + frequencyLineTimeOut--; + if (frequencyLineTimeOut < 0) { + frequencyLineTimeOut = 0; + } + //达到显示的时长,就取取消掉频率线 + if (frequencyLineTimeOut == 0) { + binding.waterfallView.setTouch_x(-1); + binding.columnarView.setTouch_x(-1); + } + binding.columnarView.setWaveData(fft); + if (mainViewModel.markMessage) {//是否标记消息 + binding.waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), mainViewModel.currentMessages); + } else { + binding.waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), null); + } + } + + private void setDeNoiseSwitchState() { + if (mainViewModel.deNoise) { + binding.deNoiseSwitch.setText(getString(R.string.de_noise)); + } else { + binding.deNoiseSwitch.setText(getString(R.string.raw_spectrum_data)); + } + } + private void setMarkMessageSwitchState(){ + if (mainViewModel.markMessage) { + binding.showMessageSwitch.setText(getString(R.string.markMessage)); + } else { + binding.showMessageSwitch.setText(getString(R.string.unMarkMessage)); + } + } + + public native void getFFTData(int[] data, int fftData[]); + + public native void getFFTDataFloat(float[] data ,int fftData[]); + + + + public native void getFFTDataRaw(int[] data, int fftData[]); + public native void getFFTDataRawFloat(float[] data,int fftData[]); + +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java new file mode 100644 index 0000000..6dbd23c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java @@ -0,0 +1,207 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 包含瀑布图、频率柱状图、标尺的自定义控件。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import static android.view.MotionEvent.ACTION_UP; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.Switch; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +public class SpectrumView extends ConstraintLayout { + private MainViewModel mainViewModel; + private ColumnarView columnarView; + private Switch controlDeNoiseSwitch; + private Switch controlShowMessageSwitch; + private WaterfallView waterfallView; + private RulerFrequencyView rulerFrequencyView; + private Fragment fragment; + + + private int frequencyLineTimeOut = 0;//画频率线的时间量 + + static { + System.loadLibrary("ft8cn"); + } + + + + public SpectrumView(@NonNull Context context) { + super(context); + } + + public SpectrumView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + View view = (View) View.inflate(context, R.layout.spectrum_layout,this); + } + + + @SuppressLint("ClickableViewAccessibility") + public void run(MainViewModel mainViewModel , Fragment fragment){ + this.mainViewModel = MainViewModel.getInstance(null); + this.fragment=fragment; + columnarView=findViewById(R.id.controlColumnarView); + controlDeNoiseSwitch=findViewById(R.id.controlDeNoiseSwitch); + waterfallView=findViewById(R.id.controlWaterfallView); + rulerFrequencyView=findViewById(R.id.controlRulerFrequencyView); + controlShowMessageSwitch=findViewById(R.id.controlShowMessageSwitch); + + + setDeNoiseSwitchState(); + setMarkMessageSwitchState(); + + rulerFrequencyView.setFreq(Math.round(GeneralVariables.getBaseFrequency())); + mainViewModel.currentMessages=null; + + + //原始频谱开关 + controlDeNoiseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mainViewModel.deNoise = b; + setDeNoiseSwitchState(); + mainViewModel.currentMessages=null; + } + }); + //标记消息开关 + controlShowMessageSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean b) { + mainViewModel.markMessage = b; + setMarkMessageSwitchState(); + } + }); + + //当声音变化,画频谱 + mainViewModel.spectrumListener.mutableDataBuffer.observe(fragment.getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(float[] ints) { + drawSpectrum(ints); + } + }); + + + //观察解码的变化 + mainViewModel.mutableIsDecoding.observe(fragment.getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + waterfallView.setDrawMessage(!aBoolean);//aBoolean==false说明解码完毕 + } + }); + + //触摸频谱时的动作 + View.OnTouchListener touchListener = new View.OnTouchListener() { + @SuppressLint("DefaultLocale") + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + + frequencyLineTimeOut = 60;//显示频率线的时长:60*0.16 + + waterfallView.setTouch_x(Math.round(motionEvent.getX())); + columnarView.setTouch_x(Math.round(motionEvent.getX())); + + + if (!mainViewModel.ft8TransmitSignal.isSynFrequency() + && (waterfallView.getFreq_hz() > 0) + && (motionEvent.getAction() == ACTION_UP) + ) {//如果时异频发射 + mainViewModel.databaseOpr.writeConfig("freq", + String.valueOf(waterfallView.getFreq_hz()), + null); + mainViewModel.ft8TransmitSignal.setBaseFrequency( + (float) waterfallView.getFreq_hz()); + + rulerFrequencyView.setFreq(waterfallView.getFreq_hz()); + + fragment.requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.sound_frequency_is_set_to) + , waterfallView.getFreq_hz()),true); + } + }); + } + return false; + } + }; + + waterfallView.setOnTouchListener(touchListener); + columnarView.setOnTouchListener(touchListener); + + + } + private void setDeNoiseSwitchState() { + if (mainViewModel==null) return; + controlDeNoiseSwitch.setChecked(mainViewModel.deNoise); + if (mainViewModel.deNoise) { + controlDeNoiseSwitch.setText(GeneralVariables.getStringFromResource(R.string.de_noise)); + } else { + controlDeNoiseSwitch.setText(GeneralVariables.getStringFromResource(R.string.raw_spectrum_data)); + } + } + private void setMarkMessageSwitchState(){ + if (mainViewModel.markMessage) { + controlShowMessageSwitch.setText(GeneralVariables.getStringFromResource(R.string.markMessage)); + } else { + controlShowMessageSwitch.setText(GeneralVariables.getStringFromResource(R.string.unMarkMessage)); + } + } + + + + + public void drawSpectrum(float[] buffer) { + if (buffer.length <= 0) { + return; + } + int[] fft = new int[buffer.length / 2]; + if (mainViewModel.deNoise) { + getFFTDataFloat(buffer, fft); + } else { + getFFTDataRawFloat(buffer, fft); + } + frequencyLineTimeOut--; + if (frequencyLineTimeOut < 0) { + frequencyLineTimeOut = 0; + } + //达到显示的时长,就取取消掉频率线 + if (frequencyLineTimeOut == 0) { + waterfallView.setTouch_x(-1); + columnarView.setTouch_x(-1); + } + columnarView.setWaveData(fft); + if (mainViewModel.markMessage) {//是否标记消息 + waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), mainViewModel.currentMessages); + } else { + waterfallView.setWaveData(fft, UtcTimer.getNowSequential(), null); + } + } + + + public native void getFFTData(int[] data, int fftData[]); + public native void getFFTDataFloat(float[] data, int fftData[]); + + public native void getFFTDataRaw(int[] data, int fftData[]); + public native void getFFTDataRawFloat(float[] data, int fftData[]); + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java new file mode 100644 index 0000000..1a8a8ea --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java @@ -0,0 +1,82 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 提示消息。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; + +import com.bg7yoz.ft8cn.GeneralVariables; + +import java.util.ArrayList; + +public class ToastMessage { + private static final String TAG="ToastMessage"; + //private static Activity activity; + private static ToastMessage toastMessage=null; + private static final ArrayList debugList=new ArrayList<>(); + public static ToastMessage getInstance(){ + if (toastMessage==null){ + toastMessage=new ToastMessage(); + } + return toastMessage; + } + + //public ToastMessage(Activity activity) { + // this.activity = activity; + //} + public ToastMessage() { + //this.activity = activity; + } + + public static void show(String message){ + addDebugInfo(message); + } + public static synchronized void show(String message,boolean clearMessage){ + if (clearMessage) { + debugList.clear(); + } + show(message); + } + @SuppressLint("DefaultLocale") + private static synchronized void addDebugInfo(String s){ + if (debugList.size()>5){ + //if (debugList.size()>20){ + debugList.remove(0); + } + final String info=s; + debugList.add(info); + GeneralVariables.mutableDebugMessage.postValue(getDebugMessage()); + + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + for (int i = 0; i offsetTime=new ArrayList<>(); + private Context mContext; + + public UtcOffsetSpinnerAdapter(Context context) { + mContext=context; + for (int i = 0; i < 30; i++) { + offsetTime.add(i*5-75); + } + } + + @Override + public int getCount() { + return offsetTime.size(); + } + + @Override + public Object getItem(int i) { + return offsetTime.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"DefaultLocale", "ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.utc_time_offset_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.serialPortItemTextView); + textView.setText(String.format(GeneralVariables.getStringFromResource(R.string.offset_time_sec) + ,(offsetTime.get(i) /10f))); + } + return view; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java new file mode 100644 index 0000000..543c03b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java @@ -0,0 +1,162 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 自定义音频强度的图形控件。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.Nullable; + +public class VolumeProgress extends View { + private Paint mRadarPaint; + private Paint mValuePaint; + + private Path mLinePath;//外部容器形状 + private Path mValuePath;//内部填充的形状 + private float mPercent=0.45f; + private int width,high; + + private int radarColor=Color.WHITE; + private int valueColor=Color.WHITE; + private int alarmColor=Color.RED; + private float alarmValue=0.5f; + + public VolumeProgress(Context context) { + super(context); + } + + public VolumeProgress(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public VolumeProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private int dpToPixel(int dp) { + return (int) (dp * getResources().getDisplayMetrics().density); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + width=w; + high=h; + postInvalidate(); + super.onSizeChanged(w, h, oldw, oldh); + } + + public void setPercent(float percent) { + mPercent = percent; + + init(); + invalidate(); + } + public void reDraw(){ + setPercent(mPercent); + } + + public void init() { + //绘制外部容器 + mRadarPaint = new Paint(); + mRadarPaint.setAntiAlias(true); + mRadarPaint.setStrokeWidth(dpToPixel(2)); + mRadarPaint.setStyle(Paint.Style.STROKE); + mRadarPaint.setColor(radarColor); + + //绘制填充的内容 + mValuePaint = new Paint(); + mValuePaint.setStrokeWidth(dpToPixel(2)); + mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE); + + + //绘制外部容器路径 + mLinePath = new Path(); + mValuePath = new Path(); + + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + //绘制外部容器 + drawLines(canvas); + //绘制每部填充 + drawRegion(canvas,mPercent); + } + + //绘制外部容器,顺时针,O-A-B-C + public void drawLines(Canvas canvas) { + mRadarPaint.setColor(radarColor); + mLinePath.reset(); + + float xa = 0;//(float) (mCenterX + width); + float ya = 0;//(float) (mCenterY); + mLinePath.moveTo(xa,ya); + + float xb = width; + float yb = high; + mLinePath.lineTo(xb, yb); + + float xc =0; + float yc = high; + mLinePath.lineTo(xc, yc); + + mLinePath.close(); + canvas.drawPath(mLinePath, mRadarPaint); + } + + //绘制覆盖图层,顺时针,O-Q-P-C + public void drawRegion(Canvas canvas, float percent) { + if (alarmValue>mPercent) { + mValuePaint.setColor(valueColor); + }else { + mValuePaint.setColor(alarmColor); + } + + //直线CB与QH,求出交点坐标P, + //其中H点=(是以B点的Y坐标,Q点的X坐标),起始就是QP的延长线 + float xa=(1f-percent)*width; + float ya=(1f-percent)*high; + float xb=width; + float yb=high; + float xc=(1f-percent)*width; + float yc=high; + + mValuePath.moveTo(xa, ya); + mValuePath.lineTo(xb, yb); + mValuePath.lineTo(xc, yc); + + + mValuePath.close(); + canvas.drawPath(mValuePath, mValuePaint); + } + + //求两直线相交的坐标 + + public void setRadarColor(int radarColor) { + this.radarColor = radarColor; + } + + public void setValueColor(int valueColor) { + this.valueColor = valueColor; + } + + public void setAlarmColor(int alarmColor) { + this.alarmColor = alarmColor; + } + + public void setAlarmValue(float alarmValue) { + this.alarmValue = alarmValue; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java new file mode 100644 index 0000000..a0fc9ed --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java @@ -0,0 +1,281 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 瀑布图自定义控件。 + * + * @author BGY70Z + * @date 2023-03-20 + */ + +import static android.graphics.Bitmap.Config.ARGB_8888; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.timer.UtcTimer; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +public class WaterfallView extends View { + private int blockHeight = 2;//色块高度 + private float freq_width = 1;//频率的宽度 + private final int cycle = 2; + private final int symbols = 93; + private int lastSequential = 0; + private Bitmap lastBitMap = null; + private Canvas _canvas; + private final Paint linePaint = new Paint(); + private Paint touchPaint = new Paint(); + private final Paint fontPaint = new Paint(); + private final Paint messagePaint = new Paint(); + private final Paint messagePaintBack = new Paint();//消息背景 + private final Paint utcPaint = new Paint(); + Paint linearPaint = new Paint(); + private final Paint utcPainBack = new Paint(); + private float pathStart = 0; + private float pathEnd = 0; + + private int touch_x = -1; + private int freq_hz = -1; + private boolean drawMessage = false;//是否画消息内容 + + public WaterfallView(Context context) { + super(context); + } + + public WaterfallView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public WaterfallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + ArrayList messages= new ArrayList<>(); + + + /** + * 把dp值转换为像素点 + * + * @param dp dp值 + * @return 像素点 + */ + private int dpToPixel(int dp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp + , getResources().getDisplayMetrics()); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setClickable(true); + blockHeight = getHeight() / (symbols * cycle); + freq_width = (float) getWidth() / 3000f; + lastBitMap = Bitmap.createBitmap(w, h, ARGB_8888); + _canvas = new Canvas(lastBitMap); + Paint blackPaint = new Paint(); + blackPaint.setColor(0xFF000000); + _canvas.drawRect(0, 0, w, h, blackPaint);//先把背景画黑,防止文字重叠 + + //linePaint = new Paint(); + linePaint.setColor(0xff990000); + touchPaint = new Paint(); + touchPaint.setColor(0xff00ffff); + touchPaint.setStrokeWidth(getResources().getDisplayMetrics().density); + + + //fontPaint = new Paint(); + fontPaint.setTextSize(dpToPixel(10)); + fontPaint.setColor(0xff00ffff); + fontPaint.setAntiAlias(true); + fontPaint.setDither(true); + fontPaint.setTextAlign(Paint.Align.LEFT); + + // messagePaint = new Paint(); + messagePaint.setTextSize(dpToPixel(11)); + messagePaint.setColor(0xff00ffff); + messagePaint.setAntiAlias(true); + messagePaint.setDither(true); + messagePaint.setStrokeWidth(0); + messagePaint.setStyle(Paint.Style.FILL_AND_STROKE); + messagePaint.setTextAlign(Paint.Align.CENTER); + + //messagePaintBack = new Paint(); + messagePaintBack.setTextSize(dpToPixel(11)); + messagePaintBack.setColor(0xff000000);//背景不透明 + messagePaintBack.setAntiAlias(true); + messagePaintBack.setDither(true); + messagePaintBack.setStrokeWidth(dpToPixel(3)); + messagePaintBack.setFakeBoldText(true); + messagePaintBack.setStyle(Paint.Style.FILL_AND_STROKE); + messagePaintBack.setTextAlign(Paint.Align.CENTER); + + //utcPaint = new Paint(); + utcPaint.setTextSize(dpToPixel(10)); + utcPaint.setColor(0xff00ffff);// + utcPaint.setAntiAlias(true); + utcPaint.setDither(true); + utcPaint.setStrokeWidth(0); + utcPaint.setStyle(Paint.Style.FILL_AND_STROKE); + utcPaint.setTextAlign(Paint.Align.LEFT); + + //utcPainBack = new Paint(); + utcPainBack.setTextSize(dpToPixel(10)); + utcPainBack.setColor(0xff000000);//背景不透明 + utcPainBack.setAntiAlias(true); + utcPainBack.setDither(true); + utcPainBack.setStrokeWidth(dpToPixel(4)); + utcPainBack.setStyle(Paint.Style.FILL_AND_STROKE); + utcPainBack.setTextAlign(Paint.Align.LEFT); + + + pathStart = blockHeight * 2; + pathEnd = blockHeight * 90; + if (pathEnd < 130 * getResources().getDisplayMetrics().density) {//为了保证能写的下 + pathEnd = 130 * getResources().getDisplayMetrics().density; + } + + super.onSizeChanged(w, h, oldw, oldh); + + } + + @SuppressLint("DefaultLocale") + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawBitmap(lastBitMap, 0, 0, null); + + //计算频率 + if (touch_x > 0) {//画触摸线 + freq_hz = Math.round(3000f * (float) touch_x / (float) getWidth()); + if (freq_hz > 2900) { + freq_hz = 2900; + } + if (freq_hz < 100) { + freq_hz = 100; + } + + if (touch_x > getWidth() / 2) { + fontPaint.setTextAlign(Paint.Align.RIGHT); + canvas.drawText(String.format("%dHz", freq_hz) + , touch_x - 10, 250, fontPaint); + } else { + fontPaint.setTextAlign(Paint.Align.LEFT); + canvas.drawText(String.format("%dHz", freq_hz) + , touch_x + 10, 250, fontPaint); + } + canvas.drawLine(touch_x, 0, touch_x, getHeight(), touchPaint); + + } + invalidate(); + } + + public void setWaveData(int[] data, int sequential, List msgs) { + if (drawMessage&& msgs!=null){//把需要画的消息复制出来防止多线程访问冲突 + messages=new ArrayList<>(msgs); + } + + if (data == null) { + return; + } + if (data.length <= 0) { + return; + } + if (lastBitMap == null) { + return; + } + + int[] colors = new int[data.length]; + + //画分割线 + if (sequential != lastSequential) { + Bitmap bitmap = Bitmap.createBitmap(lastBitMap, 0, 0, getWidth(), getHeight() - blockHeight); + _canvas.drawBitmap(bitmap, 0, blockHeight, linePaint); + bitmap.recycle(); + _canvas.drawRect(0, 0, getWidth(), getResources().getDisplayMetrics().density + , linePaint); + _canvas.drawText(UtcTimer.getTimeStr(UtcTimer.getSystemTime()), 50 + , 15 * getResources().getDisplayMetrics().density, utcPainBack); + _canvas.drawText(UtcTimer.getTimeStr(UtcTimer.getSystemTime()), 50 + , 15 * getResources().getDisplayMetrics().density, utcPaint); + } + lastSequential = sequential; + + //色块分布 + for (int i = 0; i < data.length; i++) { + + + if (data[i] < 128) {//低于一半的音量,用蓝色0~256 + colors[i] = 0xff000000 | (data[i] << 1); + } else if (data[i] < 192) { + colors[i] = 0xff0000ff | (((data[i] - 127)) << 10);//放大4倍 +// colors[i] = 0xff000000 | (data[i] * 2 * 256 + 255); + } else { + colors[i] = 0xff00ffff | (((data[i] - 127)) << 18);//放大4倍 + } + } + LinearGradient linearGradient = new LinearGradient(0, 0, getWidth() * 2, 0, colors + , null, Shader.TileMode.CLAMP); + //Paint linearPaint = new Paint(); + linearPaint.setShader(linearGradient); + Bitmap bitmap = Bitmap.createBitmap(lastBitMap, 0, 0, getWidth(), getHeight() - blockHeight); + _canvas.drawBitmap(bitmap, 0, blockHeight, linearPaint); + bitmap.recycle(); + _canvas.drawRect(0, 0, getWidth(), blockHeight, linearPaint); + + //消息有3种:普通、CQ、有我 + if (drawMessage && messages != null) { + drawMessage = false;//只画一遍 + fontPaint.setTextAlign(Paint.Align.LEFT); + for (Ft8Message msg : messages) { + + if (msg.inMyCall()) {//与我有关 + messagePaint.setColor(0xffffb2b2); + } else if (msg.checkIsCQ()) {//CQ + messagePaint.setColor(0xffeeee00); + } else { + messagePaint.setColor(0xff00ffff); + } + Path path = new Path(); + + path.moveTo(msg.freq_hz * freq_width, pathStart); + path.lineTo(msg.freq_hz * freq_width, pathEnd); + + + _canvas.drawTextOnPath(msg.getMessageText(true), path + , 0, 0, messagePaintBack);//消息背景 + _canvas.drawTextOnPath(msg.getMessageText(true), path + , 0, 0, messagePaint);//消息 + + } + } + + + } + + public void setTouch_x(int touch_x) { + this.touch_x = touch_x; + } + + public void setDrawMessage(boolean drawMessage) { + this.drawMessage = drawMessage; + } + + public int getFreq_hz() { + return freq_hz; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java new file mode 100644 index 0000000..0755f3d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java @@ -0,0 +1,306 @@ +package com.bg7yoz.ft8cn.wave; + +import android.annotation.SuppressLint; +import android.media.AudioFormat; +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +/** + * 录音类。通过AudioRecord对象来实现录音。 + * HamRecorder录音的数据通过监听类GetVoiceData来实现。HamRecorder实例中有一个监听器列表onGetVoiceList。 + * 当有录音数据后,HamRecorder会触发监听器列表中各监听器的OnReceiveData回调。 + * 制作此类的目的,是防止FT8各录音时序因录音启动时间的问题,造成重叠创建录音对象或录音的时长达不到一个时序的时长(15秒) + *

+ * @author BG7YOZ + * @date 2022-05-31 + */ + +public class HamRecorder { + private static final String TAG = "HamRecorder"; + //private int bufferSize = 0;//最小缓冲区大小 + private static final int sampleRateInHz = 12000;//采样率 + private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; //单声道 + //private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //量化位数 + private static final int audioFormat = AudioFormat.ENCODING_PCM_FLOAT; //量化位数 + + //private AudioRecord audioRecord = null;//AudioRecord对象 + private boolean isRunning = false;//是否处于录音的状态。 + + private final ArrayList voiceDataMonitorList = new ArrayList<>();//监听回调列表,在监听回调中获取数据。 + private OnVoiceMonitorChanged onVoiceMonitorChanged=null; + + private boolean isMicRecord=true; + private MicRecorder micRecorder=new MicRecorder(); + + + public HamRecorder(OnVoiceMonitorChanged onVoiceMonitorChanged){ + this.onVoiceMonitorChanged=onVoiceMonitorChanged; + } + + + public void setDataFromMic(){ + isMicRecord=true; + startRecord(); + } + public void setDataFromLan(){ + isMicRecord=false; + micRecorder.stopRecord(); + } + + /** + * 当接收到音频数据,所要处理的事情 + * @param bufferLen 数据的长度 + * @param buffer 数据缓冲区 + */ + public void doOnWaveDataReceived(int bufferLen,float[] buffer){ + if (!isRunning) return; + for (int i = 0; i < voiceDataMonitorList.size(); i++) { + //逐个监听器调用回调,把数据提供给回调函数 + if (voiceDataMonitorList.get(i)!=null) { + voiceDataMonitorList.get(i).onHamRecord.OnReceiveData(buffer, bufferLen); + } + } + + //doDataMonitorChanged(); + } + + + /** + * 是否处于录音状态 + * + * @return boolean,是否处于录音状态 + */ + public boolean isRunning() { + return isRunning; + } + + /** + * 开始录音,此方法使设备一直处于录音状态,录音数据的获取通过监听器类GetVoiceData来实现。 + * 录音对象在读取到数据(audioRecord.read)后,把监听器列表中的所有监听器的OnReceiveData回调都调用一次。 + * 录音的状态在isRecording中。 + */ + @SuppressLint("MissingPermission") + public void startRecord() { + if (isMicRecord){//如果是用MIC采集声音 + micRecorder.start(); + micRecorder.setOnDataListener(new MicRecorder.OnDataListener() { + @Override + public void onDataReceived(float[] data, int len) { + doOnWaveDataReceived(len,data); + } + }); + } + isRunning=true; + + } + + private void doDataMonitorChanged(){ + if (onVoiceMonitorChanged!=null){ + onVoiceMonitorChanged.onMonitorChanged(voiceDataMonitorList.size()); + } + } + /** + * 删除数据监听器 + * @param monitor 数据监听器 + */ + public void deleteVoiceDataMonitor(VoiceDataMonitor monitor) { + voiceDataMonitorList.remove(monitor); + doDataMonitorChanged(); + } + + /** + * 获取监听器的数量 + * @return 返回数量 + */ + public int getVoiceMonitorCount(){ + return voiceDataMonitorList.size(); + } + + /** + * 获取监听器的列表 + * @return 监听器列表 + */ + public ArrayList getVoiceDataMonitors(){ + return this.voiceDataMonitorList; + } + + /** + * 停止录音。当录音停止后,监听列表中的监听器全部删除。 + */ + public void stopRecord() { + micRecorder.stopRecord(); + isRunning = false; + } + + /** + * 获取录音数据的方法,通过加载数据监听器(VoiceDataMonitor)的方法实现。 + * 录音数据在OnGetVoiceDataDone回调中,当录音达到指定的时长(毫秒)触发。 + * 获取录音,是给录音对象加载一个监听器对象,在监听器的OnReceiveData回调中获取数据,当数据达到预期的数量时, + * 触发OnGetVoiceDataDone回调。该回调动作在另一个线程中,要注意UI的处理。 + * 监听有两种模式:一次性、循环。 + * 一次性:获取数据后,此监听器自动删除,不再触发。 + * 循环,监听器始终存在,获取数据后,重新复位数据,进入下一次监听状态。直到录音停止,监听器才被删除。 + * duration毫秒 + * + * @param duration 录音数据的时长(毫秒) + * @param afterDoneRemove 获取录音后是否删除监听器,false:循环获取录音数据。 + * @param getVoiceDataDone 当录音数据达到指定的时长后,触发此回调 + */ + public VoiceDataMonitor getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone) { + if (isRunning) { + VoiceDataMonitor dataMonitor = new VoiceDataMonitor(duration, this + , afterDoneRemove, getVoiceDataDone); + dataMonitor.voiceDataMonitor = dataMonitor;//用于监听器删除自己用。 + voiceDataMonitorList.add(dataMonitor); + doDataMonitorChanged(); + return dataMonitor; + } else { + return null; + } + } + + /** + * 监听器类,用于录音数据的获取。 + * 当监听类,需要设定录音的时长(毫秒),当达到指定的时长后,会产生一个OnGetVoiceDataDone回调,在此回调中,可以获得 + * 该时长的录音数据。可以设定此监听是一次性的(afterDoneRemove=true),还是循环往复的(afterDoneRemove=false)。 + * 一次性的,就是监听达到指定时长后,就不继续监听了,录音实例会把该监听删除。 + * 循环往复,就是监听到指定时长后,复位,继续重新监听。此模式方便形成波表数据。 + */ + static class VoiceDataMonitor { + private final String TAG = "GetVoiceData"; + private final float[] voiceData;//录音数据。大小由时长、采样率、采样位决定的。 + private int dataCount;//计数器,当前数据的获取量 + + //onHamRecord是当录音对象有数据时触发的回调,通过该回调填充voiceData缓冲区,当缓冲区满时,触发OnGetVoiceDataDone回调。 + public OnHamRecord onHamRecord; + //getVoiceData是本监听器的地址,用于在录音对象的监听列表中删除本监听器。 + // 在GetVoiceData构建后,注意!!!一定要对该变量赋值!否则无法删除本监听器。 + public VoiceDataMonitor voiceDataMonitor = null; + + /** + * 监听类,用于录音数据的获取 + * GetVoiceData类的构建方法。此类是用于添加到录音类HamRecorder中onGetVoiceList,当有录音数据返回时,产生回调。 + * 此类的目的就是录音时,可以有多个对象从录音中获取数据,而不产生冲突。 + * + * @param duration 获取录音数据的时长(毫秒)。 + * @param hamRecorder 录音类的实例。方便删除本监听器等操作。 + * @param afterDoneRemove 当达到录音的时长后,是否移除本监听实例,true:移除,false:不移除,循环监听 + * @param onGetVoiceDataDone 达到录音的时长后,触发此回调。为了防止占用太多录音的时间,此回调在另一个线程。 + */ + public VoiceDataMonitor(int duration, HamRecorder hamRecorder, boolean afterDoneRemove + , OnGetVoiceDataDone onGetVoiceDataDone) { + //时长,毫秒 + //宿主对象,方便用词对象调用删除数据获取动作列表中的本实例 + + dataCount = 0;//当前的数据获取量 + //生成预期大小中的数据缓冲区。 + //因为是16Bit采样,所以byte*2。 + //voiceData = new byte[duration * HamRecorder.sampleRateInHz * 2 / 1000]; + voiceData = new float[duration * HamRecorder.sampleRateInHz / 1000]; + + //当有录音数据时触发的回调函数。 + onHamRecord = new OnHamRecord() { + @Override + public void OnReceiveData(float[] data, int size) { + for (int i = 0; (i < size) && (dataCount < voiceData.length); i++) { + voiceData[dataCount] = data[i];//把录音缓冲区的数据搬运到本监听器中来 + dataCount++; + } + if (dataCount >= (voiceData.length)) {//当数据量达到所需要的。发起回调。 +// new Thread(new Runnable() {//以新的线程运行,防止占用过多的录音时间。 +// @Override +// public void run() { + onGetVoiceDataDone.onGetDone(voiceData); +// } +// }).start(); + + if (afterDoneRemove) {//如果是一次性的获取数据,则在录音对象中的监听列表中删除此监听回调。 + hamRecorder.deleteVoiceDataMonitor(voiceDataMonitor); + } else { + dataCount = 0;//如果是循环录音,则复位计数器。 + } + } + } + }; + + } + + } + + /** + * 类方法,把数据保存到文件中去,是临时文件名。 + * @param data 数据 + * @return 返回生成的临时文件名。 + */ + public static String saveDataToFile(byte[] data) { + String audioFileName = null; + File recordingFile; + try { + //生成临时文件名 + recordingFile = File.createTempFile("Audio", ".wav", null); + audioFileName = recordingFile.getPath(); + + //数据流文件 + DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(audioFileName))); + //写Wav文件头 + new WriteWavHeader(data.length, sampleRateInHz, channelConfig, audioFormat).writeHeader(dos); + for (int i = 0; i < data.length; i++) { + dos.write(data[i]); + } + Log.d(TAG, String.format("生成文件结束(%d字节,%.2f秒),文件:%s", data.length + 44 + , ((float) data.length / 2 / sampleRateInHz), audioFileName)); + dos.close();//关闭文件流 + + + } catch (IOException e) { + Log.e(TAG, String.format("生成临时文件出错!%s", e.getMessage())); + } + + return audioFileName; + } + + /** + * 把原始的声音数据转换成16位的数组数据。 + * @param buffer 原始的声音数据(8位) + * @return 返回16位的int格式数组 + */ + public static int[] byteDataTo16BitData(byte[] buffer){ + int[] data=new int[buffer.length /2]; + for (int i = 0; i < buffer.length/2; i++) { + int res = (buffer[i*2] & 0x000000FF) | (((int) buffer[i*2+1]) << 8); + data[i]=res; + } + return data; + } + + /** + * 把原始的声音数据转换成浮点数组数据 + * @param bytes 原始的声音数据(float) + * @return 转换成float数组 + */ + public static float[] getFloatFromBytes(byte[] bytes) { + float[] floats = new float[bytes.length / 4]; + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes)); + for (int i = 0; i < floats.length; i++) { + try { + floats[i] = dis.readFloat(); + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + try { + dis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return floats; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java new file mode 100644 index 0000000..d47d79a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java @@ -0,0 +1,102 @@ +package com.bg7yoz.ft8cn.wave; +/** + * 使用Mic录音的操作。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +public class MicRecorder { + private static final String TAG = "MicRecorder"; + private int bufferSize = 0;//最小缓冲区大小 + private static final int sampleRateInHz = 12000;//采样率 + private static final int channelConfig = AudioFormat.CHANNEL_IN_MONO; //单声道 + //private static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT; //量化位数 + private static final int audioFormat = AudioFormat.ENCODING_PCM_FLOAT; //量化位数 + + private AudioRecord audioRecord = null;//AudioRecord对象 + private boolean isRunning = false;//是否处于录音的状态。 + private OnDataListener onDataListener; + + public interface OnDataListener{ + void onDataReceived(float[] data,int len); + } + + @SuppressLint("MissingPermission") + public MicRecorder(){ + //计算最小缓冲区 + bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); +// audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz + audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRateInHz + , channelConfig, audioFormat, bufferSize);//创建AudioRecorder对象 + } + + public void start(){ + if (isRunning) return; + + float[] buffer = new float[bufferSize]; + try { + audioRecord.startRecording();//开始录音 + }catch (Exception e){ + ToastMessage.show(String.format(GeneralVariables.getStringFromResource( + R.string.recorder_cannot_record),e.getMessage())); + Log.d(TAG, "startRecord: "+e.getMessage() ); + } + + isRunning = true; + + new Thread(new Runnable() { + @Override + public void run() { + while (isRunning) { + //判断是否处于录音状态,state!=3,说明没有处于录音的状态 + if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) { + isRunning = false; + Log.d(TAG, String.format("录音失败,状态码:%d", audioRecord.getRecordingState())); + break; + } + + //读录音的数据 + int bufferReadResult = audioRecord.read(buffer, 0, bufferSize,AudioRecord.READ_BLOCKING); + + if (onDataListener!=null){ + onDataListener.onDataReceived(buffer,bufferReadResult); + } + } + try { + if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { + audioRecord.stop();//停止录音 + } + }catch (Exception e){ + ToastMessage.show(String.format(GeneralVariables.getStringFromResource( + R.string.recorder_stop_record_error),e.getMessage())); + Log.d(TAG, "startRecord: "+e.getMessage() ); + } + } + }).start(); + } + + /** + * 停止录音。当录音停止后,监听列表中的监听器全部删除。 + */ + public void stopRecord() { + isRunning = false; + } + + public OnDataListener getOnDataListener() { + return onDataListener; + } + + public void setOnDataListener(OnDataListener onDataListener) { + this.onDataListener = onDataListener; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java new file mode 100644 index 0000000..350e75b --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java @@ -0,0 +1,26 @@ +package com.bg7yoz.ft8cn.wave; + +/** + *定义音频录音结束后的回调接口。 + *回调接口主要有2个,录音开始前,录音开始后。 + * 注意!!!录音采用多线程的方式,此处的回调不在主线程中,如果回调中有UI操作的话,要使用runOnUiThread方法,防止界面锁死。 + * + * @author BG7YOZ + * @date 2022.5.7 + */ + +public interface OnAudioRecorded { + /** + * 录音开始前的回调函数。 + * @param audioFileName 生成的Wav文件名 + */ + void beginAudioRecord(String audioFileName); + + /** + * 录音结束后的回调函数。 + * @param audioFileName Wav文件名 + * @param dataSize 录音数据的大小,byte[]格式,不包括wav文件头的长度,如果要算wav文件长度,在此基础上+44。 + * @param duration 实际录音的时长(秒) + */ + void endAudioRecorded(String audioFileName,long dataSize,float duration); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java new file mode 100644 index 0000000..5f95c2a --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.wave; + +/** + * 获取音频的回调 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnGetVoiceDataDone { + void onGetDone(float[] data); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java new file mode 100644 index 0000000..1d714f8 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.wave; + +/** + * 接收到音频的回调。 + * @author BGY70Z + * @date 2023-03-20 + */ +public interface OnHamRecord { + void OnReceiveData(float[] data,int size); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java new file mode 100644 index 0000000..938dc96 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java @@ -0,0 +1,5 @@ +package com.bg7yoz.ft8cn.wave; + +public interface OnVoiceMonitorChanged { + void onMonitorChanged(int count); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java new file mode 100644 index 0000000..a80ac1d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java @@ -0,0 +1,11 @@ +package com.bg7yoz.ft8cn.wave; + +/** + * 波形文件(.wav)的读写操作,支持16bit和8bitPCM编码的单双声道文件操作 + * 已经弃用,FT8CN目前不采用文件的方式。 + * @author BGY70Z + * @date 2023-03-20 + */ +public class WaveAccess { + public static final String VERSION = "1.1"; +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java new file mode 100644 index 0000000..19a7243 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java @@ -0,0 +1,28 @@ +package com.bg7yoz.ft8cn.wave; + +/** + * Wave文件操作所用的常量。 + * 已经弃用。FT8CN目前不采用文件方式处理音频。 + * @author BGY70Z + * @date 2023-03-20 + */ +public final class WaveConstants { + static public int LENCHUNKDESCRIPTOR = 4; + static public int LENCHUNKSIZE = 4; + static public int LENWAVEFLAG = 4; + static public int LENFMTSUBCHUNK = 4; + static public int LENSUBCHUNK1SIZE = 4; + static public int LENAUDIOFORMAT = 2; + static public int LENNUMCHANNELS = 2; + static public int LENSAMPLERATE = 2; + static public int LENBYTERATE = 4; + static public int LENBLOCKLING = 2; + static public int LENBITSPERSAMPLE = 2; + static public int LENDATASUBCHUNK = 4; + static public int LENSUBCHUNK2SIZE = 4; + + public static String CHUNKDESCRIPTOR = "RIFF"; + public static String WAVEFLAG = "WAVE"; + public static String FMTSUBCHUNK = "fmt "; + public static String DATASUBCHUNK = "data"; +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java new file mode 100644 index 0000000..8bda112 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java @@ -0,0 +1,202 @@ +package com.bg7yoz.ft8cn.wave; +/** + * 读取Wave文件的操作。 + * 已经弃用,FT8CN不采用音频文件的方法。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; + +@SuppressWarnings("unused") +public class WaveFileReader { + private String filename = null; + private int[][] data = null; + + private int len = 0; + + private String chunkdescriptor = null; + private long chunksize = 0; + private String waveflag = null; + private String fmtsubchunk = null; + private long subchunk1size = 0; + private int audioformat = 0; + private int numchannels = 0; + private long samplerate = 0; + private long byterate = 0; + private int blockalign = 0; + private int bitspersample = 0; + private String datasubchunk = null; + private long subchunk2size = 0; + private FileInputStream fis = null; + private BufferedInputStream bis = null; + + private boolean issuccess = false; + + public WaveFileReader(String filename) { + + this.initReader(filename); + } + + // 判断是否创建wav读取器成功 + public boolean isSuccess() { + return issuccess; + } + + // 获取每个采样的编码长度,8bit或者16bit + public int getBitPerSample() { + return this.bitspersample; + } + + // 获取采样率 + public long getSampleRate() { + return this.samplerate; + } + + // 获取声道个数,1代表单声道 2代表立体声 + public int getNumChannels() { + return this.numchannels; + } + + // 获取数据长度,也就是一共采样多少个 + public int getDataLen() { + return this.len; + } + + // 获取数据 + // 数据是一个二维数组,[n][m]代表第n个声道的第m个采样值 + public int[][] getData() { + return this.data; + } + + private void initReader(String filename) { + this.filename = filename; + + try { + fis = new FileInputStream(this.filename); + bis = new BufferedInputStream(fis); + + this.chunkdescriptor = readString(WaveConstants.LENCHUNKDESCRIPTOR); + if (!chunkdescriptor.endsWith("RIFF")) + throw new IllegalArgumentException("RIFF miss, " + filename + " is not a wave file."); + + this.chunksize = readLong(); + this.waveflag = readString(WaveConstants.LENWAVEFLAG); + if (!waveflag.endsWith("WAVE")) + throw new IllegalArgumentException("WAVE miss, " + filename + " is not a wave file."); + + this.fmtsubchunk = readString(WaveConstants.LENFMTSUBCHUNK); + if (!fmtsubchunk.endsWith("fmt ")) + throw new IllegalArgumentException("fmt miss, " + filename + " is not a wave file."); + + this.subchunk1size = readLong(); + this.audioformat = readInt(); + this.numchannels = readInt(); + this.samplerate = readLong(); + this.byterate = readLong(); + this.blockalign = readInt(); + this.bitspersample = readInt(); + + this.datasubchunk = readString(WaveConstants.LENDATASUBCHUNK); + if (!datasubchunk.endsWith("data")) + throw new IllegalArgumentException("data miss, " + filename + " is not a wave file."); + this.subchunk2size = readLong(); + + this.len = (int) (this.subchunk2size / (this.bitspersample / 8) / this.numchannels); + + this.data = new int[this.numchannels][this.len]; + + // 读取数据 + for (int i = 0; i < this.len; ++i) { + for (int n = 0; n < this.numchannels; ++n) { + if (this.bitspersample == 8) { + this.data[n][i] = bis.read(); + } else if (this.bitspersample == 16) { + this.data[n][i] = this.readInt(); + } + } + } + + issuccess = true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (bis != null) + bis.close(); + if (fis != null) + fis.close(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + } + + private String readString(int len) { + byte[] buf = new byte[len]; + try { + if (bis.read(buf) != len) + throw new IOException("no more data!!!"); + } catch (IOException e) { + e.printStackTrace(); + } + return new String(buf); + } + + private int readInt() { + byte[] buf = new byte[2]; + int res = 0; + try { + if (bis.read(buf) != 2) + throw new IOException("no more data!!!"); + res = (buf[0] & 0x000000FF) | (((int) buf[1]) << 8); + } catch (IOException e) { + e.printStackTrace(); + } + return res; + } + + private long readLong() { + long res = 0; + try { + long[] l = new long[4]; + for (int i = 0; i < 4; ++i) { + l[i] = bis.read(); + if (l[i] == -1) { + throw new IOException("no more data!!!"); + } + } + res = l[0] | (l[1] << 8) | (l[2] << 16) | (l[3] << 24); + } catch (IOException e) { + e.printStackTrace(); + } + return res; + } + + private byte[] readBytes(int len) { + byte[] buf = new byte[len]; + try { + if (bis.read(buf) != len) + throw new IOException("no more data!!!"); + } catch (IOException e) { + e.printStackTrace(); + } + return buf; + } + + public static int[] readSingleChannel(String filename) { + if (filename == null || filename.length() == 0) { + return null; + } + try { + WaveFileReader reader = new WaveFileReader(filename); + int[] res = reader.getData()[0]; + return res; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java new file mode 100644 index 0000000..dcc46bd --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java @@ -0,0 +1,144 @@ +package com.bg7yoz.ft8cn.wave; +/** + * 把音频保存成wave文件的操作。 + * 已经弃用,FT8CN目前不做音频的保存操作。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class WaveFileWriter { + private String filename = null; + private FileOutputStream fos = null; + private BufferedOutputStream bos = null; + + private long chunksize = 0; + private long subchunk1size = 0; + private int audioformat = 0; + private int numchannels = 0; + private long samplerate = 0; + private long byterate = 0; + private int blockalign = 0; + private int bitspersample = 0; + private long subchunk2size = 0; + + public WaveFileWriter(String filename, int[][] data, long samplerate,int bitspersample) { + this.initWriter(filename, data, 0, data[0].length, samplerate,bitspersample); + } + + public WaveFileWriter(String filename, int[][] data, int offset, int len, long samplerate,int bitspersample) { + this.initWriter(filename, data, offset, len, samplerate,bitspersample); + } + + public void initWriter(String filename, int[][] data, int offset, int len, long samplerate,int bitspersample) { + this.filename = filename; + + try { + fos = new FileOutputStream(this.filename); + bos = new BufferedOutputStream(fos); + + // int datalen = data[0].length; + int datalen = len; + + this.samplerate = samplerate; + // this.bitspersample = bitspersample; + this.bitspersample = bitspersample; + this.numchannels = data.length; + this.subchunk2size = this.numchannels * (this.bitspersample / 8) * datalen; + this.subchunk1size = 16; + this.audioformat = 1; // PCM + this.byterate = this.samplerate * this.bitspersample * this.numchannels / 8; + this.blockalign = this.numchannels * this.bitspersample / 8; + + this.chunksize = this.subchunk2size + 8 + this.subchunk1size + 8 + 4; + + writeString(WaveConstants.CHUNKDESCRIPTOR, WaveConstants.LENCHUNKDESCRIPTOR); + writeLong(this.chunksize); + writeString(WaveConstants.WAVEFLAG, WaveConstants.LENWAVEFLAG); + writeString(WaveConstants.FMTSUBCHUNK, WaveConstants.LENFMTSUBCHUNK); + writeLong(this.subchunk1size); + writeInt(this.audioformat); + writeInt(this.numchannels); + writeLong(this.samplerate); + writeLong(this.byterate); + writeInt(this.blockalign); + writeInt(this.bitspersample); + writeString(WaveConstants.DATASUBCHUNK, WaveConstants.LENDATASUBCHUNK); + writeLong(this.subchunk2size); + for (int i = 0; i < datalen; ++i) { + for (int n = 0; n < this.numchannels; ++n) { + if (this.bitspersample == 16) { + writeInt(data[n][i + offset]); + } else { + writeByte((byte) data[n][i + offset]); + } + } + } + bos.flush(); + fos.flush(); + bos.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + + } + + private void writeString(String str, int len) { + if (str.length() != len) { + throw new IllegalArgumentException("length not match!!!"); + } + byte[] bt = str.getBytes(); + try { + bos.write(bt); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeByte(byte data) { + try { + bos.write(new byte[] { data }, 0, 1); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeInt(int data) { + byte[] buf = new byte[2]; + buf[1] = (byte) (data >>> 8); + buf[0] = (byte) (data & 0xFF); + try { + bos.write(buf); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeLong(long data) { + byte[] buf = new byte[4]; + buf[0] = (byte) (data & 0x00ff); + buf[1] = (byte) ((data >> 8) & 0x00ff); + buf[2] = (byte) ((data >> 16) & 0x00ff); + buf[3] = (byte) ((data >> 24) & 0x00ff); + try { + bos.write(buf); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static boolean saveSingleChannel(String filename, int[] data, long samplerate,int bitspersample) { + int[][] datar = new int[1][]; + datar[0] = data; + WaveFileWriter writer = new WaveFileWriter(filename, datar, samplerate,bitspersample); + writer.close(); + return true; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java new file mode 100644 index 0000000..243a47c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java @@ -0,0 +1,128 @@ +package com.bg7yoz.ft8cn.wave; +/** + * 写WAVE文件的头。 + * 已经弃用。FT8CN不做音频文件的操作。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.media.AudioFormat; +import android.util.Log; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class WriteWavHeader { + private int samplesBits; + private final int channels; + private final int totalAudioLen; + private final int longSampleRate; + String TAG = "HamAudioRecorder";//调试用标志 + + public WriteWavHeader( int totalAudioLen, int longSampleRate, int channels, int samplesBits) { + if (samplesBits == AudioFormat.ENCODING_PCM_16BIT) + this.samplesBits = 16; + else if (samplesBits == AudioFormat.ENCODING_PCM_8BIT) + this.samplesBits = 8; + + if (channels == AudioFormat.CHANNEL_IN_STEREO) + this.channels = 2; + else + this.channels = 1; + this.totalAudioLen = totalAudioLen; + this.longSampleRate = longSampleRate; + + } + + private byte[] makeWaveHeader(){ + int file_size = totalAudioLen + 44 - 8;//文件大小,刨除前面RIFF和file_size + byte[] header = new byte[44]; + header[0] = 'R'; // RIFF/WAVE header + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + header[4] = (byte) (file_size & 0xff); + header[5] = (byte) ((file_size >> 8) & 0xff); + header[6] = (byte) ((file_size >> 16) & 0xff); + header[7] = (byte) ((file_size >> 24) & 0xff); + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + header[12] = 'f'; // 'fmt ' chunk + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + header[16] = 16; // 4 bytes: size of 'fmt ' chunk + header[17] = 0; + header[18] = 0; + header[19] = 0; + header[20] = 1; // format = 1表示PCM编码 + header[21] = 0; + header[22] = (byte) channels;//1为单声道,2是双声道 + header[23] = 0; + header[24] = (byte) (longSampleRate & 0xff); + header[25] = (byte) ((longSampleRate >> 8) & 0xff); + header[26] = (byte) ((longSampleRate >> 16) & 0xff); + header[27] = (byte) ((longSampleRate >> 24) & 0xff); + + header[28] = (byte) (samplesBits & 0xff); + header[29] = (byte) ((samplesBits >> 8) & 0xff); + header[30] = (byte) ((samplesBits >> 16) & 0xff); + header[31] = (byte) ((samplesBits >> 24) & 0xff); + + //2字节数据块长度(每个样本的字节数=通道数*每次采样得到的样本位数/8) + if (samplesBits==AudioFormat.ENCODING_PCM_16BIT) { + header[32] = (byte) (channels * samplesBits); + header[33] = 0; + header[34] = 16; // 每个采样点的位数 + header[35] = 0; + }else if (samplesBits==AudioFormat.ENCODING_PCM_8BIT){ + header[32] = (byte) (channels ); + header[33] = 0; + header[34] = 8; // 每个采样点的位数 + header[35] = 0; + }else { + header[32] = (byte) (channels * samplesBits / 8); + header[33] = 0; + header[34] = (byte) samplesBits; //每个采样点的位数 + header[35] = 0; + } + + + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + //pcm音频数据大小 + header[40] = (byte) (totalAudioLen & 0xff); + header[41] = (byte) ((totalAudioLen >> 8) & 0xff); + header[42] = (byte) ((totalAudioLen >> 16) & 0xff); + header[43] = (byte) ((totalAudioLen >> 24) & 0xff); + return header; + } + + public void writeHeader(DataOutputStream dos) { + + try { + dos.write(makeWaveHeader()); + } catch (IOException e) { + Log.e(TAG, String.format("创建wav文件头(WriteWavHeader)错误!%s", e.getMessage())); + } + + } + public void modifyHeader(String fileName) { + + try { + RandomAccessFile raf=new RandomAccessFile(fileName,"rw"); + raf.seek(0); + raf.write(makeWaveHeader()); + raf.close(); + } catch (IOException e) { + Log.e(TAG, String.format("修改wav文件头(modifyHeader)错误!%s", e.getMessage())); + } + + } + +}