Merge pull request #74 from N0BOY/Dev

v0.91 release
pull/83/head v0.91
NØBOY 2023-09-11 22:47:35 -07:00 zatwierdzone przez GitHub
commit 89500cb162
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
81 zmienionych plików z 4682 dodań i 930 usunięć

Wyświetl plik

@ -25,39 +25,42 @@ BG7YOZ
Steve Franke(K9AN)、Bill Somerville(G4WJS)、Joe Taylor(K1JT)提出FT8和FT4协议FT是Franke和Taylor的首字母并在论文《The FT4 and FT8 Communication Protocols》详细介绍了FT4和FT8的设计初衷和在WSJT-X中的具体实现细节成为完成本APP的根本指南。
Karlis Goba(YL3JG)在代码的具体实现上提供了参考。
鸣谢:
BG7YOY在FT8CN开发阶段为我在无线电基本理论上作出指导并为FT8CN设计了图标。
BG4IGX在我刚刚入门业余无线电时为我在具体实践上作出指导。抖音上您可以搜到很多他的教学视频。
BD7MXN帮助我对部分电台的连接控制做了一些测试并提出改进建议。
BH2RSJ帮助我建立了一个FT8CN测试群为测试和后续改进提出了很多宝贵意见。
BH7ACO帮助解决了某电台的驱动和相关的配置参数。
BG7IKK帮助解决了只支持通过RTS控制PTT发射的电台的测试。
BI1NIZ帮助注册账号用于收集问题反馈和FAQ的功能。
BD3OOX以及石家庄业余无线电俱乐部FT8CN的呼号地区归属数据提取至JTDX石家庄版使呼号定位可以精确到中国的省级。
VR2UPU(BD7MJO)在FT8的开发和使用经验上提供指导并在多语言方面给予帮助。
BA2BI在业余无线电的基础知识和通联的日志处理方面上给予帮助和指导。
BI3QXJ在对某品牌系列电台的指令集上给予专业性的指导。
BG6TQD在对某型号电台的指令集测试上给予帮助。
BG5CSS提供某型号电台用于测试。
BG7YXN提供某型号电台用于测试。
BG7YRB对呼号规则运算提供帮助。
BG8KAH提供设备用于测试。
BA7LVG、JE6WUD完成日文的翻译校对工作。
BG6RI帮助解决日志的信号报告问题。
SV1EEX完成希腊文、西班牙文UI的翻译工作。
VR2VRC帮助修正历史呼号读取规则。
BA7NQ提供设备用于测试。
BD7MYM对某型号的电台测试给予指导。
NØBOY帮助提供Github源以及翻译工作。
BG5JNT帮助修正非标准呼号的识别问题。
BH3NEK协助对某型号电台进行测试。
BG2ALB协助对某型号电台进行测试。
BG6DRU协助对某型号电台进行测试。
BG7NQF提供某型号电台的隐藏指令对一些设备做兼容性测试。
BH2VSQ协助对某型号电台进行测试。
BG7YBW协助对部分功能惊醒测试。
BH1RNN协助对部分功能进行测试。
BG7BSM协助对一些BUG进行调试。
BH4FTI发现并协助对一些BUG进行调试。
BG8BXMM哥为FT8CN的使用做推广抖音和B站上有很多他的教学视频。
BG7MFQ为FT8CN的使用做推广帮助测试。
BG7YOY在FT8CN开发阶段为我在无线电基本理论上作出指导并为FT8CN设计了图标
BG4IGX在我刚刚入门业余无线电时为我在具体实践上作出指导。抖音上您可以搜到很多他的教学视频
BD7MXN帮助我对部分电台的连接控制做了一些测试并提出改进建议
BH2RSJ帮助我建立了一个FT8CN测试群为测试和后续改进提出了很多宝贵意见
BH7ACO帮助解决了某电台的驱动和相关的配置参数
BG7IKK帮助解决了只支持通过RTS控制PTT发射的电台的测试
BI1NIZ帮助注册账号用于收集问题反馈和FAQ的功能
BD3OOX以及石家庄业余无线电俱乐部FT8CN的呼号地区归属数据提取至JTDX石家庄版使呼号定位可以精确到中国的省级
VR2UPU(BD7MJO)在FT8的开发和使用经验上提供指导并在多语言方面给予帮助
BA2BI在业余无线电的基础知识和通联的日志处理方面上给予帮助和指导
BI3QXJ在对某品牌系列电台的指令集上给予专业性的指导
BG6TQD在对某型号电台的指令集测试上给予帮助
BG5CSS提供某型号电台用于测试
BG7YXN提供某型号电台用于测试
BG7YRB对呼号规则运算提供帮助
BG8KAH提供设备用于测试
BA7LVG、JE6WUD完成日文的翻译校对工作
BG6RI帮助解决日志的信号报告问题
SV1EEX完成希腊文、西班牙文UI的翻译工作
VR2VRC帮助修正历史呼号读取规则
BA7NQ提供设备用于测试
BD7MYM对某型号的电台测试给予指导
NØBOY帮助提供Github源以及翻译工作
BG5JNT帮助修正非标准呼号的识别问题
BH3NEK协助对某型号电台进行测试
BG2ALB协助对某型号电台进行测试
BG6DRU协助对某型号电台进行测试
BG7NQF提供某型号电台的隐藏指令对一些设备做兼容性测试
BH2VSQ协助对某型号电台进行测试
BG7YBW协助对部分功能进行测试
BH1RNN协助对部分功能进行测试
BG7BSM协助对一些BUG进行调试
BH4FTI发现并协助对一些BUG进行调试
BG8BXMM哥为FT8CN的使用做推广抖音和B站上有很多他的教学视频
BG7MFQ为FT8CN的使用做推广帮助测试
BG2EFX提供大数据量的日志用于测试
DS1UFX贡献(tr)uSDX audio over CAT代码
BG8HT提供某型号电台进行测试
```

Wyświetl plik

@ -22,9 +22,9 @@ android {
minSdk 23
targetSdk 33
versionCode 1
versionName '0.9'
versionName '0.91'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
//testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
dataBinding{
enabled true
}
@ -48,11 +48,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "apkBuildTime", "\"${currentTime}\"")
// true -
//minifyEnabled true
// true -
//shrinkResources true
}
}
compileOptions {
@ -65,27 +60,36 @@ android {
// version '3.18.1'
// }
// }
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
namespace 'com.bg7yoz.ft8cn'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.navigation:navigation-fragment:2.4.1'
implementation 'androidx.navigation:navigation-ui:2.4.1'
implementation 'com.google.android.gms:play-services-maps:18.0.2'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'commons-net:commons-net:3.6'//
implementation 'com.google.guava:guava:31.1-jre'//HashTablekeyHashMap
//testImplementation 'junit:junit:4.13.2'
//androidTestImplementation 'androidx.test.ext:junit:1.1.3'
//androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation files('src/libs/MPAndroidChartv_3.1.0.jar')
implementation files('src/libs/commons-net-3.6.jar')////
implementation files('src/libs/nanohttpd-2.2.0.jar')
implementation files('src/libs/osmdroid-android-6.1.14.aar')//
//implementation files('src/libs/resample-release.aar')//DS1UFX (tr)uSDX
}

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,3 @@
消息模式,是指显示消息列表的方式。
标准列表模式就是FT8CN传统的FT8消息显示模式包含FT8消息的要素外还包括双方呼号归属地、距离、未通联的分区标志、消息类型等。
精简列表模式就是简化的FT8消息显示模式只包含必要的FT8消息。

Wyświetl plik

@ -0,0 +1,5 @@
"Message mode" refers to how messages are displayed.
Standard list mode is the default FT8 display mode. In addition to FT8 messages, it includes callsign, location, distance, worked zone indicator, message type, etc.
Simple list mode is a simplified FT8 message display mode that contains only essential FT8 messages.

Wyświetl plik

@ -14,7 +14,20 @@ Please click "FAQ" if you have good suggestions or questions .
BG7YOZ
2022-07-01
2023-08-07(0.90)
2023-09-10(0.91)
1.修正发射非标准消息i3=4RR73被误发成73的问题。
2.修正因多重解码模式下无法及时回复上一周期解码的消息以及漏发73的问题。
3.修正生成双方都是复合呼号的消息时,发送方的呼号可能不正确的问题。
4.优化自动程序。
5.增加tr)uSDX Audio over cat功能由DS1UFX贡献代码。
6.增加支持XieGu(协谷)X6100的Wifi模式固件版本 1.1.7,发射音频还未解决)。
7.增加支持Kenwood TS-570D。
8.增加Yaesu FT-891/991 USB-DATA模式。
9.增加对消息列表上的呼号QSO日志一键查询。
10.增加消息列表精简显示模式。
2023-08-13(0.90)
1.增加日志导入时Web界面交互模式。
2.修正当日志数据量过大时,地图崩溃的问题。
3.优化数据库结构,提升日志数据导入、更新速度(更新此版本前,建议备份日志以防不测)。
@ -286,11 +299,13 @@ BG7YOZ
BG6DRU协助对某型号电台进行测试。
BG7NQF提供某型号电台的隐藏指令对一些设备做兼容性测试。
BH2VSQ协助对某型号电台进行测试。
BG7YBW协助对部分功能惊醒测试。
BG7YBW协助对部分功能进行测试。
BH1RNN协助对部分功能进行测试。
BG7BSM协助对一些BUG进行调试。
BH4FTI发现并协助对一些BUG进行调试。
BG8BXMM哥为FT8CN的使用做推广抖音和B站上有很多他的教学视频。
BG7MFQ为FT8CN的使用做推广帮助测试。
BG2EFX提供大数据量的日志用于测试。
DS1UFX贡献(tr)uSDX audio over cat代码。
BG8HT提供某型号电台进行测试。

Wyświetl plik

@ -21,8 +21,9 @@ ICOM IC-9700,A2,115200,0
ICOM IC-R8600,96,115200,0
ICOM ID-52A,A6,115200,0
ICOM IC-706MKIIG,58,19200,0
XIEGU(协谷) X6100/G90S(U-DIG),70,19200,13
XIEGU(协谷) X6100/G90S(USB),70,19200,9
XIEGU(协谷) X6100(U-DIG),A4,19200,13
XIEGU(协谷) G90S(U-DIG),70,19200,13
XIEGU(协谷) G90S(USB),70,19200,9
XIEGU(协谷) X5105,70,19200,9
XIEGU(协谷) X108,70,19200,9
GUOHE(国赫) Q900,00,19200,8
@ -31,16 +32,17 @@ YAESU FT-450(D),00,4800,4
YAESU FT-817,00,4800,1
YAESU FT-818,00,4800,1
YAESU FT-857,00,4800,1
YAESU FT-891,00,4800,2
YAESU FT-891/991(USB),00,4800,2
YAESU FT-891/991(DATA-USB),00,4800,19
YAESU FT-897(D),00,4800,1
YAESU FT-950,00,4800,3
YAESU FT-2000(D),00,4800,3
YAESU FT-991(A),00,4800,2
YAESU FT-DX10,00,38400,6
YAESU FT-DX101,00,38400,6
YAESU FT-DX Other series,00,4800,3
KENWOOD(建伍) TK-90,00,9600,5
KENWOOD(建伍) TS-480,00,9600,7
KENWOOD(建伍) TS-570,00,4800,18
KENWOOD(建伍) TS-590,00,9600,7
KENWOOD(建伍) TS-2000,00,9600,14
KN990,00,38400,1
@ -50,4 +52,5 @@ FlexRadio 6000 series,00,00,12
FX-4CR,00,115200,7
Qrp Labs QDX,00,9600,7
UA3REO Wolf SDR(DIGU),00,4800,15
UA3REO Wolf SDR(USB),00,4800,16
UA3REO Wolf SDR(USB),00,4800,16
(tr)uSDX,00,115200,17

Wyświetl plik

@ -160,6 +160,9 @@ public class GeneralVariables {
public static String myCallsign = "";//我的呼号
public static String toModifier = "";//呼叫的修饰符
private static float baseFrequency = 1000;//声音频率
public static boolean simpleCallItemMode=false;//紧凑型消息
public static MutableLiveData<Float> mutableBaseFrequency = new MutableLiveData<>();
public static boolean synFrequency = false;//同频发射

Wyświetl plik

@ -15,8 +15,10 @@ package com.bg7yoz.ft8cn;
* 30014.77
* <p>
*
* 2023-08-16 DS1UFX0.9(tr)uSDX audio over cat
*
* @author BG7YOZ
* @date 2022.5.6
* @date 2022.8.22
*/
import static com.bg7yoz.ft8cn.GeneralVariables.getStringFromResource;
@ -55,7 +57,7 @@ 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.icom.WifiRig;
import com.bg7yoz.ft8cn.log.QSLCallsignRecord;
import com.bg7yoz.ft8cn.log.QSLRecord;
import com.bg7yoz.ft8cn.log.SWLQsoList;
@ -69,8 +71,10 @@ 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.KenwoodTS570Rig;
import com.bg7yoz.ft8cn.rigs.KenwoodTS590Rig;
import com.bg7yoz.ft8cn.rigs.OnRigStateChanged;
import com.bg7yoz.ft8cn.rigs.TrUSDXRig;
import com.bg7yoz.ft8cn.rigs.Wolf_sdr_450Rig;
import com.bg7yoz.ft8cn.rigs.XieGu6100Rig;
import com.bg7yoz.ft8cn.rigs.XieGuRig;
@ -106,7 +110,6 @@ public class MainViewModel extends ViewModel {
public final ArrayList<Ft8Message> ft8Messages = new ArrayList<>();//消息列表
public UtcTimer utcTimer;//同步触发动作的计时器。
//public boolean showTrackerInfo=true;
//public CallsignDatabase callsignDatabase = null;//呼号信息的数据库
public DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库
@ -296,6 +299,8 @@ public class MainViewModel extends ViewModel {
//检查发射程序。从消息列表中解析发射的程序
//超出周期2秒钟就不应该解析了
if (!ft8TransmitSignal.isTransmitting()
&& !isDeep//屏蔽掉深度解码激活自动程序
//深度解码的列表应该加到没有深度解码的新消息列表中
&& (ft8SignalListener.timeSec
+ GeneralVariables.pttDelay
+ GeneralVariables.transmitDelay <= 2000)) {//考虑网络模式发射时长是13秒
@ -354,13 +359,24 @@ public class MainViewModel extends ViewModel {
//创建发射对象回调发射前发射后、QSL成功后。
ft8TransmitSignal = new FT8TransmitSignal(databaseOpr, new OnDoTransmitted() {
private boolean needControlSco() {//根据控制模式确定是不是需要开启SCO
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
return false;
}
if (GeneralVariables.controlMode != ControlMode.CAT) {
return true;
}
return baseRig != null && !baseRig.supportWaveOverCAT();
}
@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();
//if (GeneralVariables.connectMode != ConnectMode.NETWORK) stopSco();
if (needControlSco()) stopSco();
baseRig.setPTT(true);
}
}
@ -378,7 +394,8 @@ public class MainViewModel extends ViewModel {
|| GeneralVariables.controlMode == ControlMode.DTR) {
if (baseRig != null) {
baseRig.setPTT(false);
if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco();
//if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco();
if (needControlSco()) startSco();
}
}
}
@ -396,6 +413,32 @@ public class MainViewModel extends ViewModel {
}
}
}
//2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
@Override
public boolean supportTransmitOverCAT() {
if (GeneralVariables.controlMode != ControlMode.CAT) {
return false;
}
if (baseRig == null) {
return false;
}
if (!baseRig.isConnected() || !baseRig.supportWaveOverCAT()) {
return false;
}
return true;
}
@Override
public void onTransmitOverCAT(Ft8Message msg) {//通过CAT发送音频消息
if (!supportTransmitOverCAT()) {
return;
}
sendWaveDataRunnable.baseRig = baseRig;
sendWaveDataRunnable.message = msg;
sendWaveDataThreadPool.execute(sendWaveDataRunnable);
}
}, new OnTransmitSuccess() {//当通联成功时
@Override
public void doAfterTransmit(QSLRecord qslRecord) {
@ -599,7 +642,18 @@ public class MainViewModel extends ViewModel {
}
baseRig.setControlMode(GeneralVariables.controlMode);
CableConnector connector = new CableConnector(context, port, GeneralVariables.baudRate
, GeneralVariables.controlMode);
//, GeneralVariables.controlMode);
, GeneralVariables.controlMode,baseRig);
//2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
connector.setOnCableDataReceived(new CableConnector.OnCableDataReceived() {
@Override
public void OnWaveReceived(int bufferLen, float[] buffer) {
Log.i(TAG, "call hamRecorder.doOnWaveDataReceived");
hamRecorder.doOnWaveDataReceived(bufferLen, buffer);
}
});
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(connector);
connector.connect();
@ -634,7 +688,11 @@ public class MainViewModel extends ViewModel {
}, 5000);
}
public void connectIComWifiRig(Context context, IComWifiRig iComWifiRig) {
/**
* ICOMX6100
* @param wifiRig ICom,XieGu Wifi
*/
public void connectWifiRig(WifiRig wifiRig) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
if (baseRig != null) {
if (baseRig.getConnector() != null) {
@ -644,8 +702,9 @@ public class MainViewModel extends ViewModel {
}
GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式
//目前Icom与协谷x6100共用同一种连接器
IComWifiConnector iComWifiConnector = new IComWifiConnector(GeneralVariables.controlMode
, iComWifiRig);
,wifiRig);
iComWifiConnector.setOnWifiDataReceived(new IComWifiConnector.OnWifiDataReceived() {
@Override
public void OnWaveReceived(int bufferLen, float[] buffer) {
@ -657,13 +716,14 @@ public class MainViewModel extends ViewModel {
}
});
iComWifiConnector.connect();
connectRig();
connectRig();//给baseRig赋值
baseRig.setControlMode(GeneralVariables.controlMode);
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(iComWifiConnector);
//
new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的等2秒再设置频率
@Override
public void run() {
@ -713,13 +773,7 @@ public class MainViewModel extends ViewModel {
*
*/
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) {
@ -730,7 +784,10 @@ public class MainViewModel extends ViewModel {
baseRig = new Yaesu2Rig();
break;
case InstructionSet.YAESU_3_9:
baseRig = new Yaesu39Rig();//yaesu3代指令9位频率
baseRig = new Yaesu39Rig(false);//yaesu3代指令9位频率,usb模式
break;
case InstructionSet.YAESU_3_9_U_DIG:
baseRig = new Yaesu39Rig(true);//yaesu3代指令9位频率,data-usb模式
break;
case InstructionSet.YAESU_3_8:
baseRig = new Yaesu38Rig();//yaesu3代指令8位频率
@ -774,6 +831,27 @@ public class MainViewModel extends ViewModel {
case InstructionSet.WOLF_SDR_USB:
baseRig = new Wolf_sdr_450Rig(true);
break;
case InstructionSet.TRUSDX:
baseRig = new TrUSDXRig();//(tr)uSDX
break;
case InstructionSet.KENWOOD_TS570:
baseRig = new KenwoodTS570Rig();//KENWOOD TS-570D
break;
}
if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK)
|| ((GeneralVariables.instructionSet == InstructionSet.ICOM
||GeneralVariables.instructionSet==InstructionSet.XIEGU_6100)
&& GeneralVariables.connectMode == ConnectMode.NETWORK)) {
hamRecorder.setDataFromLan();
} else {
//hamRecorder.setDataFromMic();
if (GeneralVariables.controlMode != ControlMode.CAT || baseRig == null
|| !baseRig.supportWaveOverCAT()) {
hamRecorder.setDataFromMic();
} else {
hamRecorder.setDataFromLan();
}
}
mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK);

Wyświetl plik

@ -84,9 +84,12 @@ public class CallsignDatabase extends SQLiteOpenHelper {
"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)");
db.execSQL("CREATE INDEX countries_id_IDX_MORE ON countries (id,CountryNameEn,CountryNameCN,CQZone,ITUZone,DXCC)");
db.execSQL("CREATE INDEX callsigns_countryId_IDX ON callsigns (countryId)");
} catch (Exception e) {
Log.e(TAG, e.getMessage());

Wyświetl plik

@ -15,7 +15,7 @@ public class BaseRigConnector {
private OnConnectReceiveData onConnectReceiveData;//当接收到数据后的动作
private int controlMode;//控制模式
private OnRigStateChanged onRigStateChanged;
private OnConnectorStateChanged onConnectorStateChanged=new OnConnectorStateChanged() {
private final OnConnectorStateChanged onConnectorStateChanged=new OnConnectorStateChanged() {
@Override
public void onDisconnected() {
if (onRigStateChanged!=null){
@ -74,10 +74,42 @@ public class BaseRigConnector {
onConnectReceiveData=receiveData;
}
/**
* 2023-08-16 DS1UFX0.9(tr)uSDX audio over cat
* 16int32float
* @param data byte16int
*/
public void sendWaveData(byte[] data){
float[] waveFloat=new float[data.length/2];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=readShortBigEndianData(data,i*2)/32768.0f;
}
sendWaveData(waveFloat);
}
public void sendWaveData(float[] data){
//留给网络方式发送音频流
}
//2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
public void receiveWaveData(byte[] data){
float[] waveFloat=new float[data.length/2];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=readShortBigEndianData(data,i*2)/32768.0f;
}
receiveWaveData(waveFloat);
}
public void receiveWaveData(short[] data){
float[] waveFloat=new float[data.length];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=data[i]/32768.0f;
}
receiveWaveData(waveFloat);
}
public void receiveWaveData(float[] data){
}
public OnConnectReceiveData getOnConnectReceiveData() {
return onConnectReceiveData;
}
@ -100,4 +132,17 @@ public class BaseRigConnector {
public boolean isConnected(){
return connected;
}
/**
* 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);
}
}

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.rigs.BaseRig;
import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
/**
@ -15,12 +16,21 @@ import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
public class CableConnector extends BaseRigConnector {
private static final String TAG="CableConnector";
//2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
public interface OnCableDataReceived {
void OnWaveReceived(int bufferLen,float[] buffer);
}
private final CableSerialPort cableSerialPort;
private final BaseRig cableConnectedRig;
private OnCableDataReceived onCableDataReceived;
public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate
, int controlMode) {
//, int controlMode) {
, int controlMode, BaseRig cableConnectedRig) {
super(controlMode);
this.cableConnectedRig = cableConnectedRig;
cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged());
cableSerialPort.ioListener=new SerialInputOutputManager.Listener() {
@Override
@ -61,6 +71,36 @@ public class CableConnector extends BaseRigConnector {
cableSerialPort.sendData(command);//以CAT指令发送PTT
}
//以下是truSDX与wave有关的代码是2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
@Override
public void sendWaveData(byte[] data) {
sendData(data);
}
// @Override
// public void sendWaveData(float[] data) {
// // TODO float to byte
// byte[] wave = new byte[data.length * 4];
//
// sendWaveData(wave);
// }
@Override
public void receiveWaveData(float[] data){
Log.i(TAG, "received wave data");
if (onCableDataReceived!=null){
Log.i(TAG, "call onCableDataReceived.OnWaveReceived");
onCableDataReceived.OnWaveReceived(data.length,data);
}
}
public void setOnCableDataReceived(OnCableDataReceived onCableDataReceived) {
this.onCableDataReceived = onCableDataReceived;
}
@Override
public void connect() {
super.connect();
@ -69,6 +109,7 @@ public class CableConnector extends BaseRigConnector {
@Override
public void disconnect() {
cableConnectedRig.onDisconnecting();
super.disconnect();
cableSerialPort.disconnect();
}

Wyświetl plik

@ -118,7 +118,7 @@ public class CableSerialPort {
Log.e(TAG, "串口号不存在,无法打开。");
return false;
}
Log.e(TAG, "connect: port size:" + String.valueOf(driver.getPorts().size()));
Log.d(TAG, "connect: port size:" + String.valueOf(driver.getPorts().size()));
usbSerialPort = driver.getPorts().get(portNum);
usbConnection = usbManager.openDevice(driver.getDevice());

Wyświetl plik

@ -115,7 +115,7 @@ public class FlexConnector extends BaseRigConnector {
if (response.flexCommand== FlexCommand.METER_LIST){
//FlexMeters flexMeters=new FlexMeters(response.exContent);
flexMeterInfos.setMeterInfos(response.exContent);
flexRadio.commandSubMeterAll();//显示全部仪表消息
flexRadio.commandSubMeterAll();//订阅全部仪表消息
//flexMeters.getAllMeters();
//Log.e(TAG, "onResponse: ----->>>"+flexMeters.getAllMeters() );
}
@ -163,7 +163,7 @@ public class FlexConnector extends BaseRigConnector {
flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX
//todo 防止流的端口没有释放,把端口变换一下?
//FlexRadio.streamPort++;
@ -172,6 +172,9 @@ public class FlexConnector extends BaseRigConnector {
flexRadio.commandStreamCreateDaxRx(1);//创建流数据到DAX通道1
flexRadio.commandStreamCreateDaxTx(1);//创建流数据到DAX通道1
flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX
//TODO 是否设置??? dax tx T 或者 dax tx 1
flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f));
flexRadio.commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式
@ -237,6 +240,7 @@ public class FlexConnector extends BaseRigConnector {
}
public void subAllMeters(){
if (flexMeterInfos.size()==0) {
//todo commandMeterList()是否可以不使用?
flexRadio.commandMeterList();//列一下仪表
flexRadio.commandSubMeterAll();//显示全部仪表消息
}

Wyświetl plik

@ -4,27 +4,19 @@ package com.bg7yoz.ft8cn.connector;
* IComIntFloat
*
* @author BGY70Z
* @date 2023-03-20
* @date 2023-08-19
*/
import com.bg7yoz.ft8cn.icom.IComWifiRig;
public class IComWifiConnector extends BaseRigConnector{
import com.bg7yoz.ft8cn.icom.WifiRig;
public class IComWifiConnector extends WifiConnector{
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,WifiRig wifiRig) {
super(controlMode,wifiRig);
public IComWifiConnector(int controlMode,IComWifiRig iComWifiRig) {
super(controlMode);
this.iComWifiRig=iComWifiRig;
this.iComWifiRig.setOnIComDataEvents(new IComWifiRig.OnIComDataEvents() {
this.wifiRig.setOnDataEvents(new WifiRig.OnDataEvents() {
@Override
public void onReceivedCivData(byte[] data) {
if (getOnConnectReceiveData()!=null){
@ -48,65 +40,5 @@ public class IComWifiConnector extends BaseRigConnector{
});
}
@Override
public void sendWaveData(float[] data) {
if (iComWifiRig.opened) {
iComWifiRig.sendWaveData(data);
}
}
@Override
public void connect() {
super.connect();
iComWifiRig.start();
}
@Override
public void disconnect() {
super.disconnect();
iComWifiRig.close();
}
@Override
public void sendData(byte[] data) {
iComWifiRig.sendCivData(data);
}
@Override
public void setPttOn(byte[] command) {
iComWifiRig.sendCivData(command);
}
@Override
public void setPttOn(boolean on) {
if (iComWifiRig.opened){
iComWifiRig.setPttOn(on);
}
}
public OnWifiDataReceived getOnWifiDataReceived() {
return onWifiDataReceived;
}
@Override
public boolean isConnected() {
return iComWifiRig.opened;
}
public void setOnWifiDataReceived(OnWifiDataReceived onDataReceived) {
this.onWifiDataReceived = onDataReceived;
}
/**
* 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);
}
}

Wyświetl plik

@ -0,0 +1,94 @@
package com.bg7yoz.ft8cn.connector;
/**
*
* IComIntFloat
*
* @author BGY70Z
* @date 2023-08-19
*/
import android.util.Log;
import com.bg7yoz.ft8cn.icom.WifiRig;
public class WifiConnector extends BaseRigConnector{
private static final String TAG = "WifiConnector";
public interface OnWifiDataReceived{
void OnWaveReceived(int bufferLen,float[] buffer);
void OnCivReceived(byte[] data);
}
public WifiRig wifiRig;
public OnWifiDataReceived onWifiDataReceived;
public WifiConnector(int controlMode, WifiRig wifiRig) {
super(controlMode);
this.wifiRig=wifiRig;
}
@Override
public void sendWaveData(float[] data) {
if (wifiRig.opened) {
wifiRig.sendWaveData(data);
}
}
@Override
public void connect() {
super.connect();
wifiRig.start();
}
@Override
public void disconnect() {
super.disconnect();
wifiRig.close();
}
@Override
public void sendData(byte[] data) {
wifiRig.sendCivData(data);
}
@Override
public void setPttOn(byte[] command) {
wifiRig.sendCivData(command);
}
@Override
public void setPttOn(boolean on) {
if (wifiRig.opened){
wifiRig.setPttOn(on);
}
}
public OnWifiDataReceived getOnWifiDataReceived() {
return onWifiDataReceived;
}
@Override
public boolean isConnected() {
return wifiRig.opened;
}
public void setOnWifiDataReceived(OnWifiDataReceived onDataReceived) {
this.onWifiDataReceived = onDataReceived;
}
/**
* 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);
}
}

Wyświetl plik

@ -1830,6 +1830,11 @@ public class DatabaseOpr extends SQLiteOpenHelper {
GeneralVariables.band = result.equals("") ? 14074000 : Long.parseLong(result);
GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band);
}
if (name.equalsIgnoreCase("msgMode")) {
GeneralVariables.simpleCallItemMode = result.equals("1") ;
}
if (name.equalsIgnoreCase("ctrMode")) {
GeneralVariables.controlMode = result.equals("") ? ControlMode.VOX : Integer.parseInt(result);
}

Wyświetl plik

@ -15,6 +15,7 @@ import androidx.annotation.NonNull;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.wave.FT8Resample;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
@ -267,7 +268,7 @@ public class FlexRadio {
onReceiveStreamData.onReceiveAudio(data);
}
if (audioTrack != null) {//如果音频播放已经打开,就写音频流数据
float[] sound = getFloatFromBytes(data);
float[] sound = getFloatFromBytes(data);//长度是256个float
audioTrack.write(sound, 0, sound.length, AudioTrack.WRITE_NON_BLOCKING);
}
}
@ -370,6 +371,7 @@ public class FlexRadio {
public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) {
if (flexStreamPort != packet.getPort()) flexStreamPort = packet.getPort();
VITA vita = new VITA(data);
addStreamIdToSet(vita.streamId);
@ -439,17 +441,22 @@ public class FlexRadio {
}
/**
* flexRadio1200024000
* flexRadio24000
* @param data
*/
public void sendWaveData(float[] data) {
float[] temp = new float[data.length * 2];
for (int i = 0; i < data.length; i++) {//转成立体声,24000采样率
//转成立体声,24000采样率
for (int i = 0; i < data.length; i++) {
temp[i * 2] = data[i];
temp[i * 2 + 1] = data[i];
}
//port=4991;
//streamTxId=0x084000001;
// class id=0x00 00 1c 2d 53 4c 01 23????
//每5毫秒一个包立体声共256个float
Log.e(TAG, String.format("sendWaveData: streamid:0x%x,ip:%s,port:%d",streamTxId,ip, port) );
new Thread(new Runnable() {
@ -475,10 +482,14 @@ public class FlexRadio {
if (count > temp.length) break;
}
byte[] send = vita.audioDataToVita(packetCount, streamTxId, voice);
//byte[] send = vita.audioDataToVita(packetCount, streamTxId,0x534c2d, voice);
//daxTxAudioStreamId=0x0084000001&0x00000000ffffffff;
//Log.e(TAG, String.format("run: daxTxAudioStreamId:0x%X",daxTxAudioStreamId) );
byte[] send = vita.audioDataToVita(packetCount, daxTxAudioStreamId,0x534c0123, voice);
packetCount++;
try {
streamClient.sendData(send, ip, port);
//Log.e(TAG, String.format("run: send ip:%s, port:%d",ip,4993) );
streamClient.sendData(send, ip, 4993);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
@ -800,6 +811,8 @@ public class FlexRadio {
@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"));
//sendCommand(FlexCommand.DAX_AUDIO, String.format("dax audio set %d tx=%s", channel, txEnable ? "1" : "0"));
//sendCommand(FlexCommand.DAX_AUDIO, "dax tx 1");
}
@SuppressLint("DefaultLocale")
@ -819,9 +832,9 @@ public class FlexRadio {
@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 dax_channel=%d compression=none", 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"));
//sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=remote_audio_tx"));
}
public synchronized void commandRemoveDaxStream() {

Wyświetl plik

@ -136,7 +136,7 @@ public class RadioUdpClient {
byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength());
client.onUdpEvents.OnReceiveData(client.sendSocket, packet, temp);
}
//Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName());
//Log.d(TAG, "receiveData:host port: " + packet.getPort());
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "receiveData: error:" + e.getMessage());

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.flex;
/**
* VITA49
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -126,32 +127,34 @@ public class VITA {
/**
* VITAidcreate stream
*
* @param id streamId
* @param stream_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;
public byte[] audioDataToVita(int count, long stream_id, int class_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---查一下这个数字是不是变化
//tsi = VitaTSI.TSI_NONE;//
tsi = VitaTSI.TSI_OTHER;//
tsf = VitaTSF.TSF_SAMPLE_COUNT;//--TODO---查一下这个数字是不是变化
//tsf = VitaTSF.TSF_NONE;//--TODO---查一下这个数字是不是变化
//packetCount动态变化
//packetCount=?应该是这个全部音频流的总包数
packetCount=count % 16;//模16
//packetSize是以word32位4字节为单位
//packetSize值为263居多估计以音频还有其它的长度,263是包含7个word28字节的头长度。
packetSize = (data.length ) + 7;//7个word是VITA的包头
packetSize = (data.length) + 7;//7个word是VITA的包头
//----以上是Header,32位第一个word-------
streamId = id;//第二个word,此id是电台赋给的。经常是0x40000xx。
streamId = stream_id;//第二个word,此id是电台赋给的。经常是0x40000xx。
oui = 0x00001c2d;//第三个word,FlexRadio Systems OUI
classId = 0x534c0123;//第四个word64位
//classId = 0x534c0123;//第四个word64位
classId = class_id;
//classId = 0x534c03e3;//第四个word64位
//integerTimestamp =0;// System.currentTimeMillis() / 1000;//第五个word,时间戳的整数部分,以秒为单位。应该是取当前时间
@ -171,11 +174,11 @@ public class VITA {
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) 0x0000;
result[1] |= (byte) (packetCount & 0xf);//packet count
result[1] = (byte) (tsi.ordinal() << 6);//TSI
result[1] |= (byte) (tsf.ordinal() << 4);//TSF
result[1] |= (byte) (packetCount & 0xff);//packetCount
//result[1] |= (byte) (packetCount & 0xff);//packetCount
//packetSize默认263words
result[2] = (byte) ((packetSize >> 8) & 0xff);//packetSize 1高8位
@ -183,10 +186,10 @@ public class VITA {
//-----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);
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
@ -196,10 +199,16 @@ public class VITA {
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;
result[12] = (byte) ((classId & 0x00ff000000 >> 24) & 0xff);
result[13] = (byte) (((classId & 0x00ff0000) >> 16) & 0xff);
result[14] = (byte) (((classId & 0x0000ff00) >> 8) & 0xff);
result[15] = (byte) (classId & 0x000000ff);
// result[12] = 0x53;
// result[13] = 0x4c;
// result[14] = (byte) 0x01;
// result[15] = (byte) 0x23;
//---Timestamp--No.5 word----
//integerTimestamp=0x01020304
@ -229,11 +238,11 @@ public class VITA {
// 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];
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];
}
/*

Wyświetl plik

@ -159,7 +159,6 @@ public class FT8SignalListener {
onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true);
}
do {
if (timeSec > FT8Common.DEEP_DECODE_TIMEOUT) break;//此处做超时检测,超过一定时间(7秒),就不做减码操作了
//减去解码的信号

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.ft8signal;
/**
* FT8
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -28,6 +29,11 @@ public class FT8Package {
}
/**
* i3=477
* @param message
* @return
*/
public static byte[] generatePack77_i4(Ft8Message message) {
String toCall = message.callsignTo.replace("<", "").replace(">", "");
@ -61,30 +67,55 @@ public class FT8Package {
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;
switch (message.extraInfo) {
case "RRR": //r2=1
data[8] = (byte) (data[8] & 0xfe);
data[9] = (byte) (data[9] | 0x80);
break;
case "RR73": //r2=2
data[8] = (byte) (data[8] | 0x01);
//data[9] = (byte) (data[9] | 0x00);//data[9]无需改变
break;
case "73": //r2=3
data[8] = (byte) (data[8] | 0x01);
data[9] = (byte) (data[9] | 0x80);
break;
}
}
return data;
}
/**
* /
*
* /FT8
* @param compoundCallsign
* @return
*/
public static String getStdCall(String compoundCallsign) {
if (!compoundCallsign.contains("/")) return compoundCallsign;
String[] callsigns = compoundCallsign.split("/");
for (String callsign : callsigns) {//用正则表达式提取标准呼号
//FT8的认定标准业余呼号由一个或两个字符的前缀组成其中至少一个必须是字母后跟一个十进制数字和最多三个字母的后缀。
if (callsign.matches("[A-Z0-9]?[A-Z0-9][0-9][A-Z][A-Z0-9]?[A-Z]?")) {
return callsign;
}
}
//当无法提取标准呼号时,取最长的字段
int len = 0;
int index = 0;
for (int i = 0; i < callsigns.length; i++) {
if (callsigns[i].length() > len) {
len = callsigns[i].length();
index = i;
}
}
return callsigns[index];
}
/**
* i1=1,i1=2FT8EU VHF
@ -92,17 +123,14 @@ public class FT8Package {
*
*
* @param message
* @return
* @return packet77
*/
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) {
if (message.checkIsCQ() && message.modifier != null) {//把修饰符加上
if (message.modifier.length() > 0) {
toCall = toCall + " " + message.modifier;
}
}
@ -118,7 +146,8 @@ public class FT8Package {
//当双方都是复合呼号或非标准呼号时(带/的呼号),我的呼号变成标准呼号
if ((toCall.contains("/")) && fromCall.contains("/")) {
fromCall = fromCall.substring(0, fromCall.indexOf("/"));
fromCall = getStdCall(fromCall);//从复合呼号中提取标准呼号
// fromCall = fromCall.substring(0, fromCall.indexOf("/"));
}
@ -221,11 +250,11 @@ public class FT8Package {
}
//判断是否有修饰符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 (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}")) {
@ -234,26 +263,26 @@ public class FT8Package {
int a2 = 0;
int a3 = 0;
if (temp.length() == 1) {//A-Z
a0= (int) temp.charAt(0) - 65;
return a0+1004;
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;
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;
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;
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;
}
}
}
@ -262,7 +291,7 @@ public class FT8Package {
//格式化成标准的呼号。6位、第3位带数字
//c6也可以是非标准呼号。大于6位的都是非标准呼号
String c6 = formatCallsign(callsign);
//if (c6.length()>6){//生成HASH22+2063592
//判断是不是标准呼号
if (!GenerateFT8.checkIsStandardCallsign(callsign)) {//生成HASH22+2063592
return NTOKENS + getHash22(callsign);
}
@ -288,50 +317,6 @@ public class FT8Package {
}
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;
}
/**
*

Wyświetl plik

@ -20,6 +20,7 @@ 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.database.DatabaseOpr;
import com.bg7yoz.ft8cn.log.QSLRecord;
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
@ -139,8 +140,6 @@ public class FT8TransmitSignal {
}
});
//utcTimer.setTime_sec(GeneralVariables.transmitDelay);//默认晚500毫秒发射确保上一时序解码结束
utcTimer.start();
}
@ -184,65 +183,7 @@ public class FT8TransmitSignal {
}
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);
}
@ -367,6 +308,7 @@ public class FT8TransmitSignal {
/**
* 321632
*
* @param buffer 32
* @return 16
*/
@ -383,7 +325,6 @@ public class FT8TransmitSignal {
return temp;
}
//private void playFT8Signal(float[] buffer) {
private void playFT8Signal(Ft8Message msg) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//网络方式就不播放音频了
@ -413,6 +354,35 @@ public class FT8TransmitSignal {
return;
}
//进入到CAT串口传输音频方式
//2023-08-16 由DS1UFX提交修改基于0.9版),用于(tr)uSDX audio over cat的支持。
if (GeneralVariables.controlMode == ControlMode.CAT) {
Log.d(TAG, "playFT8Signal: try to transmit over CAT");
if (onDoTransmitted != null) {//处理音频数据可以给truSDX的CAT模式发送
if (onDoTransmitted.supportTransmitOverCAT()) {
onDoTransmitted.onTransmitOverCAT(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: transmitting over CAT is finished.");
afterPlayAudio();
return;
}
}
}
//进入声卡模式
float[] buffer;
@ -598,12 +568,13 @@ public class FT8TransmitSignal {
}
/**
* from0
* from01
*
* @param messages
* @return 01>1
*/
private int checkTargetCallMe(ArrayList<Ft8Message> messages){
int fromCount=1;
private int checkTargetCallMe(ArrayList<Ft8Message> messages) {
int fromCount = 1;
for (int i = messages.size() - 1; i >= 0; i--) {
Ft8Message ft8Message = messages.get(i);
if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析
@ -614,12 +585,13 @@ public class FT8TransmitSignal {
&& checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) {
return 0;
}
if (checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)){
if (checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) {
fromCount++;//计数器from是目标呼号的情况
}
}
return fromCount;
}
/**
* ,-1
*
@ -673,6 +645,20 @@ public class FT8TransmitSignal {
}
}
/**
*
* 1.
* 2.
* 3.
*
* @param msg
* @return /
*/
private boolean isExcludeMessage(Ft8Message msg) {
return msg.getSequence() == sequential || msg.band != GeneralVariables.band
|| GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom);
}
/**
* CQCQ
*
@ -682,21 +668,32 @@ public class FT8TransmitSignal {
//@RequiresApi(api = Build.VERSION_CODES.N)
private boolean checkCQMeOrFollowCQMessage(ArrayList<Ft8Message> messages) {
//此message是刚刚解码出的消息
//检查CQ我且是我呼叫的目标
//第一个循环与第二个循环都是检查是否有CQ我的消息。第一个循环是优先查询是不是我的目标呼号。
// 目的是当多个目标呼叫我,我的回复不专一的问题。
//检查CQ我不是73且是我呼叫的目标
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 (isExcludeMessage(msg)) continue;//检查是不是属于排除的消息:
if (toCallsign == null) break;
if (msg.getCallsignTo().equals(GeneralVariables.myCallsign)
&& msg.getCallsignFrom().equals(toCallsign.callsign)//todo 注意测试复合呼号的情况
&& !GeneralVariables.checkFun5(msg.extraInfo)) {//cq我、不是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;
}
}
//检查CQ我不是73
for (int i = messages.size() - 1; i >= 0; i--) {//此处是检查有没有CQ我。TO:ME,且不能是73
Ft8Message msg = messages.get(i);
if (isExcludeMessage(msg)) continue;//检查是不是属于排除的消息:
if ((msg.getCallsignTo().equals(GeneralVariables.myCallsign)
&& !GeneralVariables.checkFun5(msg.extraInfo))) {//不能是73
&& !GeneralVariables.checkFun5(msg.extraInfo))) {//cq我、不是73
//设置发射之前,确定消息的序号,避免从头开始
setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz
, msg.getSequence(), msg.snr)
@ -724,12 +721,7 @@ public class FT8TransmitSignal {
//此处是检查关注的呼号在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;
}
if (isExcludeMessage(msg)) continue;//检查是不是属于排除的消息:
//处于CQ,FROM是我的关注呼号,并且不在通联成功的呼号列表中
if ((msg.checkIsCQ()//在CQ
@ -815,7 +807,7 @@ public class FT8TransmitSignal {
if (msgList.get(0).getSequence() == sequential) {
return;
}
ArrayList<Ft8Message> messages =new ArrayList<>(msgList);//防止线程冲突
ArrayList<Ft8Message> messages = new ArrayList<>(msgList);//防止线程冲突
int newOrder = checkFunctionOrdFromMessages(messages);//检查消息中对方回复的消息序号,-1为没有收到
@ -830,18 +822,22 @@ public class FT8TransmitSignal {
// 判断通联成功对方回735||我是735且对方没回-1
// 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制
// 或我是RR73(4),且对方开始呼叫别人了,解决RR73卡死的问题
if (newOrder == 5
if (newOrder == 5//消息中目标回复我RR73了
|| (functionOrder == 5 && newOrder == -1)// 判断通联成功对方回735||我是735且对方没回-1
|| (functionOrder == 4 &&
(GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit * 2)
&& (GeneralVariables.noReplyLimit > 0)) // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制
|| (functionOrder ==4 && checkTargetCallMe(messages)>1)
) { // 或我是RR73(4),且对方开始呼叫别人了
//doComplete();//做保存的动作
|| (functionOrder == 4 && checkTargetCallMe(messages) > 1)// 或我是RR73(4),且对方开始呼叫别人了(>1是目标呼号呼叫别人了
|| (functionOrder == 4 && (GeneralVariables.noReplyCount > 20)
&& (GeneralVariables.noReplyLimit == 0))//当呼叫无回应为“忽略”且我是RR73(4)那么无回应次数大于10次就复位防止RR73卡死
) {
//进入到CQ状态
resetToCQ();
//加入检查消息中有没有呼号我的或关注的呼号在CQ
//加入检查消息中有没有呼我的或关注的呼号在CQ
checkCQMeOrFollowCQMessage(messages);
setCurrentFunctionOrder(functionOrder);//设置当前消息
mutableFunctionOrder.postValue(functionOrder);
@ -1051,7 +1047,7 @@ public class FT8TransmitSignal {
@SuppressLint("DefaultLocale")
@Override
public void run() {
//此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来
//todo 此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来
if (transmitSignal.functionOrder == 1 || transmitSignal.functionOrder == 2) {//当消息处于1或2时说明开始了通联
transmitSignal.messageStartTime = UtcTimer.getSystemTime();
}

Wyświetl plik

@ -98,12 +98,10 @@ public class GenerateFT8 {
}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]?");
//FT8的认定标准业余呼号由一个或两个字符的前缀组成其中至少一个必须是字母后跟一个十进制数字和最多三个字母的后缀。
return temp.matches("[A-Z0-9]?[A-Z0-9][0-9][A-Z][A-Z0-9]?[A-Z]?");
//FT8的认定标准业余呼号由一个或两个字符的前缀组成其中至少一个必须是字母后跟一个十进制数字和最多三个字母的后缀。
}
/**
@ -147,7 +145,6 @@ public class GenerateFT8 {
}else {
msg.modifier="";
}
//msg.callsignTo="CQ AzCz";
//判定用非标准呼号i3=4的条件
//1.FROMCALL为非标准呼号 ,且 符合2或3

Wyświetl plik

@ -11,4 +11,8 @@ public interface OnDoTransmitted {
void onBeforeTransmit(Ft8Message message,int functionOder);
void onAfterTransmit(Ft8Message message, int functionOder);
void onTransmitByWifi(Ft8Message message);
//2023-08-16 由DS1UFX提交修改基于0.9版),增加(tr)uSDX audio over cat的支持。
boolean supportTransmitOverCAT();
void onTransmitOverCAT(Ft8Message message);
}

Wyświetl plik

@ -279,7 +279,7 @@ public class GridOsmMapView {
* @param grid
* @return
*/
public GridPolygon getGridPolygon(String grid) {
public synchronized GridPolygon getGridPolygon(String grid) {
synchronized (gridPolygons) {
for (GridPolygon polygon : gridPolygons) {
if (polygon.grid.equals(grid)) return polygon;
@ -832,7 +832,7 @@ public class GridOsmMapView {
/**
*
*/
public void hideInfoWindows() {
public synchronized void hideInfoWindows() {
for (GridPolygon polygon : gridPolygons
) {
polygon.closeInfoWindow();

Wyświetl plik

@ -80,6 +80,7 @@ public class GridTrackerMainActivity extends AppCompatActivity {
private boolean configBarIsClose = false;
private QSLRecordStr qlsRecorder = null;//用于历史显示消息
private MutableLiveData<ArrayList<QSLRecordStr>> qslRecordList = new MutableLiveData<>();
public static final int DRAW_LINE = 1;
@SuppressLint("NotifyDataSetChanged")
@ -337,6 +338,7 @@ public class GridTrackerMainActivity extends AppCompatActivity {
@Override
public void onChanged(ArrayList<QSLRecordStr> qslRecordStrs) {
for (QSLRecordStr record : qslRecordStrs) {
//todo 数据量过大可能会卡死OOM
drawMessage(record);//在地图上画每一个消息
}
gridOsmMapView.mapUpdate();
@ -375,6 +377,9 @@ public class GridTrackerMainActivity extends AppCompatActivity {
}
private GridOsmMapView.GridPolyLine drawMessage(QSLRecordStr recordStr) {
if (recordStr.getMy_gridsquare().equals("") || recordStr.getGridsquare().equals("")) {
return null;
}
gridOsmMapView.gridMapView.post(new Runnable() {
@Override
public void run() {

Wyświetl plik

@ -0,0 +1,29 @@
package com.bg7yoz.ft8cn.icom;
/**
*
* @author BGY70Z
* @date 2023-08-26
*/
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;
public class AudioUdp extends IcomUdpBase {
private static final String TAG = "AudioUdp";
public AudioUdp() {
udpStyle = IcomUdpStyle.AudioUdp;
}
public void sendTxAudioData(float[] audioData){}
public void startTxAudio(){}
public void stopTXAudio(){}
}

Wyświetl plik

@ -0,0 +1,244 @@
package com.bg7yoz.ft8cn.icom;
/**
*
*
* @author BGY70Z
* @date 2023-08-26
*/
import android.util.Log;
import java.net.DatagramPacket;
import java.util.Timer;
import java.util.TimerTask;
public class ControlUdp extends IcomUdpBase {
private static final String TAG = "ControlUdp";
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 AudioUdp audioUdp;
public ControlUdp(String userName, String password, String remoteIp, int remotePort) {
udpStyle = IcomUdpStyle.ControlUdp;
this.userName = userName;
this.password = password;
this.rigIp = remoteIp;
this.rigPort = remotePort;
}
@Override
public void onDataReceived(DatagramPacket packet, byte[] data) {
// 父类默认处理一下数据包:
// 控制包0x10CMD_I_AM_HERE、CMD_RETRANSMIT
// ping包0x15
// 变长包RETRANSMIT包type=IComPacketTypes.CMD_RETRANSMIT
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();
}
//如果电台回复I'm ready,就发起login
if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) {
sendLoginPacket();//电台准备好了,申请登录 0x80包
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;
}
}
/**
* connInfo0x900x90busy=0,busy=1
* 0x90macAddress
* IcomControlUdpXieGuControlUdp
* @param data 0x90
*/
public void onReceiveConnInfoPacket(byte[] data) {
}
/**
*
*
* @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);//发送令牌确认包 0x40
startTokenTimer();//启动令牌续订时钟
isAuthenticated = true;
}
}
if (onStreamEvents != null) {//触发认证事件
onStreamEvents.OnLoginResponse(IComPacketTypes.LoginResponsePacket.authIsOK(data));
}
}
/**
* 0x50
*
* @param data 0x50
*/
public void onReceiveStatusPacket(byte[] data) {
//if (this.authDone) return;//6100会频繁激活0x50包
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));
//todo 6100与icom有差异
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");
}
}
}
/**
* civ
*
* @param data
*/
public void sendCivData(byte[] data) {
civUdp.sendCivData(data);
}
/**
*
*
* @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++;
}
/**
* 0x80
*/
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.stopTXAudio();
audioUdp.close();
civUdp.sendOpenClose(false);
}
}

Wyświetl plik

@ -1,7 +1,10 @@
package com.bg7yoz.ft8cn.icom;
import android.util.Log;
/**
* ICom
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -9,6 +12,7 @@ public class IComPacketTypes {
private static final String TAG = "IComPacketTypes";
public static final int TX_BUFFER_SIZE = 0xf0;
public static final int XIEGU_TX_BUFFER_SIZE = 0x96;
/**
*
*/
@ -25,7 +29,7 @@ public class IComPacketTypes {
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 int AUDIO_HEAD_SIZE = 0x18;//音频数据包的头是0x10+0x08后面再跟音频数据
public static final short CMD_NULL = 0x00;//空指令
@ -53,7 +57,8 @@ public class IComPacketTypes {
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 int AUDIO_SAMPLE_RATE = 12000;//音频的采样率这是FT8CN发送给iCom使用的采样率
public static final int XIEGU_AUDIO_SAMPLE_RATE = 48000;//音频的采样率这是FT8CN发送给XIEGU使用的采样率
public static final short CODEC_ALL_SUPPORTED = 0x018b;
public static final short CODEC_ONLY_24K = 0x0100;
@ -124,109 +129,115 @@ public class IComPacketTypes {
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 void setRcvdId(byte[] data, int rcvdId) {
System.arraycopy(intToBigEndian(rcvdId), 0x00, data, 0x0c, 4);
}
}
public static class AudioPacket{
public static class AudioPacket {
/**
* quint32 len; // 0x00
* quint16 type; // 0x04
* quint16 seq; // 0x06
* quint32 sentid; // 0x08
* quint32 rcvdid; // 0x0c
*
*
* //接收的时候ident=0x8116 81068006
* quint16 ident; // 0x10 发射的时候: 当datalen=0xa0时,ident=0x9781,否则ident=0x0080;
* quint16 sendseq; // 0x12
* quint16 unused; // 0x14
* quint16 datalen; // 0x16
* quint32 len; // 0x00
* quint16 type; // 0x04
* quint16 seq; // 0x06
* quint32 sentid; // 0x08
* quint32 rcvdid; // 0x0c
* <p>
* <p>
* //接收的时候ident=0x8116 81068006
* 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<AUDIO_HEAD_SIZE) return false;
return data.length-AUDIO_HEAD_SIZE==readShortData(data,0x16);
public static boolean isAudioPacket(byte[] data) {
if (data.length < AUDIO_HEAD_SIZE) return false;
return data.length - AUDIO_HEAD_SIZE == readShortData(data, 0x16);
}
public static short getDataLen(byte[] data){
return readShortData(data,0x16);
public static short getDataLen(byte[] data) {
return readShortData(data, 0x16);
}
public static byte[] getAudioData(byte[] data){
byte[] audio=new byte[data.length-AUDIO_HEAD_SIZE];
System.arraycopy(data,0x18,audio,0,audio.length);
public static byte[] getAudioData(byte[] data) {
byte[] audio = new byte[data.length - AUDIO_HEAD_SIZE];
System.arraycopy(data, 0x18, audio, 0, audio.length);
return audio;
}
public static byte[] getTxAudioPacket(byte[] audio,short seq,int sentid,int rcvdid,short sendSeq){
byte[] packet=new byte[audio.length+AUDIO_HEAD_SIZE];
System.arraycopy(intToBigEndian(packet.length),0,packet,0,4);
System.arraycopy(shortToBigEndian(seq),0,packet,0x06,2);
System.arraycopy(intToBigEndian(sentid),0,packet,0x08,4);
System.arraycopy(intToBigEndian(rcvdid),0,packet,0x0c,4);
if (audio.length==0xa0){
System.arraycopy(shortToByte((short)0x8197),0,packet,0x10,2);
}else {//这个是常用的数值
System.arraycopy(shortToByte((short)0x8000),0,packet,0x10,2);
public static byte[] getTxAudioPacket(byte[] audio, short seq, int sentid, int rcvdid, short sendSeq) {
byte[] packet = new byte[audio.length + AUDIO_HEAD_SIZE];
System.arraycopy(intToBigEndian(packet.length), 0, packet, 0, 4);//包长
System.arraycopy(shortToBigEndian(seq), 0, packet, 0x06, 2);//序号=0
System.arraycopy(intToBigEndian(sentid), 0, packet, 0x08, 4);//客户id
System.arraycopy(intToBigEndian(rcvdid), 0, packet, 0x0c, 4);//电台id
if (audio.length == 0xa0) {
System.arraycopy(shortToByte((short) 0x8197), 0, packet, 0x10, 2);
} else {//这个是常用的数值
System.arraycopy(shortToByte((short) 0x8000), 0, packet, 0x10, 2);//一般是这个值
}
System.arraycopy(shortToByte(sendSeq),0,packet,0x12,2);
System.arraycopy(shortToByte(sendSeq), 0, packet, 0x12, 2);//包序号
System.arraycopy(shortToByte((short)audio.length),0,packet,0x16,2);
System.arraycopy(audio,0,packet,0x18,audio.length);
System.arraycopy(shortToByte((short) audio.length), 0, packet, 0x16, 2);
System.arraycopy(audio, 0, packet, 0x18, audio.length);
return packet;
}
}
public static class CivPacket{
/*
quint32 len; // 0x00
quint16 type; // 0x04
quint16 seq; // 0x06
quint32 sentid; // 0x08
quint32 rcvdid; // 0x0c
char reply; // 0x10,civ是c1
quint16 civ_len; // 0x11 此字段是小端模式0x0001,在数组中的顺序是0x0100
quint16 sendseq; //0x13
byte[] civ_data;//0x15
public static class CivPacket {
/**
* CIV
* quint32 len; // 0x00
* quint16 type; // 0x04
* quint16 seq; // 0x06
* quint32 sentid; // 0x08
* quint32 rcvdid; // 0x0c
* char reply; // 0x10,civ是c1
* quint16 civ_len; // 0x11 此字段是小端模式0x0001,在数组中的顺序是0x0100
* quint16 sendseq; //0x13
* byte[] civ_data;//0x15
*/
public static boolean checkIsCiv(byte[] data){
if (data.length<=0x15) return false;
public static boolean checkIsCiv(byte[] data) {
if (data.length <= 0x15) return false;
//是civ指令的条件长度不能小于0x15dataLen字段与实际相符reply=0xc1type!=0x01
return (data.length-0x15==readShortBigEndianData(data,0x11))
&&(data[0x10]==(byte)0xc1)
&&(ControlPacket.getType(data)!=CMD_RETRANSMIT);
return (data.length - 0x15 == readShortBigEndianData(data, 0x11))
&& (data[0x10] == (byte) 0xc1)
&& (ControlPacket.getType(data) != CMD_RETRANSMIT);
}
public static byte[] getCivData(byte[] data){
byte[] civ=new byte[data.length-0x15];
System.arraycopy(data,0x15,civ,0,data.length-0x15);
public static byte[] getCivData(byte[] data) {
byte[] civ = new byte[data.length - 0x15];
System.arraycopy(data, 0x15, civ, 0, data.length - 0x15);
return civ;
}
public static byte[] setCivData(short seq,int sentid,int rcvdid,short civSeq, byte[] data){
byte[] packet=new byte[data.length+0x15];
System.arraycopy(intToBigEndian(packet.length),0,packet,0,4);
System.arraycopy(shortToBigEndian(seq),0,packet,0x06,2);
System.arraycopy(intToBigEndian(sentid),0,packet,0x08,4);
System.arraycopy(intToBigEndian(rcvdid),0,packet,0x0c,4);
packet[0x10]=(byte) 0xc1;
System.arraycopy(shortToBigEndian((short)data.length),0,packet,0x11,2);
System.arraycopy(shortToByte(civSeq),0,packet,0x13,2);
System.arraycopy(data,0,packet,0x15,data.length);
public static byte[] setCivData(short seq, int sentid, int rcvdid, short civSeq, byte[] data) {
byte[] packet = new byte[data.length + 0x15];
System.arraycopy(intToBigEndian(packet.length), 0, packet, 0, 4);
System.arraycopy(shortToBigEndian(seq), 0, packet, 0x06, 2);
System.arraycopy(intToBigEndian(sentid), 0, packet, 0x08, 4);
System.arraycopy(intToBigEndian(rcvdid), 0, packet, 0x0c, 4);
packet[0x10] = (byte) 0xc1;
System.arraycopy(shortToBigEndian((short) data.length), 0, packet, 0x11, 2);
System.arraycopy(shortToByte(civSeq), 0, packet, 0x13, 2);
System.arraycopy(data, 0, packet, 0x15, data.length);
return packet;
}
}
public static class OpenClosePacket{
/*
quint32 len; // 0x00
quint16 type; // 0x04
quint16 seq; // 0x06
quint32 sentid; // 0x08
quint32 rcvdid; // 0x0c
char reply; // 0x10,openClose是c0,civ是c1
quint16 civ_len; // 0x11 此字段是小端模式0x0001,在数组中的顺序是0x0100
quint16 sendseq; //0x13
char magic; // 0x15
public static class OpenClosePacket {
/**
* quint32 len; // 0x00
* quint16 type; // 0x04
* quint16 seq; // 0x06
* quint32 sentid; // 0x08
* quint32 rcvdid; // 0x0c
* char reply; // 0x10,openClose是c0,civ是c1
* quint16 civ_len; // 0x11 此字段是小端模式0x0001,在数组中的顺序是0x0100
* quint16 sendseq; //0x13
* char magic; // 0x15
*/
public static byte[] toBytes(short seq, int sentId, int rcvdId,short civSeq,byte magic) {
public static byte[] toBytes(short seq, int sentId, int rcvdId, short civSeq, byte magic) {
byte[] packet = new byte[OPENCLOSE_SIZE];
System.arraycopy(intToBigEndian(OPENCLOSE_SIZE), 0, packet, 0, 4);
//System.arraycopy(shortToBigEndian(type), 0, packet, 4, 2);
@ -234,10 +245,10 @@ public class IComPacketTypes {
System.arraycopy(intToBigEndian(sentId), 0, packet, 8, 4);
System.arraycopy(intToBigEndian(rcvdId), 0, packet, 12, 4);
packet[0x10]=(byte)0xc0;
System.arraycopy(shortToBigEndian((short)0x0001),0,packet,0x11,2);
System.arraycopy(shortToByte(civSeq),0,packet,0x13,2);
packet[0x15]=magic;
packet[0x10] = (byte) 0xc0;
System.arraycopy(shortToBigEndian((short) 0x0001), 0, packet, 0x11, 2);
System.arraycopy(shortToByte(civSeq), 0, packet, 0x13, 2);
packet[0x15] = magic;
return packet;
}
}
@ -247,22 +258,22 @@ public class IComPacketTypes {
* reply==0pingreply=1,PingpingSeq++
*/
public static class PingPacket {
/*
quint32 len; // 0x00
quint16 type; // 0x04
quint16 seq; // 0x06
quint32 sentid; // 0x08
quint32 rcvdid; // 0x0c
char reply; // 0x10 如果接收的数据中reply=0x00我就要用sendReplayPingData()回复Ping。
union { // 此部分发送与接收定义是不同的
struct { // Ping包
quint32 time; // 0x11
};
struct { // 发送
quint16 datalen; // 0x11
quint16 sendseq; //0x13
};
}
/**
* quint32 len; // 0x00
* quint16 type; // 0x04
* quint16 seq; // 0x06
* quint32 sentid; // 0x08
* quint32 rcvdid; // 0x0c
* char reply; // 0x10 如果接收的数据中reply=0x00我就要用sendReplayPingData()回复Ping。
* union { // 此部分发送与接收定义是不同的
* struct { // Ping包
* quint32 time; // 0x11
* };
* struct { // 发送
* quint16 datalen; // 0x11
* quint16 sendseq; //0x13
* };
* }
*/
public static boolean isPingPacket(byte[] data) {
return readShortBigEndianData(data, 0x04) == CMD_PING;
@ -326,8 +337,8 @@ public class IComPacketTypes {
* Status0x50
*/
public static class StatusPacket {
/*
quint32 len; // 0x00
/**
quint32 len; // 0x00
quint16 type; // 0x04
quint16 seq; // 0x06
quint32 sentid; // 0x08
@ -386,7 +397,7 @@ public class IComPacketTypes {
* 0x90APP.
*/
public static class ConnInfoPacket {
/*
/**
quint32 len; // 0x00
quint16 type; // 0x04
quint16 seq; // 0x06
@ -437,7 +448,7 @@ public class IComPacketTypes {
*/
public static boolean getBusy(byte[] data) {
return data[0x60] == 0x00;
return data[0x60] != 0x00;
}
public static byte[] getMacAddress(byte[] data) {
@ -497,7 +508,7 @@ public class IComPacketTypes {
short seq, int localSID, int remoteSID
, byte requestReply, byte requestType
, short authInnerSendSeq, short tokRequest, int token
, String rigName, String userName, int sampleRate
, String rigName, String userName, int rx_sampleRate,int tx_sampleRate
, int civPort, int audioPort, int txBufferSize) {
byte[] packet = new byte[CONNINFO_SIZE];
System.arraycopy(intToBigEndian(CONNINFO_SIZE), 0, packet, 0, 4);//len
@ -527,8 +538,8 @@ public class IComPacketTypes {
packet[0x71] = 0x01;//txEnable支持发射
packet[0x72] = IcomCodecType.LPCM_1CH_16BIT;//rxCodec,0x04:LPcm 16Bit 1ch,0x02:LPcm 8Bit 1ch
packet[0x73] = IcomCodecType.LPCM_1CH_16BIT;//txCodec,0x04:LPcm 16Bit 1ch,0x02:LPcm 8Bit 1ch
System.arraycopy(intToByte(sampleRate), 0, packet, 0x74, 4);//rxSampleRate采样率
System.arraycopy(intToByte(sampleRate), 0, packet, 0x78, 4);//txSampleRate采样率
System.arraycopy(intToByte(rx_sampleRate), 0, packet, 0x74, 4);//rxSampleRate采样率
System.arraycopy(intToByte(tx_sampleRate), 0, packet, 0x78, 4);//txSampleRate采样率
System.arraycopy(intToByte(civPort), 0, packet, 0x7c, 4);//civPort本地CI-V端口
System.arraycopy(intToByte(audioPort), 0, packet, 0x80, 4);//audioPort本地音频端口
System.arraycopy(intToByte(txBufferSize), 0, packet, 0x84, 4);//txBuffer发送缓冲区
@ -579,7 +590,7 @@ public class IComPacketTypes {
* 0x66Capabilities0x420xA8
*/
public static class RadioCapPacket {
/*
/**
union {
struct {
quint8 unusede[7]; // 0x00
@ -649,7 +660,7 @@ public class IComPacketTypes {
* Token,tokRequest
*/
public static class TokenPacket {
/*
/**
public int len = TOKEN_SIZE; // 0x00 (int32)
public short type; // 0x04(int16)
public short seq; // 0x06(int16)
@ -755,7 +766,7 @@ public class IComPacketTypes {
* 0x6096
*/
public static class LoginResponsePacket {
/*
/**
public int len;// 0x00 (int32)
public short type; // 0x04(int16)
public short seq; // 0x06(int16)
@ -802,7 +813,7 @@ public class IComPacketTypes {
* ,0x80128
*/
public static class LoginPacket {
/*
/**
public int len = LOGIN_SIZE; // 0x00 (int32)
public short type; // 0x04(int16)
public short seq; // 0x06(int16)
@ -907,7 +918,7 @@ public class IComPacketTypes {
byte[] buf = new byte[len];
byte[] temp = s.getBytes();
for (int i = 0; i < temp.length; i++) {
if (i > len) break;
if (i >= len) break;
buf[i] = temp[i];
}
return buf;

Wyświetl plik

@ -1,12 +1,10 @@
package com.bg7yoz.ft8cn.icom;
/**
* WIFI
* WIFIiCom
* @author BGY70Z
* @date 2023-03-20
*/
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import com.bg7yoz.ft8cn.GeneralVariables;
@ -16,36 +14,18 @@ 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 class IComWifiRig extends WifiRig{
public IComWifiRig(String ip, int port, String userName, String password) {
this.ip = ip;
this.port = port;
this.userName = userName;
this.password = password;
super(ip,port,userName,password);
}
@Override
public void start(){
opened=true;
openAudio();//打开音频
controlUdp=new IcomControlUdp(userName,password,ip,port);
//设置事件,这里可以处理电台状态,和接收电台送来的音频数据
controlUdp.setOnStreamEvents(new IcomUdpBase.OnStreamEvents() {
@Override
@ -55,15 +35,15 @@ public class IComWifiRig {
@Override
public void OnReceivedCivData(byte[] data) {
if (onIComDataEvents!=null){
onIComDataEvents.onReceivedCivData(data);
if (onDataEvents!=null){
onDataEvents.onReceivedCivData(data);
}
}
@Override
public void OnReceivedAudioData(byte[] audioData) {
if (onIComDataEvents!=null){
onIComDataEvents.onReceivedWaveData(audioData);
if (onDataEvents!=null){
onDataEvents.onReceivedWaveData(audioData);
}
if (audioTrack!=null){
// if (!isPttOn) {//如果ptt没有按下
@ -95,15 +75,19 @@ public class IComWifiRig {
controlUdp.startAreYouThereTimer();//开始连接电台
}
@Override
public void setPttOn(boolean on){//打开PTT
isPttOn=on;
controlUdp.civUdp.sendPttAction(on);
controlUdp.audioUdp.isPttOn=on;
}
@Override
public void sendCivData(byte[] data){
controlUdp.civUdp.sendCivData(data);
controlUdp.sendCivData(data);
}
@Override
public void sendWaveData(float[] data){//发送音频数据到电台
controlUdp.sendWaveData(data);
}
@ -111,42 +95,12 @@ public class IComWifiRig {
/**
*
*/
@Override
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;
}
}

Wyświetl plik

@ -1,8 +1,8 @@
package com.bg7yoz.ft8cn.icom;
/**
* ICom
* IComAudioUdp
* @author BGY70Z
* @date 2023-03-20
* @date 2023-08-26
*/
import android.util.Log;
@ -15,31 +15,21 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class IcomAudioUdp extends IcomUdpBase {
public class IcomAudioUdp extends AudioUdp {
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];
//传递过来的音频是LPCM,32 float12000Hz
//iCOM的音频格式是LPCM 16 Int12000Hz
//要做一下浮点到16位int的转换
for (int i = 0; i < audioData.length; i++) {
float x = audioData[i];
@ -54,7 +44,7 @@ public class IcomAudioUdp extends IcomUdpBase {
}
private static class DoTXAudioRunnable implements Runnable{
IcomAudioUdp icomAudioUdp;
short[] audioData;
short[] audioData;//传递过来的音频是LPCM 16bit Int,12000hz
public DoTXAudioRunnable(IcomAudioUdp icomAudioUdp) {
this.icomAudioUdp = icomAudioUdp;
@ -95,9 +85,21 @@ public class IcomAudioUdp extends IcomUdpBase {
}
}
}
Log.e(TAG, "run: 音频发送完毕!!" );
Log.d(TAG, "run: 音频发送完毕!!" );
Thread.currentThread().interrupt();
}
}
@Override
public void onDataReceived(DatagramPacket packet, byte[] data) {
super.onDataReceived(packet, data);
//接收到的是12000采样率的数据
if (!IComPacketTypes.AudioPacket.isAudioPacket(data)) return;
byte[] audioData = IComPacketTypes.AudioPacket.getAudioData(data);
if (onStreamEvents != null) {
onStreamEvents.OnReceivedAudioData(audioData);
}
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.icom;
/**
* ICom
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -11,83 +12,22 @@ import java.net.DatagramPacket;
import java.util.Timer;
import java.util.TimerTask;
public class IcomControlUdp extends IcomUdpBase {
public class IcomControlUdp extends ControlUdp {
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;
super(userName,password,remoteIp,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;
}
}
/**
* connInfo0x900x90busy=0,busy=1
@ -95,150 +35,25 @@ public class IcomControlUdp extends IcomUdpBase {
*
* @param data 0x90
*/
@Override
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");
if (!rigIsBusy) {//说明是第一次收到0x90数据包要回复一个x090数据包
sendTrackedPacket(
IComPacketTypes.ConnInfoPacket.connInfoPacketData(data, (short) 0
, localId, remoteId
, (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken
, rigName, userName
, IComPacketTypes.AUDIO_SAMPLE_RATE//48000采样率
, IComPacketTypes.AUDIO_SAMPLE_RATE//接收12000采样率
, IComPacketTypes.AUDIO_SAMPLE_RATE//发射12000采样率
, civUdp.localPort, audioUdp.localPort
, IComPacketTypes.TX_BUFFER_SIZE));
, IComPacketTypes.XIEGU_TX_BUFFER_SIZE));//0x96是wfView常用的缓冲区长度
//, IComPacketTypes.TX_BUFFER_SIZE));//0xf0是之前测试的缓冲区长度
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);
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.icom;
/**
* udp
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -10,6 +11,8 @@ import android.util.Log;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import org.checkerframework.checker.units.qual.A;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
@ -18,18 +21,20 @@ import java.net.UnknownHostException;
import java.util.Timer;
import java.util.TimerTask;
public class IcomUdpBase {
public enum IcomUdpStyle{//数据流的类型
public enum IcomUdpStyle {//数据流的类型
UdpBase,
ControlUdp,
CivUdp,
AudioUdp
}
public static String getUdpStyle(IcomUdpStyle style){
switch (style){
public static String getUdpStyle(IcomUdpStyle style) {
switch (style) {
case ControlUdp:
return GeneralVariables.getStringFromResource(R.string.control_stream);
case CivUdp:
case CivUdp:
return GeneralVariables.getStringFromResource(R.string.civ_stream);
case AudioUdp:
return GeneralVariables.getStringFromResource(R.string.audio_stream);
@ -37,18 +42,24 @@ public class IcomUdpBase {
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 OnUdpSendIOException(IcomUdpStyle style, IOException e);
void OnLoginResponse(boolean authIsOK);
//void OnWatchDogAlert(IcomUdpStyle style,boolean isAlerted);
}
public IcomUdpStyle udpStyle=IcomUdpStyle.UdpBase;
public IcomUdpStyle udpStyle = IcomUdpStyle.UdpBase;
private static final String TAG = "IcomUdpBase";
public int rigPort;
@ -63,14 +74,13 @@ public class IcomUdpBase {
public short innerSeq = 0x30;
public int rigToken;//电台提供的令牌
public short localToken = (short) System.currentTimeMillis();//本地生成的令牌,可以是随机数
public boolean isPttOn=false;
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 long lastReceivedTime = System.currentTimeMillis();//最后收到数据的时间
public long lastSentTime = System.currentTimeMillis();//最后收到数据的时间
public IcomUdpClient udpClient;//用于与电台通讯的udp
@ -79,32 +89,35 @@ public class IcomUdpBase {
public OnStreamEvents onStreamEvents;//一些事件处理
//Timer执行代码部分TimerTask具体执行timer.schedule(task,delay,period)
public Timer areYouThereTimer;
private AreYouThereTimerTask areYouThereTask = null;
public Timer pingTimer;
public Timer idleTimer;//发送空数据包的时钟
public void close(){
onStreamEvents=null;//不用弹出网络异常的消息了
public void close() {
onStreamEvents = null;//不用弹出网络异常的消息了
sendUntrackedPacket(IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_DISCONNECT
,(short)0,localId,remoteId));
, (short) 0, localId, remoteId));
stopTimer(areYouThereTimer);
stopTimer(pingTimer);
stopTimer(idleTimer);
closeStream();
}
/**
* udpClient
*/
public void closeStream(){
if (udpClient!=null){
public void closeStream() {
if (udpClient != null) {
try {
udpClient.setActivated(false);
} catch (SocketException e) {
e.printStackTrace();
Log.e(TAG, "closeStream: "+e.getMessage() );
Log.e(TAG, "closeStream: " + e.getMessage());
}
}
}
/**
* UdpUdp
*/
@ -116,16 +129,17 @@ public class IcomUdpBase {
@Override
public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) {
//屏蔽掉非法的数据包
if (data.length<IComPacketTypes.CONTROL_SIZE) return;//如果小于最小的控制包,退出
if (IComPacketTypes.ControlPacket.getRcvdId(data)!=localId) return;//如果接收的ID与我的ID不同也退出
if (data.length < IComPacketTypes.CONTROL_SIZE) return;//如果小于最小的控制包,退出
if (IComPacketTypes.ControlPacket.getRcvdId(data) != localId)
return;//如果接收的ID与我的ID不同也退出
onDataReceived(packet,data);
onDataReceived(packet, data);
}
@Override
public void OnUdpSendIOException(IOException e) {
if (onStreamEvents!=null){
onStreamEvents.OnUdpSendIOException(udpStyle,e);
if (onStreamEvents != null) {
onStreamEvents.OnUdpSendIOException(udpStyle, e);
}
}
});
@ -158,7 +172,7 @@ public class IcomUdpBase {
*
* @param data
*/
public void onDataReceived(DatagramPacket packet,byte[] data) {
public void onDataReceived(DatagramPacket packet, byte[] data) {
//此处是对共性的数据接包做处理不是共性的可以在后续继承的类中override.
switch (data.length) {
@ -236,13 +250,6 @@ public class IcomUdpBase {
*/
public void retransmitPacket(short retransmitSeq) {
byte[] packet = txSeqBuffer.get(retransmitSeq);
if (packet==null){
Log.e(TAG, String.format("retransmitPacket:remotePort:%d,seq=0x%x,data:null ",rigPort,retransmitSeq) );
}else {
//IComPacketTypes.ControlPacket.setRcvdId(packet,remoteId);//如果mei
Log.e(TAG, String.format("retransmitPacket:remotePort:%d,seq=0x%x,data:%s "
, rigPort,retransmitSeq, IComPacketTypes.byteToStr(packet)));
}
if (packet != null) {//找到了历史发送的数据包
sendUntrackedPacket(packet);
@ -267,22 +274,19 @@ public class IcomUdpBase {
}
/**
* Are you there
* 500ms
* Are you there0x10 0x03
*/
public void startAreYouThereTimer() {
Log.e(TAG, "startAreYouThereTimer: stop timer:" + this.toString());
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);
areYouThereTimer.scheduleAtFixedRate(new AreYouThereTimerTask()
, 0, IComPacketTypes.ARE_YOU_THERE_PERIOD_MS);
}
/**
@ -292,13 +296,9 @@ public class IcomUdpBase {
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周期
pingTimer.scheduleAtFixedRate(new PingTimerTask(), 0, IComPacketTypes.PING_PERIOD_MS);
}
/**
*
*/
@ -306,16 +306,8 @@ public class IcomUdpBase {
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);
idleTimer.scheduleAtFixedRate(new IdleTimerTask(), IComPacketTypes.IDLE_PERIOD_MS
, IComPacketTypes.IDLE_PERIOD_MS);
}
/**
@ -349,9 +341,9 @@ public class IcomUdpBase {
* 0x40
* @param requestType 0x020x05
*/
public void sendTokenPacket(byte requestType){
sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short)0
,localId,remoteId,requestType,innerSeq,localToken,rigToken));
public void sendTokenPacket(byte requestType) {
sendTrackedPacket(IComPacketTypes.TokenPacket.getTokenPacketData((short) 0
, localId, remoteId, requestType, innerSeq, localToken, rigToken));
innerSeq++;
}
@ -394,7 +386,7 @@ public class IcomUdpBase {
*/
public synchronized void sendTrackedPacket(byte[] data) {
try {
lastSentTime=System.currentTimeMillis();
lastSentTime = System.currentTimeMillis();
System.arraycopy(IComPacketTypes.shortToBigEndian(trackedSeq), 0
, data, 6, 2);//把序号写到数据列表里
udpClient.sendData(data, rigIp, rigPort);
@ -426,5 +418,43 @@ public class IcomUdpBase {
this.onStreamEvents = onStreamEvents;
}
/**
* Are you there
* 0x100x03
*/
public class AreYouThereTimerTask extends TimerTask {
@Override
public void run() {
Log.d(TAG, String.format("Task,AreYouThereTimer: local port:%d,remote port %d", localPort, rigPort));
sendUntrackedPacket(
IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_ARE_YOU_THERE
, (short) 0, localId, 0));
}
}
/**
* Ping
*/
public class PingTimerTask extends TimerTask {
@Override
public void run() {
sendPingPacket();//发送Ping包
}
}
/**
*
*/
public class IdleTimerTask extends TimerTask {
@Override
public void run() {
if (txSeqBuffer.getTimeOut() > 200) {//当超过200毫秒没有发送指令就发送一个空指令
sendTrackedPacket(
IComPacketTypes.ControlPacket.toBytes(IComPacketTypes.CMD_NULL
, (short) 0, localId, remoteId));
}
}
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.icom;
/**
* udp
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -22,31 +23,32 @@ public class IcomUdpClient {
private static final String TAG = "RadioUdpSocket";
private final int MAX_BUFFER_SIZE = 1024 *2;
private final int MAX_BUFFER_SIZE = 1024 * 2;
private DatagramSocket sendSocket;
//private int remotePort;
private int localPort=-1;
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 DoReceiveRunnable doReceiveRunnable = new DoReceiveRunnable(this);
private final ExecutorService sendDataThreadPool = Executors.newCachedThreadPool();
private SendDataRunnable sendDataRunnable=new SendDataRunnable(this);
private SendDataRunnable sendDataRunnable = new SendDataRunnable(this);
public IcomUdpClient() {//本地端口随机
localPort=-1;
}
public IcomUdpClient(int localPort) {//如果localPort==-1本地端口随机
this.localPort=localPort;
localPort = -1;
}
public void sendData(byte[] data, String ip,int port) throws UnknownHostException {
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;
sendDataRunnable.address = address;
sendDataRunnable.data = data;
sendDataRunnable.port = port;
sendDataThreadPool.execute(sendDataRunnable);
// new Thread(new Runnable() {
// @Override
@ -66,7 +68,8 @@ public class IcomUdpClient {
// }
// }).start();
}
private static class SendDataRunnable implements Runnable{
private static class SendDataRunnable implements Runnable {
byte[] data;
int port;
InetAddress address;
@ -81,12 +84,15 @@ public class IcomUdpClient {
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
synchronized (this) {
try {
client.sendSocket.send(packet);
if (client.sendSocket != null) client.sendSocket.send(packet);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "IComUdpClient: " + e.getMessage());
if (client.onUdpEvents!=null){
client.onUdpEvents.OnUdpSendIOException(e);
if (client != null) {
if (client.onUdpEvents != null) {
client.onUdpEvents.OnUdpSendIOException(e);
}
}
}
}
@ -103,12 +109,12 @@ public class IcomUdpClient {
sendSocket = new DatagramSocket();
//new DatagramSocket(null);//绑定的端口号随机
sendSocket.setReuseAddress(true);
if (localPort!=-1) {//绑定指定的本机端口
if (localPort != -1) {//绑定指定的本机端口
sendSocket.bind(new InetSocketAddress(localPort));
}
//更新一下本地端口值
localPort=sendSocket.getLocalPort();
localPort = sendSocket.getLocalPort();
Log.e(TAG, "openUdpPort: " + sendSocket.getLocalPort());
//Log.e(TAG, "openUdpIp: " + sendSocket.getLocalAddress());
@ -161,6 +167,7 @@ public class IcomUdpClient {
public interface OnUdpEvents {
void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data);
void OnUdpSendIOException(IOException e);
}
@ -192,7 +199,8 @@ public class IcomUdpClient {
}
return s.toString();
}
private static class DoReceiveRunnable implements Runnable{
private static class DoReceiveRunnable implements Runnable {
IcomUdpClient icomUdpClient;
public DoReceiveRunnable(IcomUdpClient icomUdpClient) {

Wyświetl plik

@ -0,0 +1,95 @@
package com.bg7yoz.ft8cn.icom;
/**
* WIFIiCom
*
* @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 abstract class WifiRig {
public interface OnDataEvents {
void onReceivedCivData(byte[] data);
void onReceivedWaveData(byte[] data);
}
public ControlUdp controlUdp;
public AudioTrack audioTrack = null;
public final String ip;
public final int port;
public final String userName;
public final String password;
public boolean opened = false;
public boolean isPttOn = false;
public OnDataEvents onDataEvents;
public WifiRig(String ip, int port, String userName, String password) {
this.ip = ip;
this.port = port;
this.userName = userName;
this.password = password;
}
public abstract void start();
public abstract void setPttOn(boolean on);
public abstract void sendCivData(byte[] data);
public abstract void sendWaveData(float[] data);
/**
*
*/
public abstract void close();
/**
*
*/
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 setOnDataEvents(OnDataEvents onDataEvents) {
this.onDataEvents = onDataEvents;
}
}

Wyświetl plik

@ -0,0 +1,191 @@
package com.bg7yoz.ft8cn.icom;
/**
* AudioUdp
*
* @author BGY70Z
* @date 2023-08-26
*/
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;
public class XieGuAudioUdp extends AudioUdp {
private static final String TAG = "XieGuAudioUdp";
private final ExecutorService doTXThreadPool = Executors.newCachedThreadPool();
// private final DoTXAudioRunnable doTXAudioRunnable = new DoTXAudioRunnable(this);
private final AudioRunnable audioRunnable = new AudioRunnable(this);
private boolean audioIsRunning = false;
//private DatagramPacket packet;
//private byte[] data;
@Override
public void sendTxAudioData(float[] audioData) {
if (audioData == null) return;
short[] temp = new short[audioData.length];//12000
//传递过来的音频是LPCM,32 float12000Hz
//要做一下浮点到16位int的转换
for (int i = 0; i < audioData.length; i++) {
float x = audioData[i];
if (x > 0.999999f)
temp[i] = 32767;
else if (x < -0.999999f)
temp[i] = -32766;
else
temp[i] = (short) (x * 32767.0);
}
audioRunnable.setAudioData(temp);
//doTXAudioRunnable.audioData = temp;
//doTXThreadPool.execute(doTXAudioRunnable);
}
@Override
public void startTxAudio() {
if (!audioIsRunning) {
audioIsRunning = true;
doTXThreadPool.execute(audioRunnable);
}
}
@Override
public void stopTXAudio() {
audioIsRunning = false;
audioRunnable.stop();
}
private static class AudioRunnable implements Runnable {
private final int partialLen = (int) (IComPacketTypes.AUDIO_SAMPLE_RATE * 0.02);//20ms的数据包的长度
private final byte[] audioPacket = new byte[partialLen * 2];
private final byte[] ft8Audio = new byte[15 * IComPacketTypes.AUDIO_SAMPLE_RATE * 2];//15秒采样率*216位所以2倍
private int index = 0;
XieGuAudioUdp audioUdp;
private boolean isRunning = true;
public AudioRunnable(XieGuAudioUdp audioUdp) {
this.audioUdp = audioUdp;
Log.e(TAG, "AudioRunnable: create runnable");
}
public void setAudioData(short[] audioData) {
for (int i = 0; i < audioData.length; i++) {
System.arraycopy(IComPacketTypes.shortToBigEndian((short)
(audioData[i]
* GeneralVariables.volumePercent))//乘以信号量的比率
, 0, ft8Audio, i * 2, 2);
}
index = 0;
}
@Override
public void run() {
while (isRunning) {
long now = System.currentTimeMillis() - 1;//获取当前时间
if (audioUdp.isPttOn) {
System.arraycopy(ft8Audio, index, audioPacket, 0, audioPacket.length);
index = index + partialLen * 2;
if (index >= ft8Audio.length) index = 0;
}
audioUdp.sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket
, (short) 0, audioUdp.localId, audioUdp.remoteId, audioUdp.innerSeq));
audioUdp.innerSeq++;
while (isRunning) {
if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期
break;
}
}
}
}
public void stop() {
isRunning = false;
}
}
//
// private static class DoTXAudioRunnable implements Runnable {
// XieGuAudioUdp audioUdp;
// short[] audioData;//传递过来的音频是LPCM 16bit Int,12000hz
//
// public DoTXAudioRunnable(XieGuAudioUdp audioUdp) {
// this.audioUdp = audioUdp;
// }
//
// @Override
// public void run() {
// if (audioData == null) return;
//
// final int partialLen = (int) (IComPacketTypes.AUDIO_SAMPLE_RATE * 0.02);//20ms的数据包的长度
//
// //要转换一下到BYTE,小端模式
// //先播放是给出空的声音for i 循环做了一个判断是给前面的空声音for j循环做得判断是让后面发送空声音
// byte[] audioPacket = new byte[partialLen * 2];
// for (int i = 0; i < (audioData.length / partialLen) + 8; i++) {//多出6个周期前面3个后面3个多
// if (!audioUdp.isPttOn) break;
// long now = System.currentTimeMillis() - 1;//获取当前时间
//
// audioUdp.sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket
// , (short) 0, audioUdp.localId, audioUdp.remoteId, audioUdp.innerSeq));
// audioUdp.innerSeq++;
//
// Arrays.fill(audioPacket, (byte) 0x00);
// if (i >= 3) {//让前两个空数据发送出去
// for (int j = 0; j < partialLen; j++) {
// if ((i - 3) * partialLen + j < audioData.length) {
// System.arraycopy(IComPacketTypes.shortToBigEndian((short)
// (audioData[(i - 3) * partialLen + j]
// * GeneralVariables.volumePercent))//乘以信号量的比率
// , 0, audioPacket, j * 2, 2);
// }
// }
// }
// while (audioUdp.isPttOn) {
// if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期
// break;
// }
// }
// }
// Log.d(TAG, "run: 音频发送完毕!!");
// Thread.currentThread().interrupt();
// }
//
// }
/**
*
*
* @param packet
* @param data
*/
@Override
public void onDataReceived(DatagramPacket packet, byte[] data) {
super.onDataReceived(packet, data);
if (IComPacketTypes.CONTROL_SIZE == data.length) {
if (IComPacketTypes.ControlPacket.getType(data) == IComPacketTypes.CMD_I_AM_READY) {
startTxAudio();
}
}
if (!IComPacketTypes.AudioPacket.isAudioPacket(data)) return;
byte[] audioData = IComPacketTypes.AudioPacket.getAudioData(data);
if (onStreamEvents != null) {
onStreamEvents.OnReceivedAudioData(audioData);
}
}
}

Wyświetl plik

@ -0,0 +1,64 @@
package com.bg7yoz.ft8cn.icom;
/**
* Wifi
*
* @author BGY70Z
* @date 2023-08-26
*/
import android.util.Log;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import java.io.IOException;
import java.net.DatagramPacket;
import java.util.Timer;
import java.util.TimerTask;
public class XieGuControlUdp extends ControlUdp {
private static final String TAG = "XieGuControlUdp";
public XieGuControlUdp(String userName, String password, String remoteIp, int remotePort) {
super(userName,password,remoteIp,remotePort);
civUdp = new IcomCivUdp();
audioUdp = new XieGuAudioUdp();
civUdp.rigIp = remoteIp;
audioUdp.rigIp = remoteIp;
civUdp.openStream();
audioUdp.openStream();
}
/**
* connInfo0x900x90busy=0,busy=1
* 0x90macAddress
*
* @param data 0x90
*/
@Override
public void onReceiveConnInfoPacket(byte[] data) {
rigMacAddress = IComPacketTypes.ConnInfoPacket.getMacAddress(data);
rigIsBusy = IComPacketTypes.ConnInfoPacket.getBusy(data);
rigName = IComPacketTypes.ConnInfoPacket.getRigName(data);
if (!rigIsBusy) {//说明是第一次收到0x90数据包要回复一个x090数据包
sendTrackedPacket(
IComPacketTypes.ConnInfoPacket.connInfoPacketData(data, (short) 0
, localId, remoteId
, (byte) 0x01, (byte) 0x03, innerSeq, localToken, rigToken
, rigName, userName
, IComPacketTypes.AUDIO_SAMPLE_RATE//接收12000采样率
, IComPacketTypes.AUDIO_SAMPLE_RATE//12000采样率
//, IComPacketTypes.XIEGU_AUDIO_SAMPLE_RATE//48000采样率
, civUdp.localPort, audioUdp.localPort
, IComPacketTypes.XIEGU_TX_BUFFER_SIZE));
innerSeq++;
}
}
}

Wyświetl plik

@ -0,0 +1,104 @@
package com.bg7yoz.ft8cn.icom;
/**
* WIFI
* @author BGY70Z
* @date 2023-08-27
*/
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 XieGuWifiRig extends WifiRig{
public XieGuWifiRig(String ip, int port, String userName, String password) {
super(ip,port,userName,password);
}
@Override
public void start(){
opened=true;
openAudio();//打开音频
controlUdp=new XieGuControlUdp(userName,password,ip,port);
//设置事件,这里可以处理电台状态,和接收电台送来的音频数据
controlUdp.setOnStreamEvents(new IcomUdpBase.OnStreamEvents() {
@Override
public void OnReceivedIAmHere(byte[] data) {
}
@Override
public void OnReceivedCivData(byte[] data) {
if (onDataEvents!=null){
onDataEvents.onReceivedCivData(data);
}
}
@Override
public void OnReceivedAudioData(byte[] audioData) {
if (onDataEvents!=null){
onDataEvents.onReceivedWaveData(audioData);
}
if (audioTrack!=null){
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();//开始连接电台
}
@Override
public void setPttOn(boolean on){//打开PTT
isPttOn=on;
controlUdp.civUdp.sendPttAction(on);
controlUdp.audioUdp.isPttOn=on;
}
@Override
public void sendCivData(byte[] data){
controlUdp.sendCivData(data);
}
@Override
public void sendWaveData(float[] data){//发送音频数据到电台
controlUdp.sendWaveData(data);
}
/**
*
*/
@Override
public void close(){
opened=false;
controlUdp.closeAll();
closeAudio();
}
}

Wyświetl plik

@ -130,4 +130,16 @@ public abstract class BaseRig {
public boolean isPttOn() {
return isPttOn;
}
/**
* 2023-08-16 DS1UFX0.9(tr)uSDX audio over cat
*/
public boolean supportWaveOverCAT() {
return false;
}
public void onDisconnecting() {
}
}

Wyświetl plik

@ -88,7 +88,7 @@ public class FlexNetworkRig extends BaseRig {
if (getConnector() != null) {
float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
, 24000);//flex音频的采样率是24000todo 此处可改为动态设置2400048000
, 24000);//flex音频的采样率是24000
if (data == null) {
setPTT(false);
return;

Wyświetl plik

@ -183,6 +183,7 @@ public class GuoHeQ900Rig extends BaseRig {
public GuoHeQ900Rig() {
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT);
//readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, 4000);
}
}

Wyświetl plik

@ -115,7 +115,7 @@ public class IcomCommand {
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
&& buffer[i + 3] == (byte) rigAddr) {//电台地址705的默认值是A4协谷是70
position = i;
break;
}

Wyświetl plik

@ -1,5 +1,10 @@
package com.bg7yoz.ft8cn.rigs;
/**
* IcomRigIcomwifiIComWifiConnector(WifiConnector)
* IComWifiConnectorIComWifiRig
*/
import android.util.Log;
import com.bg7yoz.ft8cn.Ft8Message;
@ -131,9 +136,9 @@ public class IcomRig extends BaseRig {
@Override
public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式
if (getConnector() != null) {
if (getConnector() != null) {//把生成的具体音频数据传递到Connector
float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
,12000);//此处icom电台发射音频的采样率是12000todo 此处可改为动态设置2400048000
,12000);//此处icom电台发射音频的采样率是12000
if (data==null){
setPTT(false);
return;

Wyświetl plik

@ -19,4 +19,11 @@ public class InstructionSet {
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;
public static final int TRUSDX=17;//(tr)USDX;
public static final int KENWOOD_TS570=18;//KENWOOD TS-570D
public static final int YAESU_3_9_U_DIG=19;//KENWOOD TS-570D
}

Wyświetl plik

@ -1,4 +1,7 @@
package com.bg7yoz.ft8cn.rigs;
/**
* DS1UFX 2023-08-160.9(tr)uSDX audio over cat
*/
import android.annotation.SuppressLint;
@ -12,8 +15,8 @@ public class KenwoodTK90RigConstant {
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;//超过,在表上显示红色
public static final int ts_590_swr_alert_max = 15;//相当于3.0
public static final int ts_590_alc_alert_max = 15;//超过,在表上显示红色
//PTT状态
@ -26,19 +29,26 @@ public class KenwoodTK90RigConstant {
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_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
private static final String TS570_PTT_OFF = "RX;";//KENWOOD TS570,PTT
private static final String TS570_PTT_ON = "TX;";//KENWOOD TS570,PTT
// (tr)uSDX extensions
private static final String TRUSDX_STREAMING_OFF = "UA0;";
private static final String TRUSDX_STREAMING_ON_SPEAKER_ON = "UA1;";
private static final String TRUSDX_STREAMING_ON_SPEAKER_OFF = "UA2;";
private static final String TRUSDX_STREAMING_AUDIO = "US";
public static String getModeStr(int mode) {
switch (mode) {
@ -76,7 +86,16 @@ public class KenwoodTK90RigConstant {
return TS590_PTT_OFF.getBytes();
}
} public static byte[] setTS2000PTTState(boolean on) {
}
public static byte[] setTS570PTTState(boolean on) {
if (on) {
return TS570_PTT_ON.getBytes();
} else {
return TS570_PTT_OFF.getBytes();
}
}
public static byte[] setTS2000PTTState(boolean on) {
if (on) {
return TS2000_PTT_ON.getBytes();
} else {
@ -84,6 +103,7 @@ public class KenwoodTK90RigConstant {
}
}
public static byte[] setFLEX6000PTTState(boolean on) {
if (on) {
return FLEX_6000_PTT_ON.getBytes();
@ -95,41 +115,64 @@ public class KenwoodTK90RigConstant {
//设置成VFO模式
public static byte[] setVFOMode(){
public static byte[] setVFOMode() {
return SET_VFO.getBytes();
}
public static byte[] setTS590VFOMode(){
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();
return String.format("FA%011d\r", freq).getBytes();
}
public static byte[] setReadOperationFreq(){
@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(){
public static byte[] setRead590Meters() {
return TS590_READ_METERS.getBytes();
}
public static byte[] setTS590ReadOperationFreq(){
public static byte[] setTS590ReadOperationFreq() {
return TS590_READ_FREQ.getBytes();
}
//2023-08-16 由DS1UFX提交修改基于0.9版),增加(tr)uSDX audio over cat的支持。
public static byte[] setTrUSDXStreaming(boolean on) {
if (on) {
return TRUSDX_STREAMING_ON_SPEAKER_OFF.getBytes();
} else {
return TRUSDX_STREAMING_OFF.getBytes();
}
}
public static byte[] setTrUSDXPTTState(boolean on) {
if (on) {
return ";TX0;".getBytes();
} else {
return ";RX;".getBytes();
}
}
}

Wyświetl plik

@ -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 KenwoodTS570Rig extends BaseRig {
private static final String TAG = "KenwoodTS570Rig";
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.setTS570PTTState(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-570";
}
public KenwoodTS570Rig() {
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);
}
}

Wyświetl plik

@ -0,0 +1,401 @@
/**
* (tr)uSDX, fork from KENWOOD TS590.
* 0.9TrSDXRig
*
* @author Sunguk Lee
* 2023-08-16
*/
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.Ft8Message;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import com.bg7yoz.ft8cn.wave.FT8Resample;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
/**
* (tr)uSDX, fork from KENWOOD TS590.
* 2023-08-16 DS1UFX0.9(tr)uSDX audio over cat
*/
public class TrUSDXRig extends BaseRig {
private static final String TAG = "TrUSDXRig";
private static final int rxSampling = 7812;
private static final int txSampling = 11520;
private final StringBuilder buffer = new StringBuilder();
private final ByteArrayOutputStream rxStreamBuffer = new ByteArrayOutputStream();
private Timer readFreqTimer = new Timer();
private int swr = 0;
private int alc = 0;
private boolean alcMaxAlert = false;
private boolean swrAlert = false;
private boolean rxStreaming = false;
private TimerTask readTask() {
return new TimerTask() {
@Override
public void run() {
try {
if (!isConnected()) {
readFreqTimer.cancel();
readFreqTimer.purge();
readFreqTimer = null;
return;
}
if (isPttOn()) {
clearBufferData();
} else {
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:
if (on) {
rxStreaming = false;
}
getConnector().setPttOn(KenwoodTK90RigConstant.setTrUSDXPTTState(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) {
byte[] remain = data;
String s = new String(data);
while (s.contains(";")) { // ;
// TODO apply effective way
int idx = s.indexOf(";");
byte[] cutted = Arrays.copyOf(remain, idx);
remain = Arrays.copyOfRange(remain, idx + 1, remain.length);
s = new String(remain);
if (rxStreaming) {
onReceivedWaveData(cutted, true);
rxStreaming = false;
} else {
buffer.append(new String(cutted));
//开始分析数据
Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString());
clearBufferData();//清一下缓存
if (yaesu3Command == null) {
continue;
}
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("US")) {
rxStreaming = true;
byte[] wave = Arrays.copyOfRange(cutted, 2, cutted.length);
onReceivedWaveData(wave);
}
}
}
if (remain.length <= 0) {
return;
}
if (rxStreaming) {
onReceivedWaveData(remain);
} else if (remain.length >= 2 && remain[0] == 0x55 && remain[1] == 0x53) {// US
clearBufferData();
rxStreaming = true;
byte[] wave = Arrays.copyOfRange(remain, 2, remain.length);
onReceivedWaveData(wave);
} else {
buffer.append(s);
}
}
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();//清空一下缓存
// force reset
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXPTTState(false));
getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq());
}
}
@Override
public String getName() {
return "(tr)uSDX";
}
@Override
public boolean supportWaveOverCAT() {
return true;
}
@Override
public void onDisconnecting() {
if (getConnector() != null) {
clearBufferData();
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXStreaming(false));
}
}
/**
* 7812Hz12000HzConnector
*
* @param data 7812Hz
*/
public void onReceivedWaveData(byte[] data) {
onReceivedWaveData(data, false);
}
/**
* 7812Hz12000HzConnector
*
* @param data 7812Hz
* @param force
*/
public void onReceivedWaveData(byte[] data, boolean force) {
if (data.length == 0) {
return;
}
if (getConnector() == null) {
return;
}
//Resample rxResample = new Resample(Resample.ConverterType.SRC_LINEAR, 1
// , rxSampling, 12000);
rxStreamBuffer.write(data, 0, data.length);
if (rxStreamBuffer.size() >= 256 || force) {//8位转16位7812Hz转12000Hz
//byte[] resampled = rxResample.processCopy(toWaveSamples8To16(rxStreamBuffer.toByteArray()));
float[] resampled = FT8Resample.get32Resample16(toWaveSamples8To16Int(rxStreamBuffer.toByteArray()), rxSampling, 12000);
rxStreamBuffer.reset();
getConnector().receiveWaveData(resampled);
}
//rxResample.close();
}
@Override
public void sendWaveData(Ft8Message message) {
if (getConnector() == null) {
return;
}
float[] wave = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
, 24000);
if (wave == null) {
setPTT(false);
return;
}
//调整信号强度
for (int i = 0; i < wave.length; i++) {
wave[i]=wave[i]*GeneralVariables.volumePercent;
}
//
// byte[] pcm16 = toWaveFloatToPCM16(wave);
// Resample txResample = new Resample(Resample.ConverterType.SRC_SINC_FASTEST, 1
// , 24000, txSampling);
// byte[] resampled = txResample.processCopy(pcm16);
// txResample.close();
// byte[] pcm8 = toWaveSamples16To8(resampled);
byte[] pcm8 = FT8Resample.get8Resample32(wave, 24000, txSampling);
for (int i = 0; i < pcm8.length; i++) {
if (pcm8[i] == 0x3B) pcm8[i] = 0x3A; // ; to :
}
while (pcm8.length > 0) {
if (pcm8.length <= 256) {
getConnector().sendData(pcm8);
break;
} else {
getConnector().sendData(Arrays.copyOfRange(pcm8, 0, 256));
pcm8 = Arrays.copyOfRange(pcm8, 256, pcm8.length);
}
}
}
/**
* 8bit16bit
*
* @param in 8 bit
* @return 16 bitbyte
*/
private static byte[] toWaveSamples8To16(byte[] in) {
ByteBuffer buf = ByteBuffer.allocate(in.length * 2);
for (int i = 0; i < in.length; i++) {
short v = (short) (((short) in[i] - 128) << 8);
buf.putShort(v);
}
return buf.array();
}
/**
* 8bitcaiyang16bit
*
* @param in 8 bit
* @return 16 bit short
*/
private static short[] toWaveSamples8To16Int(byte[] in) {
short[] buf = new short[in.length];
for (int i = 0; i < in.length; i++) {
buf[i] = (short) (((short) in[i] - 128) << 8);
}
return buf;
}
private static byte[] toWaveFloatToPCM16(float[] in) {
ByteBuffer buf = ByteBuffer.allocate(in.length * 2);
buf.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < in.length; i++) {
float x = in[i];
short v = (short) (x * 32767.0f);
buf.putShort(v);
}
return buf.array();
}
private static byte[] toWaveFloatToPCM8(float[] in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
float x = in[i];
short v = (short) (x * 32767.0f);
out[i] = (byte) ((byte) (v >> 8) + 128);
}
return out;
}
/**
* 16 bit 8 bit
*
* @param in 16 bit
* @return 8 bit
*/
private static byte[] toWaveSamples16To8(byte[] in) {
byte[] out = new byte[in.length / 2];
for (int i = 0; i < out.length; i++) {
short v = readShortBigEndianData(in, i * 2);
out[i] = (byte) (((byte) (v >> 8)) + 128);
}
return out;
}
private static byte[] toWaveSamples16To8(short[] in) {
byte[] out = new byte[in.length];
for (int i = 0; i < out.length; i++) {
out[i] = (byte) (((byte) (in[i] >> 8)) + 128);
}
return out;
}
public TrUSDXRig() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (getConnector() != null) {
getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode());
//改成设置usb模式
getConnector().sendData(KenwoodTK90RigConstant.setTS590OperationUSBMode());
getConnector().sendData(KenwoodTK90RigConstant.setTrUSDXStreaming(true));
}
}
}, START_QUERY_FREQ_DELAY - 500);
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT);
}
/**
* 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);
}
}

Wyświetl plik

@ -0,0 +1,248 @@
package com.bg7yoz.ft8cn.rigs;
import android.util.Log;
/**
* X6100_V1.1.6
*
*
* 1. CI-V:
* 1A 01 (C1) (C2)
* C1: , See IC-705 CI-V Command Table
* C2: (), See IC-705 CI-V Command Table
* X6100
* FE FE # 2 byte, CI-V header
* E0 XX 1A 01 01 01 # 6 bytes, The command payload, XX is the rig's address
* 00 00 80 01 00 # 5 bytes, Operating frequency setting
* 03 02 # 2 bytes, Operating mode setting
* 00 # 1 byte, Data mode setting
* 00 # 1 byte, Duplex and Tone settings
* 00 # 1 byte, Digital squelch setting
* 00 08 85 # 3 bytes, Repeater tone frequency setting
* 00 08 85 # 3 bytes, Repeater tone frequency setting
* 00 00 23 # 3 bytes, DTCS code setting
* 00 # 1 byte, DV Digital code squelch setting
* 00 50 00 # 3 bytes, Duplex offset frequency setting
* 58 36 31 30 30 20 20 20 # 8 bytes, UR (Destination) call sign setting
* 20 20 20 20 20 20 20 20 # 8 bytes, R1 (Access repeater) call sign setting
* 20 20 20 20 20 20 20 20 # 8 bytes, R2 (Gateway/Link repeater) call sign setting
* FD # 1 byte, CI-V tail
* 2. CI-V:
* 1A 06
* See IC-705 CI-V Command Table
* 3. CI-V:
* 21 00
* 21 01
* 21 02
* See IC-705 CI-V Command Table
* 4. CI-V:
* 26 (C1) (C2) (C3) (C4)
* C1: VFO (VFO index)
* 0: Foreground VFO
* other: Background VFO
* C2: (Operating mode)
* See IC-705 CI-V Command Table
* C3: (Data mode)
* 0: OFF
* other: ON
* C4: (Filter setting)
* 1: FILTER1
* 2: FILTER2
* 3: FILTER3
* other: Invalid
* *Note: [LSB/USB mode] with Data mode ON -> L-DIG/U-DIG
* [Other operating mode] with Data mode ON -> No effect
* 5. SPP,使FLRIG,Omni-RigPC线X6100
* ,Win+R,bthprops.cpl,"更多蓝牙设置",
* "蓝牙设置""COM端口","X6100 Bluetooth 'Serial Port'"CI-V,
* :
*
* COM3 X6100 Bluetooth 'Serial Port'
*/
public class XieGu6100Command {
private static final String TAG = "6100RigCommand";
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];
}
/**
* 21
* @return
*/
public int getSubCommand2() {//获取子命令
if (rawData.length < 8) {
return -1;
}
return readShortData(rawData,6);
}
/**
* 31
* @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 E000
* @param rigAddr 705A4
* @param buffer
* @return null
*/
public static XieGu6100Command 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
) {//协谷CIV默认地址是0x70,但是在测试1.1.7固件的时候发现回复频率地址始终是0xA4这似乎是个BUG暂时忽略CIV地址的判断
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;
}
XieGu6100Command icomCommand = new XieGu6100Command();
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);
}
}

Wyświetl plik

@ -5,16 +5,19 @@ import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY;
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.ui.ToastMessage;
import java.util.Timer;
import java.util.TimerTask;
public class XieGu6100Rig extends BaseRig {
private static final String TAG = "IcomRig";
private static final String TAG = "x6100Rig";
private final int ctrAddress = 0xE0;//接收地址默认0xE0;电台回复命令有时也可以是0x00
private byte[] dataBuffer = new byte[0];//数据缓冲区
@ -52,6 +55,12 @@ public class XieGu6100Rig extends BaseRig {
super.setPTT(on);
if (getConnector() != null) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
getConnector().setPttOn(on);
return;
}
switch (getControlMode()) {
case ControlMode.CAT://以CIV指令
getConnector().setPttOn(IcomRigConstant.setPTTState(ctrAddress, getCivAddress()
@ -127,24 +136,24 @@ public class XieGu6100Rig extends BaseRig {
if (headIndex == -1) {//说明没有指令头
return;
}
IcomCommand icomCommand;
XieGu6100Command xieGu6100Command;
if (headIndex == 0) {
icomCommand = IcomCommand.getCommand(ctrAddress, getCivAddress(), data);
xieGu6100Command = XieGu6100Command.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);
xieGu6100Command = XieGu6100Command.getCommand(ctrAddress, getCivAddress(), temp);
}
if (icomCommand == null) {
if (xieGu6100Command == null) {
return;
}
//目前只对频率和模式消息作反应
switch (icomCommand.getCommandID()) {
switch (xieGu6100Command.getCommandID()) {
case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据
case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY:
//获取频率
long freqTemp = icomCommand.getFrequency(false);
long freqTemp = xieGu6100Command.getFrequency(false);
if (freqTemp >= 500000 && freqTemp <= 250000000) {//协谷的频率范围
setFreq(freqTemp);
}
@ -153,9 +162,9 @@ public class XieGu6100Rig extends BaseRig {
case IcomRigConstant.CMD_READ_OPERATING_MODE:
break;
case IcomRigConstant.CMD_READ_METER://读meter//此处的指令,只在网络模式实现,以后可能会在串口方面实现
if (icomCommand.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) {
if (xieGu6100Command.getSubCommand() == IcomRigConstant.CMD_READ_METER_SWR) {
//协谷的小端模式
int temp=IcomRigConstant.twoByteBcdToIntBigEnd(icomCommand.getData(true));
int temp=IcomRigConstant.twoByteBcdToIntBigEnd(xieGu6100Command.getData(true));
if (temp!=255) {
swr = temp;//
}
@ -209,10 +218,24 @@ public class XieGu6100Rig extends BaseRig {
}
@Override
public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式
if (getConnector() != null) {//把生成的具体音频数据传递到Connector
float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
,12000);//此处icom电台发射音频的采样率是12000
if (data==null){
setPTT(false);
return;
}
getConnector().sendWaveData(data);
}
}
@Override
public void readFreqFromRig() {
if (getConnector() != null) {
getConnector().sendData(IcomRigConstant.setReadFreq(ctrAddress, getCivAddress()));
//getConnector().sendData(IcomRigConstant.setReadFreq(getCivAddress(), getCivAddress()));
}
}
@ -237,6 +260,5 @@ public class XieGu6100Rig extends BaseRig {
setCivAddress(civAddress);
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT);
//readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,1000);
}
}

Wyświetl plik

@ -5,16 +5,18 @@ import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY;
import android.util.Log;
import com.bg7yoz.ft8cn.Ft8Message;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8;
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 static final String TAG = "XieGu6100Rig";
private final int ctrAddress = 0xE0;//接收地址默认0xE0;电台回复命令有时也可以是0x00
private byte[] dataBuffer = new byte[0];//数据缓冲区
@ -222,7 +224,20 @@ public class XieGuRig extends BaseRig {
@Override
public String getName() {
return "XIEGU series";
return "XIEGU 6100 series";
}
@Override
public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式
if (getConnector() != null) {//把生成的具体音频数据传递到Connector
float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
,12000);//此处icom电台发射音频的采样率是12000
if (data==null){
setPTT(false);
return;
}
getConnector().sendWaveData(data);
}
}
@ -231,7 +246,7 @@ public class XieGuRig extends BaseRig {
}
public XieGuRig(int civAddress) {
Log.d(TAG, "XieGuRig: Create.");
Log.d(TAG, "XieGuRig 6100: Create.");
setCivAddress(civAddress);
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT);

Wyświetl plik

@ -24,6 +24,8 @@ public class Yaesu39Rig extends BaseRig {
private boolean alcMaxAlert = false;
private boolean swrAlert = false;
private boolean isDataUsb=false;//是不是DATA-USB模式
private Timer readFreqTimer = new Timer();
private TimerTask readTask() {
@ -112,7 +114,11 @@ public class Yaesu39Rig extends BaseRig {
@Override
public void setUsbModeToRig() {
if (getConnector() != null) {
getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode());
if (isDataUsb) {//使用DATA-USB模式
getConnector().sendData(Yaesu3RigConstant.setOperationUSB_Data_Mode());
}else {
getConnector().sendData(Yaesu3RigConstant.setOperationUSBMode());
}
}
}
@ -178,7 +184,8 @@ public class Yaesu39Rig extends BaseRig {
return "YAESU FT-891";
}
public Yaesu39Rig() {
public Yaesu39Rig(boolean isDataUsb) {
this.isDataUsb=isDataUsb;
readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY, QUERY_FREQ_TIMEOUT);
}
}

Wyświetl plik

@ -38,7 +38,6 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
private final ArrayList<Ft8Message> ft8MessageArrayList;
private final Context context;
//private boolean isCallingList = true;
private final ShowMode showMode;
private View.OnClickListener onItemClickListener;
@ -83,6 +82,11 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
, ft8Message.getCallsignTo())).setActionView(view);
}
//增加查询日志
contextMenu.add(0, 7, 0
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_query_log_menu)
, ft8Message.getCallsignTo())).setActionView(view);
}
}
@ -104,6 +108,11 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s)
, ft8Message.getCallsignFrom())).setActionView(view);
}
//增加查询日志
contextMenu.add(0, 8, 0
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_query_log_menu)
, ft8Message.getCallsignFrom())).setActionView(view);
}
}
@ -115,7 +124,6 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
, ArrayList<Ft8Message> messages, ShowMode showMode) {
this.mainViewModel = mainViewModel;
this.context = context;
//this.isCallingList = isCallingList;
this.showMode=showMode;
ft8MessageArrayList = messages;
}
@ -124,7 +132,12 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
@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);
View view ;
if (GeneralVariables.simpleCallItemMode) {
view = layoutInflater.inflate(R.layout.call_list_holder_simple_item, parent, false);
}else {
view = layoutInflater.inflate(R.layout.call_list_holder_item, parent, false);
}
return new CallingListItemHolder(view,onItemClickListener,menuListener);
}
@ -275,8 +288,7 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
// setQueryHolderCallsign(holder);//查询呼号归属地
// }
if (holder.ft8Message.freq_hz <= 0.01f) {//这是发射
//holder.callingListSequenceTextView.setVisibility(View.GONE);
if (holder.ft8Message.freq_hz <= 0.01f) {//这是发射界面
holder.callingListIdBTextView.setVisibility(View.GONE);
holder.callListDtTextView.setVisibility(View.GONE);
holder.callingListFreqTextView.setText("TX");
@ -292,8 +304,16 @@ public class CallingListAdapter extends RecyclerView.Adapter<CallingListAdapter.
holder.dxccFromImageView.setVisibility(View.GONE);
holder.ituFromImageView.setVisibility(View.GONE);
holder.cqFromImageView.setVisibility(View.GONE);
} else {
//holder.callingListSequenceTextView.setVisibility(View.VISIBLE);
} else if (GeneralVariables.simpleCallItemMode){//简单列表模式
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.dxccToImageView.setVisibility(View.GONE);
holder.ituToImageView.setVisibility(View.GONE);
holder.cqToImageView.setVisibility(View.GONE);
}else {//标准列表模式
holder.callingListIdBTextView.setVisibility(View.VISIBLE);
holder.callListDtTextView.setVisibility(View.VISIBLE);
holder.bandItemTextView.setVisibility(View.VISIBLE);

Wyświetl plik

@ -190,6 +190,22 @@ public class CallingListFragment extends Fragment {
}
});
//切换精简模式和标准模式
binding.callingListToolsBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GeneralVariables.simpleCallItemMode=!GeneralVariables.simpleCallItemMode;
callListRecyclerView.setAdapter(callingListAdapter);
callingListAdapter.notifyDataSetChanged();
callListRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1);
if (GeneralVariables.simpleCallItemMode){
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.message_list_simple_mode));
}else {
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.message_list_standard_mode));
}
}
});
return binding.getRoot();
}
@ -309,6 +325,17 @@ public class CallingListFragment extends Fragment {
return true;
}
/**
*
* @param callsign
*/
private void navigateToLogFragment(String callsign){
mainViewModel.queryKey=callsign;//把呼号作为关键字提交
NavController navController = Navigation.findNavController(requireActivity()
, R.id.fragmentContainerView);
navController.navigate(R.id.action_menu_nav_calling_list_to_menu_nav_history);//跳转到日志
}
/**
*
*/
@ -384,6 +411,12 @@ public class CallingListFragment extends Fragment {
case 6://from 的QRZ
showQrzFragment(ft8Message.getCallsignFrom());
break;
case 7://查to的日志
navigateToLogFragment(ft8Message.getCallsignTo());
break;
case 8://查from的日志
navigateToLogFragment(ft8Message.getCallsignFrom());
break;
}

Wyświetl plik

@ -289,6 +289,9 @@ public class ConfigFragment extends Fragment {
//设置音频输出采样率
setAudioOutputRateMode();
//设置显示消息模式
setMessageMode();
//设置控制模式 VOX CAT
setControlMode();
@ -845,6 +848,38 @@ public class ConfigFragment extends Fragment {
/**
*
*/
private void setMessageMode() {
binding.messageModeRadioGroup.clearCheck();
if (GeneralVariables.simpleCallItemMode){
binding.msgSimpleRadioButton.setChecked(true);
}else {
binding.msgStandardRadioButton.setChecked(true);
}
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View view) {
int buttonId = binding.messageModeRadioGroup.getCheckedRadioButtonId();
GeneralVariables.simpleCallItemMode=
binding.messageModeRadioGroup.getCheckedRadioButtonId()
==binding.msgSimpleRadioButton.getId();
writeConfig("msgMode", GeneralVariables.simpleCallItemMode?"1":"0");
}
};
binding.msgStandardRadioButton.setOnClickListener(listener);
binding.msgSimpleRadioButton.setOnClickListener(listener);
}
/**
* VOX CAT
*/
@ -950,7 +985,9 @@ public class ConfigFragment extends Fragment {
//打开网络电台列表对话框
if (GeneralVariables.instructionSet== InstructionSet.FLEX_NETWORK) {
new SelectFlexRadioDialog(requireContext(), mainViewModel).show();
}else if(GeneralVariables.instructionSet== InstructionSet.ICOM) {
}else if(GeneralVariables.instructionSet== InstructionSet.ICOM
||GeneralVariables.instructionSet== InstructionSet.XIEGU_6100
||GeneralVariables.instructionSet== InstructionSet.XIEGUG90S) {
new LoginIcomRadioDialog(requireContext(), mainViewModel).show();
}else {
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.only_flex_supported));
@ -1030,6 +1067,16 @@ public class ConfigFragment extends Fragment {
, true).show();
}
});
//显示列表方式
binding.messageModeeHelpImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new HelpDialog(requireContext(),requireActivity()
,GeneralVariables.getStringFromResource(R.string.message_mode_help)
,true).show();
}
});
//设置ABOUT
binding.aboutButton.setOnClickListener(new View.OnClickListener() {
@Override

Wyświetl plik

@ -1,6 +1,7 @@
package com.bg7yoz.ft8cn.ui;
/**
* ICOM
*
* @author BGY70Z
* @date 2023-03-20
*/
@ -25,6 +26,8 @@ import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.MainViewModel;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.icom.IComWifiRig;
import com.bg7yoz.ft8cn.icom.XieGuWifiRig;
import com.bg7yoz.ft8cn.rigs.InstructionSet;
public class LoginIcomRadioDialog extends Dialog {
private static final String TAG = "LoginIcomRadioDialog";
@ -34,7 +37,7 @@ public class LoginIcomRadioDialog extends Dialog {
private EditText inputIcomUserNameEdit;
private EditText inputIcomPasswordEdit;
private Button icomLoginButton;
private boolean passVisible=false;
private boolean passVisible = false;
public LoginIcomRadioDialog(@NonNull Context context, MainViewModel mainViewModel) {
@ -62,16 +65,25 @@ public class LoginIcomRadioDialog extends Dialog {
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));
if (GeneralVariables.instructionSet == InstructionSet.ICOM) {//icom 电台
ToastMessage.show(String.format(
GeneralVariables.getStringFromResource(R.string.connect_icom_ip)
, inputIcomAddressEdit.getText()));
mainViewModel.connectWifiRig(new IComWifiRig(GeneralVariables.icomIp
, GeneralVariables.icomUdpPort
, GeneralVariables.icomUserName
, GeneralVariables.icomPassword));
} else if (GeneralVariables.instructionSet == InstructionSet.XIEGU_6100) {//协谷x6100
ToastMessage.show(String.format(
GeneralVariables.getStringFromResource(R.string.connect_xiegu_ip)
, inputIcomAddressEdit.getText()));
mainViewModel.connectWifiRig(new XieGuWifiRig(GeneralVariables.icomIp
, GeneralVariables.icomUdpPort
, GeneralVariables.icomUserName
, GeneralVariables.icomPassword));
}
dismiss();
}
});
@ -91,7 +103,7 @@ public class LoginIcomRadioDialog extends Dialog {
@Override
public void afterTextChanged(Editable editable) {
checkInput();
GeneralVariables.icomIp=inputIcomAddressEdit.getText().toString().trim();
GeneralVariables.icomIp = inputIcomAddressEdit.getText().toString().trim();
writeConfig("icomIp", inputIcomAddressEdit.getText().toString().trim());
}
});
@ -112,7 +124,7 @@ public class LoginIcomRadioDialog extends Dialog {
checkInput();
if (GeneralVariables.isInteger(inputIcomPortEdit.getText().toString().trim())) {
writeConfig("icomPort", inputIcomPortEdit.getText().toString().trim());
GeneralVariables.icomUdpPort=Integer.parseInt(inputIcomPortEdit.getText().toString().trim());
GeneralVariables.icomUdpPort = Integer.parseInt(inputIcomPortEdit.getText().toString().trim());
}
}
});
@ -131,7 +143,7 @@ public class LoginIcomRadioDialog extends Dialog {
public void afterTextChanged(Editable editable) {
checkInput();
writeConfig("icomUserName", inputIcomUserNameEdit.getText().toString().trim());
GeneralVariables.icomUserName=inputIcomUserNameEdit.getText().toString().trim();
GeneralVariables.icomUserName = inputIcomUserNameEdit.getText().toString().trim();
}
});
inputIcomPasswordEdit.addTextChangedListener(new TextWatcher() {
@ -149,18 +161,18 @@ public class LoginIcomRadioDialog extends Dialog {
public void afterTextChanged(Editable editable) {
checkInput();
writeConfig("icomPassword", inputIcomPasswordEdit.getText().toString());
GeneralVariables.icomPassword=inputIcomPasswordEdit.getText().toString();
GeneralVariables.icomPassword = inputIcomPasswordEdit.getText().toString();
}
});
showPassImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
passVisible=!passVisible;
passVisible = !passVisible;
if (passVisible) {
inputIcomPasswordEdit.setTransformationMethod(
HideReturnsTransformationMethod.getInstance());
}else {
} else {
inputIcomPasswordEdit.setTransformationMethod(
PasswordTransformationMethod.getInstance());
}

Wyświetl plik

@ -26,6 +26,8 @@ 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;
@ -139,12 +141,28 @@ public class MyCallingFragment extends Fragment {
case 6://from 的QRZ
showQrzFragment(ft8Message.getCallsignFrom());
break;
case 7://查to的日志
navigateToLogFragment(ft8Message.getCallsignTo());
break;
case 8://查from的日志
navigateToLogFragment(ft8Message.getCallsignFrom());
break;
}
return super.onContextItemSelected(item);
}
/**
*
* @param callsign
*/
private void navigateToLogFragment(String callsign){
mainViewModel.queryKey=callsign;//把呼号作为关键字提交
NavController navController = Navigation.findNavController(requireActivity()
, R.id.fragmentContainerView);
navController.navigate(R.id.action_menu_nav_mycalling_to_menu_nav_history);//跳转到日志
}
/**
* QRZ
*
@ -398,6 +416,22 @@ public class MyCallingFragment extends Fragment {
}
});
binding.mycallToolsBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GeneralVariables.simpleCallItemMode=!GeneralVariables.simpleCallItemMode;
transmitRecycleView.setAdapter(transmitCallListAdapter);
transmitCallListAdapter.notifyDataSetChanged();
transmitRecycleView.scrollToPosition(transmitCallListAdapter.getItemCount() - 1);
if (GeneralVariables.simpleCallItemMode){
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.message_list_simple_mode));
}else {
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.message_list_standard_mode));
}
}
});
showFreeTextEdit();
return binding.getRoot();
}

Wyświetl plik

@ -0,0 +1,125 @@
package com.bg7yoz.ft8cn.ui;
/**
* RadioGroup
* RadioGroupRadioButton
* @author BGY70Z
* @date 2023-08-30
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RadioGroup;
public class RadioGroupFt8cn extends RadioGroup {
public RadioGroupFt8cn(Context context) {
super(context);
}
public RadioGroupFt8cn(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//调用ViewGroup的方法测量子view
measureChildren(widthMeasureSpec, heightMeasureSpec);
//最大的宽
int maxWidth = 0;
//累计的高
int totalHeight = 0;
//当前这一行的累计行宽
int lineWidth = 0;
//当前这行的最大行高
int maxLineHeight = 0;
//用于记录换行前的行宽和行高
int oldHeight;
int oldWidth;
int count = getChildCount();
//假设 widthMode和heightMode都是AT_MOST
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
//得到这一行的最高
oldHeight = maxLineHeight;
//当前最大宽度
oldWidth = maxWidth;
int deltaX = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
if (lineWidth + deltaX + getPaddingLeft() + getPaddingRight() > widthSize) {//如果折行,height增加
//和目前最大的宽度比较,得到最宽。不能加上当前的child的宽,所以用的是oldWidth
maxWidth = Math.max(lineWidth, oldWidth);
//重置宽度
lineWidth = deltaX;
//累加高度
totalHeight += oldHeight;
//重置行高,当前这个View属于下一行因此当前最大行高为这个child的高度加上margin
maxLineHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
} else {
//不换行,累加宽度
lineWidth += deltaX;
//不换行,计算行最高
int deltaY = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
maxLineHeight = Math.max(maxLineHeight, deltaY);
}
if (i == count - 1) {
//前面没有加上下一行的搞,如果是最后一行,还要再叠加上最后一行的最高的值
totalHeight += maxLineHeight;
//计算最后一行和前面的最宽的一行比较
maxWidth = Math.max(lineWidth, oldWidth);
}
}
//加上当前容器的padding值
maxWidth += getPaddingLeft() + getPaddingRight();
totalHeight += getPaddingTop() + getPaddingBottom();
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : maxWidth,
heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//pre为前面所有的child的相加后的位置
int preLeft = getPaddingLeft();
int preTop = getPaddingTop();
//记录每一行的最高值
int maxHeight = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
//r-l为当前容器的宽度。如果子view的累积宽度大于容器宽度就换行。
if (preLeft + params.leftMargin + child.getMeasuredWidth() + params.rightMargin + getPaddingRight() > (r - l)) {
//重置
preLeft = getPaddingLeft();
//要选择child的height最大的作为设置
preTop = preTop + maxHeight;
maxHeight = getChildAt(i).getMeasuredHeight() + params.topMargin + params.bottomMargin;
} else { //不换行,计算最大高度
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + params.topMargin + params.bottomMargin);
}
//left坐标
int left = preLeft + params.leftMargin;
//top坐标
int top = preTop + params.topMargin;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
//为子view布局
child.layout(left, top, right, bottom);
//计算布局结束后preLeft的值
preLeft += params.leftMargin + child.getMeasuredWidth() + params.rightMargin;
}
}
}

Wyświetl plik

@ -187,6 +187,8 @@ public class WaterfallView extends View {
public void setWaveData(int[] data, int sequential, List<Ft8Message> msgs) {
if (drawMessage&& msgs!=null){//把需要画的消息复制出来防止多线程访问冲突
messages=new ArrayList<>(msgs);
}else {
messages.clear();//当设定不标记消息时,要清空原来的消息
}
if (data == null) {

Wyświetl plik

@ -0,0 +1,29 @@
package com.bg7yoz.ft8cn.wave;
/**
*
* @author bg7yoz
* @date 2023-09-09
*/
public class FT8Resample {
static {
System.loadLibrary("ft8cn");
}
public static native short[] get16Resample16(short[] inputData, int inputRate
, int outputRate);
public static native float[] get32Resample16(short[] inputData, int inputRate
, int outputRate);
public static native short[] get16Resample32(float[] inputData, int inputRate
, int outputRate);
public static native float[] get32Resample32(float[] inputData, int inputRate
, int outputRate);
public static native byte[] get8Resample16(short[] inputData, int inputRate
, int outputRate);
public static native byte[] get8Resample32(float[] inputData, int inputRate
, int outputRate);
}

Wyświetl plik

@ -147,7 +147,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="110dp" />
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/textView4"

Wyświetl plik

@ -215,7 +215,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="110dp" />
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/textView4"

Wyświetl plik

@ -63,7 +63,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:textSize="11sp"
android:textSize="9sp"
tools:ignore="SmallSp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
@ -74,7 +75,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:textSize="10sp"
android:textSize="9sp"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/linearLayout"
@ -86,7 +87,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="110dp" />
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/callingListSequenceTextView"
@ -97,6 +98,7 @@
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:ignore="TextContrastCheck"
tools:text="0" />
<androidx.constraintlayout.widget.Guideline
@ -250,9 +252,10 @@
android:id="@+id/isWeakSignalImageView"
android:layout_width="10dp"
android:layout_height="10dp"
app:layout_constraintBottom_toBottomOf="@+id/callingListSequenceTextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/callingListSequenceTextView"
app:srcCompat="@drawable/is_weak_signal_24" />
app:srcCompat="@drawable/is_weak_signal_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/callListHolderConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="1dp"
android:layout_marginTop="1dp"
android:layout_marginRight="1dp"
android:layout_marginBottom="1dp"
android:background="@drawable/calling_list_cell_style"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
tools:ignore="TouchTargetSizeCheck">
<TextView
android:id="@+id/callListMessageTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="parent"
tools:text="CQ BG7YOY BG7YOZ" />
<TextView
android:id="@+id/callingListIdBTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toStartOf="@+id/guideline9"
app:layout_constraintStart_toStartOf="@+id/guideline9"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:text="-12" />
<TextView
android:id="@+id/callListDtTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:text="-1.2" />
<TextView
android:id="@+id/callingListFreqTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toStartOf="@+id/guideline7"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:text="1234" />
<TextView
android:id="@+id/bandItemTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:textSize="9sp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:ignore="SmallSp"
tools:text="14.074Hz"
tools:visibility="gone" />
<TextView
android:id="@+id/callingListDistTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:textSize="9sp"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/linearLayout"
tools:ignore="SmallSp"
tools:text="12345公里"
tools:visibility="gone" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/callingListSequenceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView"
tools:ignore="TextContrastCheck"
tools:text="0" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="55dp" />
<TextView
android:id="@+id/callingUtcTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/linearLayout"
tools:ignore="SmallSp"
tools:text="123456"
tools:visibility="gone" />
<TextView
android:id="@+id/callingListCommandIInfoTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:textSize="9sp"
app:layout_constraintBottom_toBottomOf="@+id/callingUtcTextView"
app:layout_constraintEnd_toStartOf="@+id/guideline2"
app:layout_constraintStart_toEndOf="@+id/callingUtcTextView"
app:layout_constraintTop_toTopOf="@+id/callingUtcTextView"
tools:ignore="SmallSp"
tools:text="4.0:非标准消息"
tools:visibility="gone" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="70dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="28dp" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@+id/cqFromImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/callListMessageTextView">
<TextView
android:id="@+id/callToItemTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:textAlignment="textStart"
android:textSize="10sp"
tools:ignore="RtlCompat,SmallSp"
tools:text="TOOOOO"
tools:visibility="gone" />
<TextView
android:id="@+id/callingListCallsignFromTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:textAlignment="textStart"
android:textSize="10sp"
android:textStyle="normal"
tools:ignore="RtlCompat,SmallSp"
tools:text="FROMMMMM" />
</LinearLayout>
<ImageView
android:id="@+id/dxccToImageView"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="1dp"
android:contentDescription="@string/dxcc_image"
app:layout_constraintBottom_toBottomOf="@+id/callingListCommandIInfoTextView"
app:layout_constraintStart_toStartOf="@+id/guideline2"
app:layout_constraintTop_toTopOf="@+id/callingListCommandIInfoTextView"
app:srcCompat="@drawable/dxcc"
tools:visibility="gone" />
<ImageView
android:id="@+id/ituToImageView"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="2dp"
android:contentDescription="@string/count_dxcc_proportion"
app:layout_constraintBottom_toBottomOf="@+id/dxccToImageView"
app:layout_constraintStart_toEndOf="@+id/dxccToImageView"
app:layout_constraintTop_toTopOf="@+id/dxccToImageView"
app:srcCompat="@drawable/itu"
tools:visibility="gone" />
<ImageView
android:id="@+id/cqToImageView"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="2dp"
android:contentDescription="@string/cqzone"
app:layout_constraintBottom_toBottomOf="@+id/ituToImageView"
app:layout_constraintStart_toEndOf="@+id/ituToImageView"
app:layout_constraintTop_toTopOf="@+id/ituToImageView"
app:srcCompat="@drawable/cqzone"
tools:visibility="gone" />
<ImageView
android:id="@+id/cqFromImageView"
android:layout_width="9dp"
android:layout_height="9dp"
android:contentDescription="@string/cqzone"
app:layout_constraintBottom_toBottomOf="@+id/callListMessageTextView"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/cqzone" />
<ImageView
android:id="@+id/ituFromImageView"
android:layout_width="9dp"
android:layout_height="9dp"
android:layout_marginEnd="2dp"
android:contentDescription="@string/itu_image"
app:layout_constraintBottom_toBottomOf="@+id/cqFromImageView"
app:layout_constraintEnd_toStartOf="@+id/cqFromImageView"
app:layout_constraintTop_toTopOf="@+id/cqFromImageView"
app:srcCompat="@drawable/itu" />
<ImageView
android:id="@+id/dxccFromImageView"
android:layout_width="9dp"
android:layout_height="9dp"
android:layout_marginEnd="2dp"
android:contentDescription="@string/dxcc_image"
app:layout_constraintBottom_toBottomOf="@+id/ituFromImageView"
app:layout_constraintEnd_toStartOf="@+id/ituFromImageView"
app:layout_constraintTop_toTopOf="@+id/ituFromImageView"
app:srcCompat="@drawable/dxcc" />
<ImageView
android:id="@+id/isWeakSignalImageView"
android:layout_width="10dp"
android:layout_height="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/callingListSequenceTextView"
app:srcCompat="@drawable/is_weak_signal_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -30,7 +30,6 @@
android:id="@+id/timerTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:textColor="@color/bar_text_view_color"
android:textSize="12sp"
@ -150,7 +149,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="110dp" />
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/textView4"

Wyświetl plik

@ -368,7 +368,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RadioGroup
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/audioBitsRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -406,9 +406,9 @@
app:layout_constraintBottom_toBottomOf="@+id/audio16BitsRadioButton"
app:layout_constraintStart_toEndOf="@+id/audio16BitsRadioButton"
app:layout_constraintTop_toTopOf="@+id/audio16BitsRadioButton" />
</RadioGroup>
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>
<RadioGroup
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/audioRateRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -464,7 +464,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/audioOutputText"
app:layout_constraintTop_toTopOf="parent" />
</RadioGroup>
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>
<ImageButton
android:id="@+id/audioOutputImageButton"
@ -723,6 +723,87 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/messageModeLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:background="@drawable/editor_layout_style"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/operatonBandLayout">
<TextView
android:id="@+id/messageModeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/config_msg_mode"
android:textColor="@color/text_view_color"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/messageModeeHelpImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@drawable/imagebutton_transparent_style"
android:contentDescription="@string/help"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_baseline_info_32"
tools:ignore="TouchTargetSizeCheck" />
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/messageModeRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/messageModeeHelpImageButton"
app:layout_constraintStart_toEndOf="@+id/messageModeText"
app:layout_constraintTop_toTopOf="parent">
<RadioButton
android:id="@+id/msgStandardRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="?attr/colorPrimary"
android:text="@string/config_msg_std_mode"
android:textColor="@color/text_view_color"
android:textSize="12sp" />
<RadioButton
android:id="@+id/msgSimpleRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="?attr/colorPrimary"
android:text="@string/config_msg_simple_mode"
android:textColor="@color/text_view_color"
android:textSize="12sp" />
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/controlModeLayout"
android:layout_width="0dp"
@ -731,7 +812,7 @@
android:background="@drawable/editor_layout_style"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/operatonBandLayout">
app:layout_constraintTop_toBottomOf="@+id/messageModeLayout">
<TextView
android:id="@+id/controlModeDelayText"
@ -759,7 +840,7 @@
app:srcCompat="@drawable/ic_baseline_info_32"
tools:ignore="TouchTargetSizeCheck" />
<RadioGroup
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/controlModeRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -805,7 +886,7 @@
android:text="@string/DTR"
android:textColor="@color/text_view_color"
android:textSize="12sp" />
</RadioGroup>
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>
</androidx.constraintlayout.widget.ConstraintLayout>
@ -846,7 +927,7 @@
app:srcCompat="@drawable/ic_baseline_info_32"
tools:ignore="TouchTargetSizeCheck" />
<RadioGroup
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/connectModeRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -884,7 +965,7 @@
android:textColor="@color/text_view_color"
android:textSize="12sp" />
</RadioGroup>
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>
</androidx.constraintlayout.widget.ConstraintLayout>
@ -1066,7 +1147,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RadioGroup
<com.bg7yoz.ft8cn.ui.RadioGroupFt8cn
android:id="@+id/decodeModeRadioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -1097,7 +1178,7 @@
</RadioGroup>
</com.bg7yoz.ft8cn.ui.RadioGroupFt8cn>

Wyświetl plik

@ -217,7 +217,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="110dp" />
app:layout_constraintGuide_begin="105dp" />
<TextView
android:id="@+id/textView4"

Wyświetl plik

@ -12,12 +12,19 @@
<action
android:id="@+id/action_menu_nav_calling_list_to_menu_nav_mycalling"
app:destination="@id/menu_nav_mycalling" />
<action
android:id="@+id/action_menu_nav_calling_list_to_menu_nav_history"
app:destination="@id/menu_nav_history" />
</fragment>
<fragment
android:id="@+id/menu_nav_mycalling"
android:name="com.bg7yoz.ft8cn.ui.MyCallingFragment"
android:label="@string/nav_menu_title_my_calling"
tools:layout="@layout/fragment_my_calling" />
tools:layout="@layout/fragment_my_calling" >
<action
android:id="@+id/action_menu_nav_mycalling_to_menu_nav_history"
app:destination="@id/menu_nav_history" />
</fragment>
<fragment
android:id="@+id/menu_nav_config"
android:name="com.bg7yoz.ft8cn.ui.ConfigFragment"

Wyświetl plik

@ -347,6 +347,7 @@
<string name="icom_login_button">Login</string>
<string name="show_password">Password is visibility</string>
<string name="connect_icom_ip">Connect ICom Radio %s ...</string>
<string name="connect_xiegu_ip">Connect XIEGU Radio %s ...</string>
<string name="network_exception">%s : Network transmission exception : %s</string>
<string name="login_succeed">Login succeeded !</string>
<string name="loging_failed">Login failed, invalid user name or password.</string>
@ -483,6 +484,13 @@
<string name="import_readed_html">Validated QSL: %d</string>
<string name="import_canceled_html">Canceled!</string>
<string name="import_cancel_button">Stop</string>
<string name="qsl_query_log_menu">Log (%s)</string>
<string name="message_list_simple_mode">Simple List Mode</string>
<string name="message_list_standard_mode">Standard List Mode</string>
<string name="config_msg_mode">Message mode</string>
<string name="config_msg_std_mode">Standard</string>
<string name="config_msg_simple_mode">Simple</string>
<string name="message_mode_help">messageMode_en.txt</string>
<!-- <string name="hello_blank_fragment">Hello blank fragment</string>-->

Wyświetl plik

@ -347,6 +347,7 @@
<string name="icom_login_button">Login</string>
<string name="show_password">Password is visibility</string>
<string name="connect_icom_ip">Connect ICom Radio %s ...</string>
<string name="connect_xiegu_ip">Connect XIEGU Radio %s ...</string>
<string name="network_exception">%s : Network transmission exception : %s</string>
<string name="login_succeed">Login succeeded !</string>
<string name="loging_failed">Login failed, invalid user name or password.</string>
@ -483,6 +484,13 @@
<string name="import_readed_html">Validated QSL: %d</string>
<string name="import_canceled_html">Canceled!</string>
<string name="import_cancel_button">Stop</string>
<string name="qsl_query_log_menu">Log (%s)</string>
<string name="message_list_simple_mode">Simple List Mode</string>
<string name="message_list_standard_mode">Standard List Mode</string>
<string name="config_msg_mode">Message mode</string>
<string name="config_msg_std_mode">Standard</string>
<string name="config_msg_simple_mode">Simple</string>
<string name="message_mode_help">messageMode_en.txt</string>
<!-- <string name="hello_blank_fragment">Hello blank fragment</string>-->
</resources>

Wyświetl plik

@ -630,6 +630,7 @@
<string name="icom_login_button">ログイン</string>
<string name="show_password">パスワードを表示する</string>
<string name="connect_icom_ip">IComの無線機 %s に接続する</string>
<string name="connect_xiegu_ip">XIEGUの無線機 %s に接続する</string>
<string name="network_exception">%s : ネットワーク送信例外 : %s</string>
<string name="login_succeed">ログイン成功</string>
<string name="loging_failed">ログイン失敗 無効なユーザー名もしくはパスワード</string>
@ -766,6 +767,13 @@
<string name="import_readed_html">確認済みQSL: %d</string>
<string name="import_canceled_html">インポートが取り消されました!</string>
<string name="import_cancel_button">インポートを取り消す</string>
<string name="qsl_query_log_menu">Log (%s)</string>
<string name="message_list_simple_mode">単純リストモード</string>
<string name="message_list_standard_mode">標準リストモード</string>
<string name="config_msg_mode">メッセージモード</string>
<string name="config_msg_std_mode">標準</string>
<string name="config_msg_simple_mode">わかりやすい</string>
<string name="message_mode_help">messageMode_en.txt</string>
</resources>

Wyświetl plik

@ -346,6 +346,7 @@
<string name="icom_login_button">登录</string>
<string name="show_password">显示密码</string>
<string name="connect_icom_ip">连接到ICom电台 %s …</string>
<string name="connect_xiegu_ip">连接到XIEGU电台 %s …</string>
<string name="network_exception">%s网络传输异常%s</string>
<string name="login_succeed">登录成功!</string>
<string name="loging_failed">登录失败,无效的用户名或密码。</string>
@ -482,6 +483,13 @@
<string name="import_readed_html">有效的QSL%d</string>
<string name="import_canceled_html">导入被取消!</string>
<string name="import_cancel_button">停止导入</string>
<string name="qsl_query_log_menu">查日志 (%s)</string>
<string name="message_list_simple_mode">精简列表模式</string>
<string name="message_list_standard_mode">标准列表模式</string>
<string name="config_msg_mode">消息模式</string>
<string name="config_msg_std_mode">标准</string>
<string name="config_msg_simple_mode">精简</string>
<string name="message_mode_help">messageMode.txt</string>
</resources>

Wyświetl plik

@ -346,6 +346,7 @@
<string name="icom_login_button">登入</string>
<string name="show_password">顯示密碼</string>
<string name="connect_icom_ip">連接到ICom電臺 %s …</string>
<string name="connect_xiegu_ip">連接到XIEGU電臺 %s …</string>
<string name="network_exception">%s網路傳輸異常%s</string>
<string name="login_succeed">登錄成功!</string>
<string name="loging_failed">登錄失敗,無效的用戶名或密碼。</string>
@ -482,6 +483,13 @@
<string name="import_readed_html">有效的QSL%d</string>
<string name="import_canceled_html">匯入被取消!</string>
<string name="import_cancel_button">終止匯入</string>
<string name="qsl_query_log_menu">查日誌 (%s)</string>
<string name="message_list_simple_mode">精簡列表模式</string>
<string name="message_list_standard_mode">標准列表模式</string>
<string name="config_msg_mode">消息模式</string>
<string name="config_msg_std_mode">標准</string>
<string name="config_msg_simple_mode">精簡</string>
<string name="message_mode_help">messageMode.txt</string>
</resources>

Wyświetl plik

@ -346,6 +346,7 @@
<string name="icom_login_button">登入</string>
<string name="show_password">顯示密碼</string>
<string name="connect_icom_ip">連接到ICom電臺 %s …</string>
<string name="connect_xiegu_ip">連接到XIEGU電臺 %s …</string>
<string name="network_exception">%s網路傳輸異常%s</string>
<string name="login_succeed">登錄成功!</string>
<string name="loging_failed">登錄失敗,無效的用戶名或密碼。</string>
@ -482,6 +483,13 @@
<string name="import_readed_html">有效的QSL%d</string>
<string name="import_canceled_html">匯入被取消!</string>
<string name="import_cancel_button">終止匯入</string>
<string name="qsl_query_log_menu">查日誌 (%s)</string>
<string name="message_list_simple_mode">精簡列表模式</string>
<string name="message_list_standard_mode">標准列表模式</string>
<string name="config_msg_mode">消息模式</string>
<string name="config_msg_std_mode">標准</string>
<string name="config_msg_simple_mode">精簡</string>
<string name="message_mode_help">messageMode.txt</string>
</resources>

Wyświetl plik

@ -346,6 +346,7 @@
<string name="icom_login_button">登入</string>
<string name="show_password">顯示密碼</string>
<string name="connect_icom_ip">連接到ICom電臺 %s …</string>
<string name="connect_xiegu_ip">連接到XIEGU電臺 %s …</string>
<string name="network_exception">%s網路傳輸異常%s</string>
<string name="login_succeed">登錄成功!</string>
<string name="loging_failed">登錄失敗,無效的用戶名或密碼。</string>
@ -482,6 +483,13 @@
<string name="import_readed_html">有效的QSL%d</string>
<string name="import_canceled_html">匯入被取消!</string>
<string name="import_cancel_button">終止匯入</string>
<string name="qsl_query_log_menu">查日誌 (%s)</string>
<string name="message_list_simple_mode">精簡列表模式</string>
<string name="message_list_standard_mode">標准列表模式</string>
<string name="config_msg_mode">消息模式</string>
<string name="config_msg_std_mode">標准</string>
<string name="config_msg_simple_mode">精簡</string>
<string name="message_mode_help">messageMode.txt</string>
</resources>

Wyświetl plik

@ -350,6 +350,7 @@
<string name="icom_login_button">Login</string>
<string name="show_password">Password is visibility</string>
<string name="connect_icom_ip">Connect ICom Radio %s …</string>
<string name="connect_xiegu_ip">Connect XIEGU Radio %s …</string>
<string name="network_exception">%s : Network transmission exception : %s</string>
<string name="login_succeed">Login succeeded !</string>
<string name="loging_failed">Login failed, invalid user name or password.</string>
@ -486,6 +487,13 @@
<string name="import_readed_html">Validated QSL: %d</string>
<string name="import_canceled_html">Canceled!</string>
<string name="import_cancel_button">Stop</string>
<string name="qsl_query_log_menu">Log (%s)</string>
<string name="message_list_simple_mode">Simple List Mode</string>
<string name="message_list_standard_mode">Standard List Mode</string>
<string name="config_msg_mode">Message mode</string>
<string name="config_msg_std_mode">Standard</string>
<string name="config_msg_simple_mode">Simple</string>
<string name="message_mode_help">messageMode_en.txt</string>
</resources>