kopia lustrzana https://github.com/N0BOY/FT8CN
commit
89500cb162
73
README.md
73
README.md
|
@ -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进行调试。
|
||||
BG8BXM(M哥),为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进行调试
|
||||
BG8BXM(M哥),为FT8CN的使用做推广,抖音和B站上有很多他的教学视频
|
||||
BG7MFQ,为FT8CN的使用做推广,帮助测试
|
||||
BG2EFX,提供大数据量的日志用于测试
|
||||
DS1UFX,贡献(tr)uSDX audio over CAT代码
|
||||
BG8HT,提供某型号电台进行测试
|
||||
```
|
||||
|
|
|
@ -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'//用于HashTable(多key的HashMap)
|
||||
|
||||
|
||||
//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.
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,3 @@
|
|||
消息模式,是指显示消息列表的方式。
|
||||
标准列表模式,就是FT8CN传统的FT8消息显示模式,包含FT8消息的要素外,还包括双方呼号归属地、距离、未通联的分区标志、消息类型等。
|
||||
精简列表模式,就是简化的FT8消息显示模式,只包含必要的FT8消息。
|
|
@ -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.
|
|
@ -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=4)RR73被误发成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进行调试。
|
||||
BG8BXM(M哥),为FT8CN的使用做推广,抖音和B站上有很多他的教学视频。
|
||||
BG7MFQ,为FT8CN的使用做推广,帮助测试。
|
||||
BG2EFX,提供大数据量的日志用于测试。
|
||||
DS1UFX,贡献(tr)uSDX audio over cat代码。
|
||||
BG8HT,提供某型号电台进行测试。
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;//同频发射
|
||||
|
|
|
@ -15,8 +15,10 @@ package com.bg7yoz.ft8cn;
|
|||
* 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右
|
||||
* <p>
|
||||
*
|
||||
* 2023-08-16 由DS1UFX提交修改(基于0.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) {
|
||||
/**
|
||||
* 以网络方式连接到ICOM、协谷X6100系列电台
|
||||
* @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);
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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 由DS1UFX提交修改(基于0.9版),用于(tr)uSDX audio over cat的支持。
|
||||
* 发送音频数据流,把16位int格式转为32位float格式
|
||||
* @param data byte格式,实际上是16位的int
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
//以下是(tr)uSDX与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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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();//显示全部仪表消息
|
||||
}
|
||||
|
|
|
@ -4,27 +4,19 @@ package com.bg7yoz.ft8cn.connector;
|
|||
* 注:ICom网络方式的音频数据包是Int类型,需要转换成Float类型
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* 网络方式的连接器的基本类。
|
||||
* 注:基本兼容ICom网络方式,但有些差异的音频数据包是Int类型,需要转换成Float类型
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
|
||||
/**
|
||||
* flexRadio要把12000采样率改为24000采样率,还要把单声道改为立体声
|
||||
* flexRadio发射的采样率为24000采样率,还要把单声道改为立体声
|
||||
* @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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
/**
|
||||
* VITA49协议的简单解包和封包操作。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
@ -126,32 +127,34 @@ public class VITA {
|
|||
/**
|
||||
* 生成音频流的VITA数据包,id应当是电台create 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是以word(32位,4字节)为单位,
|
||||
//packetSize值为263居多估计以音频,还有其它的长度,263是包含7个word(28字节)的头长度。
|
||||
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;//第四个word,64位
|
||||
//classId = 0x534c0123;//第四个word,64位
|
||||
classId = class_id;
|
||||
//classId = 0x534c03e3;//第四个word,64位
|
||||
|
||||
//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默认263(words)
|
||||
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];
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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秒),就不做减码操作了
|
||||
//减去解码的信号
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.bg7yoz.ft8cn.ft8signal;
|
||||
/**
|
||||
* 按照FT8协议打包符号。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
@ -28,6 +29,11 @@ public class FT8Package {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成i3=4的非标准消息的77位数据包。
|
||||
* @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=2,在FT8协议的定义当中,分别是标准消息,和欧盟甚高频(EU 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化标准的呼号
|
||||
|
|
|
@ -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 {
|
|||
|
||||
/**
|
||||
* 为了最大限度兼容,把32位浮点转换成16位整型,有些声卡不支持32位的浮点。
|
||||
*
|
||||
* @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 {
|
|||
}
|
||||
|
||||
/**
|
||||
* 检查消息中from中有目标呼号的数量。当有目标呼号呼叫我的消息,返回0,
|
||||
* 检查消息中from中有目标呼号的数量。当有目标呼号呼叫我的消息,返回0,如果目标呼号呼叫别人,返回值应当大于1
|
||||
*
|
||||
* @param messages 消息列表
|
||||
* @return 0:有目标呼叫我的,1:没有任何目标呼号发出的消息,>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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查有没有人CQ我,或我关注的呼号在CQ
|
||||
*
|
||||
|
@ -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 {
|
|||
// 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1)
|
||||
// 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制
|
||||
// 或我是RR73(4),且对方开始呼叫别人了,解决RR73卡死的问题
|
||||
if (newOrder == 5
|
||||
if (newOrder == 5//消息中目标回复我RR73了
|
||||
|| (functionOrder == 5 && newOrder == -1)// 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1)
|
||||
|| (functionOrder == 4 &&
|
||||
(GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit * 2)
|
||||
&& (GeneralVariables.noReplyLimit > 0)) // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制
|
||||
|| (functionOrder ==4 && checkTargetCallMe(messages)>1)
|
||||
) { // 或我是RR73(4),且对方开始呼叫别人了
|
||||
//doComplete();//做保存的动作
|
||||
|
||||
|| (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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(){}
|
||||
}
|
|
@ -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) {
|
||||
// 父类默认处理一下数据包:
|
||||
// 控制包0x10(CMD_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=0,第二次busy=1。
|
||||
* 在0x90数据包中取macAddress,电台名称
|
||||
* 这部分留给IcomControlUdp和XieGuControlUdp来处理
|
||||
* @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);
|
||||
}
|
||||
}
|
|
@ -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 ,8106,8006
|
||||
* 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 ,8106,8006
|
||||
* quint16 ident; // 0x10 发射的时候: 当datalen=0xa0时,ident=0x9781,否则ident=0x0080;
|
||||
* quint16 sendseq; // 0x12
|
||||
* quint16 unused; // 0x14
|
||||
* quint16 datalen; // 0x16
|
||||
*/
|
||||
public static boolean isAudioPacket(byte[] data){
|
||||
if (data.length<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指令的条件:长度不能小于0x15,dataLen字段与实际相符,reply=0xc1,type!=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==0,说明是对方ping过来的包,必须要回复。如果reply=1,说明是对方回Ping的,本地的pingSeq++
|
||||
*/
|
||||
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 {
|
|||
* Status(0x50)包,
|
||||
*/
|
||||
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 {
|
|||
* 0x90包。电台回复,或APP回复连接参数包.
|
||||
*/
|
||||
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 {
|
|||
* 电台参数数据包(0x66长度),它是在Capabilities(0x42长度)包的后面,如果是一个,总数据包的长度是0xA8,
|
||||
*/
|
||||
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 {
|
|||
* 登录回复包,0x60包,长度96;
|
||||
*/
|
||||
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 {
|
|||
* 登录用数据包,0x80包,长度128
|
||||
*/
|
||||
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;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* WIFI模式下电台操作。
|
||||
* WIFI模式下iCom电台操作。
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* 处理ICom的音频流。
|
||||
* 处理ICom的音频流,继承至AudioUdp。
|
||||
* @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 float,12000Hz
|
||||
//iCOM的音频格式是LPCM 16 Int,12000Hz
|
||||
//要做一下浮点到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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开Udp流端口,如果Udp端口已经打开了,会再打开一次,本地端口应该会变化
|
||||
*/
|
||||
|
@ -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 there包是控制包0x10包,类型 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 令牌类型,0x02确认,0x05续订
|
||||
*/
|
||||
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时钟任务
|
||||
* 控制包0x10,类型0x03
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* WIFI模式下iCom电台操作。
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
|
@ -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 float,12000Hz
|
||||
//要做一下浮点到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秒,采样率*2(16位,所以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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理电台发送过来的connInfo(0x90)数据包,电台发送0x90包有两次,第一次busy=0,第二次busy=1。
|
||||
* 在0x90数据包中取macAddress,电台名称
|
||||
*
|
||||
* @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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -130,4 +130,16 @@ public abstract class BaseRig {
|
|||
public boolean isPttOn() {
|
||||
return isPttOn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 2023-08-16 由DS1UFX提交修改(基于0.9版),增加(tr)uSDX audio over cat的支持。
|
||||
*/
|
||||
public boolean supportWaveOverCAT() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onDisconnecting() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ public class FlexNetworkRig extends BaseRig {
|
|||
|
||||
if (getConnector() != null) {
|
||||
float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency()
|
||||
, 24000);//flex音频的采样率是24000,todo 此处可改为动态设置24000,48000
|
||||
, 24000);//flex音频的采样率是24000
|
||||
if (data == null) {
|
||||
setPTT(false);
|
||||
return;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.rigs;
|
||||
|
||||
/**
|
||||
* IcomRig是通用的Icom电台控制类。对于wifi模式,实际的控制是通过IComWifiConnector(继承于WifiConnector)
|
||||
* 在IComWifiConnector中,有IComWifiRig具体操作电台
|
||||
*/
|
||||
|
||||
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电台发射音频的采样率是12000,todo 此处可改为动态设置24000,48000
|
||||
,12000);//此处icom电台发射音频的采样率是12000
|
||||
if (data==null){
|
||||
setPTT(false);
|
||||
return;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
package com.bg7yoz.ft8cn.rigs;
|
||||
/**
|
||||
* 由DS1UFX 于2023-08-16提交修改(基于0.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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,401 @@
|
|||
|
||||
/**
|
||||
* (tr)uSDX, fork from KENWOOD TS590.
|
||||
* 基于0.9版,增加TrSDXRig的支持。
|
||||
*
|
||||
* @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 由DS1UFX提交修改(基于0.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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当接收到音频数据后,把音频数据的采样率7812Hz转换为12000Hz,发送给Connector。
|
||||
*
|
||||
* @param data 接收到的音频(7812Hz)
|
||||
*/
|
||||
public void onReceivedWaveData(byte[] data) {
|
||||
onReceivedWaveData(data, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 当接收到音频数据后,把音频数据的采样率7812Hz转换为12000Hz,发送给Connector。
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频8bit采样转换为16bit采样位深
|
||||
*
|
||||
* @param in 8 bit 数据
|
||||
* @return 16 bit数据(byte类型)
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 音频8bitcaiyang转换为16bit采样位深
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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-Rig等PC软件无线控制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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带2字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。
|
||||
* @return 子指令
|
||||
*/
|
||||
public int getSubCommand2() {//获取子命令
|
||||
if (rawData.length < 8) {
|
||||
return -1;
|
||||
}
|
||||
return readShortData(rawData,6);
|
||||
}
|
||||
/**
|
||||
* 获取带3字节的子命令,有的指令没有子命令,有的指令只有1个字节,要注意。
|
||||
* @return 子指令
|
||||
*/
|
||||
public int getSubCommand3() {//获取子命令
|
||||
if (rawData.length < 9) {
|
||||
return -1;
|
||||
}
|
||||
return ((int) rawData[7] & 0xff)
|
||||
| ((int) rawData[6] & 0xff) << 8
|
||||
| ((int) rawData[5] & 0xff) << 16;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据区,有的指令有子命令,有的没有子命令,所以要区分出来。子命令占一个字节
|
||||
*
|
||||
* @param hasSubCommand 是否有子命令
|
||||
* @return 返回数据区
|
||||
*/
|
||||
public byte[] getData(boolean hasSubCommand) {
|
||||
int pos;
|
||||
|
||||
if (hasSubCommand) {
|
||||
pos = 6;
|
||||
} else {
|
||||
pos = 5;
|
||||
}
|
||||
if (rawData.length < pos + 1) {//没有数据区了
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = new byte[rawData.length - pos];
|
||||
|
||||
for (int i = 0; i < rawData.length - pos; i++) {
|
||||
data[i] = rawData[pos + i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public byte[] getData2Sub() {
|
||||
if (rawData.length < 9) {//没有数据区了
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] data = new byte[rawData.length - 8];
|
||||
|
||||
System.arraycopy(rawData, 8, data, 0, rawData.length - 8);
|
||||
return data;
|
||||
}
|
||||
//解析接收的指令
|
||||
|
||||
/**
|
||||
* 从串口中接到的数据解析出指令的数据:FE FE E0 A4 Cn Sc data FD
|
||||
*
|
||||
* @param ctrAddr 控制者地址,默认E0或00
|
||||
* @param rigAddr 电台地址,705默认是A4
|
||||
* @param buffer 从串口接收到的数据
|
||||
* @return 返回电台指令对象,如果不符合指令的格式,返回null。
|
||||
*/
|
||||
public static 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
package com.bg7yoz.ft8cn.ui;
|
||||
/**
|
||||
* 用于可以自动换行的RadioGroup控件
|
||||
* 原生RadioGroup控件在布局不够宽度时,无法把RadioButton自动换行,此控件可以自动换行
|
||||
* @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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>-->
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Ładowanie…
Reference in New Issue