kopia lustrzana https://github.com/N0BOY/FT8CN
commit
356f5f90f0
|
@ -15,14 +15,14 @@ static def getCurrentTime() {
|
|||
|
||||
android {
|
||||
|
||||
compileSdk 32
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.bg7yoz.ft8cn"
|
||||
minSdk 23
|
||||
targetSdk 32
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName '0.86'
|
||||
versionName '0.87'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
dataBinding{
|
||||
|
@ -35,7 +35,6 @@ android {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
ndk{
|
||||
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
|
||||
}
|
||||
|
@ -90,11 +89,12 @@ dependencies {
|
|||
implementation 'com.google.android.gms:play-services-maps:18.0.2'
|
||||
|
||||
// implementation 'com.google.code.gson:gson:2.7'
|
||||
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'
|
||||
//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/nanohttpd-2.2.0.jar')
|
||||
|
|
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 binarny nie jest wyświetlany.
|
@ -6,4 +6,4 @@ FT8CN在数据库中保存有您关注的呼号、通联过程中的消息(可
|
|||
|
||||
清空“关注的呼号”,关注的呼号是在平时通联是您关注的呼号,在该呼号出现时,会自动添加的呼叫列表中,方便自动呼叫。清空关注的呼号,是把所有关注的呼号清空。
|
||||
|
||||
清空“缓存的QSO消息”,缓存的QSO消息是您在通联呼叫时保存下来的通联过程,该消息没有在FT8CN界面中显示,可以在后台中查看,保存该数据的目的是方便查看您通联的过程,做为调试的一部分。如果您对该数据不感兴趣,可以定期清空。
|
||||
清空“解码的消息”,FT8CN可以保存所有解码的消息,这些消息暂时没有在FT8CN界面中显示,可以在后台中查看和查询。
|
|
@ -1,10 +1,12 @@
|
|||
About Empty the Cache:
|
||||
FT8CN keeps the tracked callsign list and QSO decoding log in a cache file. You can permanently remove them using empty cache option. Note that clearing cache won't delete any QSO log.
|
||||
Clear Cache Data:
|
||||
FT8CN keeps a list of followed callsign and decoded messages in the cache file. You can permanently delete those using empty cache option.
|
||||
|
||||
Clear Tracked callsign list:
|
||||
Tracked callsign list contains all your favorite callsigns. When a tracked callsign shows up, it will be added to the calling message for quick or auto call.
|
||||
Note that clearing cache won't delete any QSO log.
|
||||
|
||||
Note that clearing tracked callsign list will remove all saved favorite callsigns.
|
||||
List of followed callsign:
|
||||
This list contains all your followed callsigns. When a followed callsign shows up, it will be added to the calling message for quick-call or auto-call.
|
||||
|
||||
Note that clearing the list of tracked callsign will remove all saved favorite callsigns.
|
||||
|
||||
Clean "cached QSO decoding":
|
||||
FT8CN saves all decoded entries during your QSO. It doesn't display on the UI, but can be accessed for debugging and troubleshooting. You can clean it regularly if not interested in any debugging.
|
||||
FT8CN can save all decoded messages. You review them from Web UI. (Not on main UI yet)
|
|
@ -14,6 +14,17 @@ Please click "FAQ" if you have good suggestions or questions .
|
|||
BG7YOZ
|
||||
2022-07-01
|
||||
|
||||
2023-03-10(0.87)
|
||||
1.增加查询的通联日志结果在地图中定位显示功能。
|
||||
2.增加FlexRadio仪表显示和参数设置(目前暂不支持发射)。
|
||||
3.增加时间自动同步功能(服务器是Microsoft NTP)。
|
||||
4.增加SWL模式,对解码消息以及QSO有保存和导出的功能(SWL的QSO认定标准:至少要有双方的报告,以及结束语73、RR73、RRR)。
|
||||
5.丰富后台数据的查询功能。
|
||||
6.修正后台查询“呼号与网格映射表”中距离计算的错误。
|
||||
7.针对协谷G90S未来的新固件,调整电台型号选项。
|
||||
8.解决解码消息较多时界面会卡顿的问题。
|
||||
9.优化日志查询性能。
|
||||
|
||||
2023-02-06(0.86)
|
||||
1.提高日志导入的健壮性,反馈格式错误的日志信息。
|
||||
2.修正在推算SNR时,偶尔会出现数组下标越界造成闪退的问题。
|
||||
|
@ -251,7 +262,6 @@ BG7YOZ
|
|||
BH1RNN,协助对部分功能进行测试。
|
||||
BG7BSM,协助对一些BUG进行调试。
|
||||
BH4FTI,发现并协助对一些BUG进行调试。
|
||||
BH4FTI,发现并协助对一些BUG进行调试。
|
||||
BG8BXM(M哥),为FT8CN的使用做推广,抖音和B站上有很多他的教学视频。
|
||||
BG7MFQ,为FT8CN的使用做推广,帮助测试。
|
||||
|
||||
|
|
|
@ -20,10 +20,10 @@ ICOM IC-9100,7C,19200,0
|
|||
ICOM IC-9700,A2,115200,0
|
||||
ICOM IC-R8600,96,115200,0
|
||||
ICOM ID-52A,A6,115200,0
|
||||
XIEGU(协谷) X6100,70,19200,13
|
||||
ICOM IC-706MKIIG,58,19200,0
|
||||
XIEGU(协谷) X6100/G90S(U-DIG),70,19200,13
|
||||
XIEGU(协谷) X6100/G90S(USB),70,19200,9
|
||||
XIEGU(协谷) X5105,70,19200,9
|
||||
XIEGU(协谷) G90S,70,19200,9
|
||||
XIEGU(协谷) X108,70,19200,9
|
||||
GUOHE(国赫) Q900,00,19200,8
|
||||
YAESU FT-450(D),00,4800,4
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
SWL是英文Shortwave Listener的简称,即短波收听爱好者。
|
||||
SWL模式是仅仅收听其他业余无线电爱好者的通联,而不进行无线电发射的模式。
|
||||
针对SWL,FT8CN增加了“保存解码消息”和“保存SWL记录”功能。
|
||||
保存解码消息:就是把FT8CN所有解码的消息都保存下来。
|
||||
保存SWL记录:就是把守听到的其他业余无线电台通联的QSO日志保存下来。在FT8CN中,认定成功的SWL QSO标准是:有结束语(73、RR73、RRR)且有双方的信号报告,双方的网格报告不是必须项。
|
||||
保存的“解码消息”和“SWL记录”可以在后台(需要在同网段的局域网,用浏览器访问)做查询以及导出操作。
|
||||
保存的“解码消息”和“SWL记录”的删除操作在”设置界面“的”清空缓存“中操作。
|
|
@ -0,0 +1,7 @@
|
|||
SWL stands for Shortwave Listener, whom may not hold an amateur radio licence to transmit but listen and report amateur radio QSOs. FT8CN supports "Save decoded messages" and "Save SWL QSO" for SWL.
|
||||
|
||||
Save decoded messages: FT8CN will save all decoded messages.
|
||||
Save SWL QSO: FT8CN can save QSO log as SWL when both stations provide signal report and 73/RR73/RRR (Gridsqure is optional).
|
||||
|
||||
You can look up and export saved decoded messages or SWL QSO log via Web UI (under the same LAN).
|
||||
You can delete them using "Clear Cache Data" in settings.
|
|
@ -1,3 +1,3 @@
|
|||
时间偏移,是指本APP在每个周期相对于系统时钟的偏移值。
|
||||
FT8的时钟周期是15秒,所有电台的时钟都是以UTC时间为基准,以每分钟的第0秒、15秒、30秒、45秒为周期的起始时间。
|
||||
一般来说,手机会自动同步时间,如果您的设备与UTC时间不同步,需要设定偏移值。
|
||||
如果可以访问网络,FT8CN会自动同步时间,如果不能访问网络,可以手动设定偏移值。
|
|
@ -1,3 +1,3 @@
|
|||
Time offset refers to the offset value of the APP relative to the system clock in each cycle.
|
||||
The clock period of FT8 is 15 seconds. The clocks of all RIGs are based on UTC time, with the 0th, 15th, 30th, and 45th seconds of each minute as the starting time of the cycle.
|
||||
In general, the phone will automatically sync the time. If your device is out of sync with UTC time, you need to set the offset value.
|
||||
Local clock offset:
|
||||
In FT8, all stations start TX/RX at each 0/15/45/60 seconds, which require accurate time synchronization.
|
||||
FT8CN will automatically sync time via internet. When internet is not available, you can adjust the local clock by changing this local clock offset.
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* 问题收集的WebView。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
|
||||
/**
|
||||
* FT8有关的常量。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public final class FT8Common {
|
||||
public static final int FT8_MODE=0;
|
||||
public static final int FT4_MODE=1;
|
||||
|
|
|
@ -7,8 +7,8 @@ package com.bg7yoz.ft8cn;
|
|||
* 1.为方便在列表中显示,各要素通过Get方法,返回String类型的结果。
|
||||
* -----2022.5.13---
|
||||
* 2.增加i3,n3消息类型内容
|
||||
* BG7YOZ
|
||||
* 2022.5.6
|
||||
* @author BG7YOZ
|
||||
* @date 2022.5.6
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
@ -42,7 +42,7 @@ public class Ft8Message {
|
|||
public String callsignFrom = null;//发起呼叫的呼号
|
||||
public String callsignTo = null;//接收呼叫的呼号
|
||||
|
||||
public String modifier=null;//目标呼号的修饰符 如CQ POTA BG7YOZ OL50中的POTA
|
||||
public String modifier = null;//目标呼号的修饰符 如CQ POTA BG7YOZ OL50中的POTA
|
||||
|
||||
public String extraInfo = null;
|
||||
public String maidenGrid = null;
|
||||
|
@ -53,26 +53,26 @@ public class Ft8Message {
|
|||
public long callToHash10 = 0;//12位长度的哈希码
|
||||
public long callToHash12 = 0;//12位长度的哈希码
|
||||
public long callToHash22 = 0;//12位长度的哈希码
|
||||
public boolean isCallMe = false;//是不是CALL我的消息
|
||||
//private boolean isCallMe = false;//是不是CALL我的消息
|
||||
public long band;//载波频率
|
||||
|
||||
public String fromWhere = null;//用于显示地址
|
||||
public String toWhere = null;//用于显示地址
|
||||
|
||||
public boolean isQSL_Callsign=false;//是不是通联过的呼号
|
||||
public boolean isQSL_Callsign = false;//是不是通联过的呼号
|
||||
|
||||
public static MessageHashMap hashList = new MessageHashMap();
|
||||
|
||||
|
||||
public boolean fromDxcc=false;
|
||||
public boolean fromItu=false;
|
||||
public boolean fromCq=false;
|
||||
public boolean toDxcc=false;
|
||||
public boolean toItu=false;
|
||||
public boolean toCq=false;
|
||||
public boolean fromDxcc = false;
|
||||
public boolean fromItu = false;
|
||||
public boolean fromCq = false;
|
||||
public boolean toDxcc = false;
|
||||
public boolean toItu = false;
|
||||
public boolean toCq = false;
|
||||
|
||||
public LatLng fromLatLng=null;
|
||||
public LatLng toLatLng=null;
|
||||
public LatLng fromLatLng = null;
|
||||
public LatLng toLatLng = null;
|
||||
|
||||
|
||||
@NonNull
|
||||
|
@ -93,20 +93,22 @@ public class Ft8Message {
|
|||
this.signalFormat = signalFormat;
|
||||
}
|
||||
|
||||
public Ft8Message(String callTo,String callFrom,String extraInfo) {
|
||||
public Ft8Message(String callTo, String callFrom, String extraInfo) {
|
||||
//如果是自由文本,callTo=CQ,callFrom=MyCall,extraInfo=freeText
|
||||
this.callsignTo=callTo.toUpperCase();
|
||||
this.callsignFrom=callFrom.toUpperCase();
|
||||
this.extraInfo=extraInfo.toUpperCase();
|
||||
this.callsignTo = callTo.toUpperCase();
|
||||
this.callsignFrom = callFrom.toUpperCase();
|
||||
this.extraInfo = extraInfo.toUpperCase();
|
||||
}
|
||||
public Ft8Message(int i3,int n3,String callTo,String callFrom,String extraInfo) {
|
||||
this.callsignTo=callTo;
|
||||
this.callsignFrom=callFrom;
|
||||
this.extraInfo=extraInfo;
|
||||
this.i3=i3;
|
||||
this.n3=n3;
|
||||
this.utcTime=UtcTimer.getSystemTime();//用于显示TX
|
||||
|
||||
public Ft8Message(int i3, int n3, String callTo, String callFrom, String extraInfo) {
|
||||
this.callsignTo = callTo;
|
||||
this.callsignFrom = callFrom;
|
||||
this.extraInfo = extraInfo;
|
||||
this.i3 = i3;
|
||||
this.n3 = n3;
|
||||
this.utcTime = UtcTimer.getSystemTime();//用于显示TX
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个解码消息对象
|
||||
*
|
||||
|
@ -137,10 +139,10 @@ public class Ft8Message {
|
|||
} else {
|
||||
callsignTo = message.callsignTo;
|
||||
}
|
||||
if (message.i3==4){
|
||||
hashList.addHash(FT8Package.getHash22(message.callsignFrom),message.callsignFrom);
|
||||
hashList.addHash(FT8Package.getHash12(message.callsignFrom),message.callsignFrom);
|
||||
hashList.addHash(FT8Package.getHash10(message.callsignFrom),message.callsignFrom);
|
||||
if (message.i3 == 4) {
|
||||
hashList.addHash(FT8Package.getHash22(message.callsignFrom), message.callsignFrom);
|
||||
hashList.addHash(FT8Package.getHash12(message.callsignFrom), message.callsignFrom);
|
||||
hashList.addHash(FT8Package.getHash10(message.callsignFrom), message.callsignFrom);
|
||||
}
|
||||
|
||||
extraInfo = message.extraInfo;
|
||||
|
@ -187,16 +189,16 @@ public class Ft8Message {
|
|||
*/
|
||||
public String getMessageText() {
|
||||
|
||||
if (i3==0&&n3==0){//说明是自由文本
|
||||
if (extraInfo.length()<13){
|
||||
return String.format("%-13s",extraInfo.toUpperCase());
|
||||
}else {
|
||||
return extraInfo.toUpperCase().substring(0,13);
|
||||
if (i3 == 0 && n3 == 0) {//说明是自由文本
|
||||
if (extraInfo.length() < 13) {
|
||||
return String.format("%-13s", extraInfo.toUpperCase());
|
||||
} else {
|
||||
return extraInfo.toUpperCase().substring(0, 13);
|
||||
}
|
||||
}
|
||||
if (modifier!=null&&checkIsCQ()){//修饰符
|
||||
if (modifier.matches("[0-9]{3}|[A-Z]{1,4}")){
|
||||
return String.format("%s %s %s %s", callsignTo,modifier, callsignFrom, extraInfo).trim();
|
||||
if (modifier != null && checkIsCQ()) {//修饰符
|
||||
if (modifier.matches("[0-9]{3}|[A-Z]{1,4}")) {
|
||||
return String.format("%s %s %s %s", callsignTo, modifier, callsignFrom, extraInfo).trim();
|
||||
}
|
||||
}
|
||||
return String.format("%s %s %s", callsignTo, callsignFrom, extraInfo).trim();
|
||||
|
@ -204,11 +206,12 @@ public class Ft8Message {
|
|||
|
||||
/**
|
||||
* 返回解码消息带信噪比的内容
|
||||
*
|
||||
* @return 内容
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String getMessageTextWithDb(){
|
||||
return String.format("%d %s %s %s",snr, callsignTo, callsignFrom, extraInfo).trim();
|
||||
public String getMessageTextWithDb() {
|
||||
return String.format("%d %s %s %s", snr, callsignTo, callsignFrom, extraInfo).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,11 +272,13 @@ public class Ft8Message {
|
|||
/**
|
||||
* 消息中含有mycall呼号的
|
||||
*
|
||||
* @param mycall 我的呼号
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean inMyCall(String mycall) {
|
||||
return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals(""));
|
||||
public boolean inMyCall() {
|
||||
if (GeneralVariables.myCallsign.length() == 0) return false;
|
||||
return this.callsignFrom.contains(GeneralVariables.myCallsign)
|
||||
|| this.callsignTo.contains(GeneralVariables.myCallsign);
|
||||
//return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals(""));
|
||||
}
|
||||
/*
|
||||
i3.n3类型 基本目的 消息范例 位字段标签
|
||||
|
@ -327,7 +332,7 @@ t71 遥感数据,最多18位十六进制数字
|
|||
if (callsignFrom == null) {
|
||||
return "";
|
||||
}
|
||||
return callsignFrom.replace("<","").replace(">","");
|
||||
return callsignFrom.replace("<", "").replace(">", "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -346,7 +351,7 @@ t71 遥感数据,最多18位十六进制数字
|
|||
|| callsignTo.substring(0, 3).equals("QRZ")) {
|
||||
return "";
|
||||
}
|
||||
return callsignTo.replace("<","").replace(">","");
|
||||
return callsignTo.replace("<", "").replace(">", "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,20 +361,21 @@ t71 遥感数据,最多18位十六进制数字
|
|||
*/
|
||||
public String getMaidenheadGrid(DatabaseOpr db) {
|
||||
if (i3 != 1 && i3 != 2) {//一般只有i3=1或i3=2,标准消息,甚高频消息才有网格
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom,db);//到对应表中找一下网格
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
|
||||
} else {
|
||||
String[] msg = getMessageText().split(" ");
|
||||
if (msg.length < 1) {
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom,db);//到对应表中找一下网格
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
|
||||
}
|
||||
String s = msg[msg.length - 1];
|
||||
if (MaidenheadGrid.checkMaidenhead(s)) {
|
||||
return s;
|
||||
} else {//不是网格信息,就可能是信号报告
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom,db);//到对应表中找一下网格
|
||||
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getToMaidenheadGrid(DatabaseOpr db) {
|
||||
if (checkIsCQ()) return "";
|
||||
return GeneralVariables.getGridByCallsign(callsignTo, db);
|
||||
|
@ -392,39 +398,50 @@ t71 遥感数据,最多18位十六进制数字
|
|||
/**
|
||||
* 查消息的类型。i3.n3。
|
||||
*
|
||||
* @return String
|
||||
* @return 消息类型
|
||||
*/
|
||||
|
||||
public String getCommandInfo() {
|
||||
return getCommandInfoByI3N3(i3, n3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查消息的类型。i3.n3。
|
||||
*
|
||||
* @param i i3
|
||||
* @param n n3
|
||||
* @return 消息类型
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String getCommandInfo() {
|
||||
public static String getCommandInfoByI3N3(int i, int n) {
|
||||
String format = "%d.%d:%s";
|
||||
switch (i3) {
|
||||
switch (i) {
|
||||
case 1:
|
||||
case 2:
|
||||
return String.format(format, i3, 0, GeneralVariables.getStringFromResource(R.string.std_msg));
|
||||
return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.std_msg));
|
||||
case 5:
|
||||
case 3:
|
||||
case 4:
|
||||
return String.format(format, i3, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg));
|
||||
return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg));
|
||||
case 0:
|
||||
switch (n3) {
|
||||
switch (n) {
|
||||
case 0:
|
||||
return String.format(format, i3, n3, GeneralVariables.getStringFromResource(R.string.free_text));
|
||||
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.free_text));
|
||||
case 1:
|
||||
return String.format(format, i3, n3, GeneralVariables.getStringFromResource(R.string.dXpedition));
|
||||
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.dXpedition));
|
||||
case 3:
|
||||
case 4:
|
||||
return String.format(format, i3, n3, GeneralVariables.getStringFromResource(R.string.field_day));
|
||||
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.field_day));
|
||||
case 5:
|
||||
return String.format(format, i3, n3, GeneralVariables.getStringFromResource(R.string.telemetry));
|
||||
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.telemetry));
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
//获取发送者的传输对象
|
||||
public TransmitCallsign getFromCallTransmitCallsign() {
|
||||
return new TransmitCallsign(this.i3,this.n3,this.callsignFrom, freq_hz
|
||||
return new TransmitCallsign(this.i3, this.n3, this.callsignFrom, freq_hz
|
||||
, this.getSequence()
|
||||
, snr);
|
||||
}
|
||||
|
@ -432,9 +449,9 @@ t71 遥感数据,最多18位十六进制数字
|
|||
//获取发送者的传输对象,注意!!!与发送者的时序是相反的!!!
|
||||
public TransmitCallsign getToCallTransmitCallsign() {
|
||||
if (report == -100) {//如果消息中没有信号报告,就用发送方的SNR代替
|
||||
return new TransmitCallsign(this.i3,this.n3,this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, snr);
|
||||
return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, snr);
|
||||
} else {
|
||||
return new TransmitCallsign(this.i3,this.n3,this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, report);
|
||||
return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, report);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* 常用变量。关于mainContext有内存泄漏的风险,以后解决。
|
||||
* mainContext
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.bg7yoz.ft8cn.callsign.CallsignDatabase;
|
||||
|
@ -12,15 +14,14 @@ import com.bg7yoz.ft8cn.connector.ConnectMode;
|
|||
import com.bg7yoz.ft8cn.database.ControlMode;
|
||||
import com.bg7yoz.ft8cn.database.DatabaseOpr;
|
||||
import com.bg7yoz.ft8cn.ft8transmit.QslRecordList;
|
||||
import com.bg7yoz.ft8cn.log.QSLRecord;
|
||||
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
|
||||
import com.bg7yoz.ft8cn.timer.UtcTimer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
|
@ -29,10 +30,15 @@ public class GeneralVariables {
|
|||
public static String VERSION = BuildConfig.VERSION_NAME;//版本号"0.62(Beta 4)";
|
||||
public static String BUILD_DATE = BuildConfig.apkBuildTime;//编译的时间
|
||||
public static int MESSAGE_COUNT = 3000;//消息的最大缓存数量
|
||||
public static boolean saveSWLMessage=false;//保存解码消息开关
|
||||
public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关
|
||||
|
||||
public static MutableLiveData<Float> mutableVolumePercent = new MutableLiveData<>();
|
||||
public static float volumePercent = 0.5f;//播放音频的音量,是百分比
|
||||
|
||||
public static int flexMaxRfPower=10;//flex电台的最大发射功率
|
||||
public static int flexMaxTunePower=10;//flex电台的最大调谐功率
|
||||
|
||||
private Context mainContext;
|
||||
public static CallsignDatabase callsignDatabase = null;
|
||||
|
||||
|
@ -42,7 +48,7 @@ public class GeneralVariables {
|
|||
|
||||
public static boolean isChina = true;//语言是不是中国
|
||||
public static boolean isTraditionalChinese = true;//语言是不是繁体中文
|
||||
public static double maxDist = 0;//最远距离
|
||||
//public static double maxDist = 0;//最远距离
|
||||
|
||||
//各已经通联的分区列表
|
||||
public static final Map<String, String> dxccMap = new HashMap<>();
|
||||
|
@ -99,7 +105,7 @@ public class GeneralVariables {
|
|||
if (i == 0) {
|
||||
calls.append(key);
|
||||
} else {
|
||||
calls.append("," + key);
|
||||
calls.append(",").append(key);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
@ -366,6 +372,43 @@ public class GeneralVariables {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是不是信号报告,如果是,把值赋给 report
|
||||
* @param extraInfo 消息扩展
|
||||
* @return 信号报告值,没找到是-100
|
||||
*/
|
||||
public static int checkFun2_3(String extraInfo){
|
||||
if (extraInfo.equals("73")) return -100;
|
||||
if (extraInfo.matches("[R]?[+-]?[0-9]{1,2}")){
|
||||
try {
|
||||
return Integer.parseInt(extraInfo.replace("R",""));
|
||||
} catch (Exception e) {
|
||||
return -100;
|
||||
}
|
||||
}
|
||||
return -100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是不是网格报告,如果是,把值赋给 report
|
||||
* @param extraInfo 消息扩展
|
||||
* @return 信号报告
|
||||
*/
|
||||
public static boolean checkFun1_6(String extraInfo){
|
||||
return extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]")
|
||||
&& !extraInfo.trim().equals("RR73");
|
||||
}
|
||||
/**
|
||||
* 检查是否是通联结束:RRR、RR73、73
|
||||
* @param extraInfo 消息后缀
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean checkFun4_5(String extraInfo){
|
||||
return extraInfo.trim().equals("RR73")
|
||||
|| extraInfo.trim().equals("RRR")
|
||||
||extraInfo.trim().equals("73");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从String.xml中提取字符串
|
||||
*
|
||||
|
@ -517,7 +560,7 @@ public class GeneralVariables {
|
|||
|
||||
}
|
||||
|
||||
public static synchronized void deleteArrayListMore(ArrayList list) {
|
||||
public static synchronized void deleteArrayListMore(ArrayList<Ft8Message> list) {
|
||||
if (list.size() > GeneralVariables.MESSAGE_COUNT) {
|
||||
while (list.size() > GeneralVariables.MESSAGE_COUNT) {
|
||||
list.remove(0);
|
||||
|
|
|
@ -6,8 +6,9 @@ package com.bg7yoz.ft8cn;
|
|||
* 1.生成MainViewModel实例。MainViewModel是用于整个生存周期,用于录音、解析等功能。
|
||||
* 2.录音、存储的权限申请。
|
||||
* 3.实现Fragment的导航管理。
|
||||
* BG7YOZ
|
||||
* 2022.5.6
|
||||
* 4.USB串口连接后的提示
|
||||
* @author BG7YOZ
|
||||
* @date 2022.5.6
|
||||
*/
|
||||
|
||||
|
||||
|
@ -60,6 +61,7 @@ import com.bg7yoz.ft8cn.ui.ToastMessage;
|
|||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
|
@ -132,8 +134,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
setContentView(binding.getRoot());
|
||||
|
||||
|
||||
|
||||
|
||||
ToastMessage.getInstance();
|
||||
registerBluetoothReceiver();//注册蓝牙动作改变的广播
|
||||
if (mainViewModel.isBTConnected()) {
|
||||
|
@ -195,6 +195,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
//清空缓存中的文件
|
||||
//deleteFolderFile(this.getCacheDir().getPath());
|
||||
|
||||
//Log.e(TAG, this.getCacheDir().getPath());
|
||||
|
||||
//用于Fragment的导航。
|
||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView);
|
||||
|
@ -217,18 +218,53 @@ public class MainActivity extends AppCompatActivity {
|
|||
binding.welcomTextView.setText(String.format(getString(R.string.version_info)
|
||||
, GeneralVariables.VERSION, GeneralVariables.BUILD_DATE));
|
||||
|
||||
|
||||
floatView = new FloatView(this, 32);
|
||||
if (!animatorRunned) {
|
||||
animationImage();
|
||||
animatorRunned = true;
|
||||
} else {
|
||||
binding.initDataLayout.setVisibility(View.GONE);
|
||||
|
||||
InitFloatView();
|
||||
}
|
||||
//初始化数据
|
||||
InitData();
|
||||
|
||||
|
||||
//观察是不是flex radio
|
||||
|
||||
mainViewModel.mutableIsFlexRadio.observe(this, new Observer<Boolean>() {
|
||||
@Override
|
||||
public void onChanged(Boolean aBoolean) {
|
||||
//if (floatView==null) return;
|
||||
if (aBoolean) {
|
||||
//添加flex配置按钮
|
||||
floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon
|
||||
, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
||||
navController.navigate(R.id.flexRadioInfoFragment);
|
||||
|
||||
// if (mainViewModel.baseRig != null) {
|
||||
// if (mainViewModel.baseRig.isConnected()) {
|
||||
// ToastMessage.show("flex connected");
|
||||
// }else {
|
||||
// ToastMessage.show("flex disconnected");
|
||||
// }
|
||||
// }else {
|
||||
// ToastMessage.show("rig is null");
|
||||
// }
|
||||
|
||||
}
|
||||
});
|
||||
} else {//删除flex配置按钮
|
||||
floatView.deleteButtonByName("flex_radio");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//关闭串口设备列表按钮
|
||||
binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
|
@ -281,7 +317,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
*/
|
||||
|
||||
private void InitFloatView() {
|
||||
floatView = new FloatView(this, 32);
|
||||
//floatView = new FloatView(this, 32);
|
||||
|
||||
binding.container.addView(floatView);
|
||||
floatView.setButtonMargin(0);
|
||||
|
@ -332,6 +368,15 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon
|
||||
// , new View.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(View view) {
|
||||
// navController.navigate(R.id.flexRadioInfoFragment);
|
||||
// }
|
||||
// });
|
||||
|
||||
floatView.initLocation();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,21 @@ package com.bg7yoz.ft8cn;
|
|||
* 1.解码的总条数。decoded_counter和mutable_Decoded_Counter。
|
||||
* 2.解码消息的列表。消息以Ft8Message展示,列表用ArrayList泛型实现。ft8Messages,mutableFt8MessageList。
|
||||
* 3.解码和录音都需要时间同步,也就是以UTC时间的每15秒为一个周期。同步事件的触发由UtcTimer类来实现。
|
||||
* 录音方法runRecode,解码暂时用testFt8实现,后续应改为类方式。--------TO DO------------
|
||||
* 录音方法runRecode,解码暂时用testFt8实现,后续应改为类方式。--------TODO------------
|
||||
* 4.当前的UTC时间。timerSec,更新频率(心跳频率)由UtcTimer确定,暂定100毫秒。
|
||||
*
|
||||
* <p>
|
||||
* 5.通过类方法getInstance获取当前的MainViewModel的实例,确保有唯一的实例。
|
||||
* 6.用HamAudioRecorder类实现录音,目前只实现录音成文件,然后读取文件的数据给解码模块,后面要改成直接给数组的方式----TO DO---
|
||||
*
|
||||
* <p>
|
||||
* 7.解码采用JNI接口调用原生C语言。调用接口名时ft8cn,由cpp文件夹下的CMakeLists.txt维护。各函数的调用接口在decode_ft8.cpp中。
|
||||
*
|
||||
* <p>
|
||||
* -----2022.5.9-----
|
||||
* 如果系统没有发射信号,触发器会在每一个周期触发录音动作,因录音开始和结束要浪费一些时间,如果不干预上一个录音的动作,将出现
|
||||
* 连续的周期内录音动作重叠,造成第二个录音动作失败。所以,第二个周期的录音开始前,要停止前一个周期的录音,造成的结果就是每一次录音
|
||||
* 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右
|
||||
*
|
||||
* BG7YOZ
|
||||
* 2022.5.6
|
||||
* <p>
|
||||
* @author BG7YOZ
|
||||
* @date 2022.5.6
|
||||
*/
|
||||
|
||||
import static com.bg7yoz.ft8cn.GeneralVariables.getStringFromResource;
|
||||
|
@ -59,7 +59,9 @@ import com.bg7yoz.ft8cn.ft8transmit.OnDoTransmitted;
|
|||
import com.bg7yoz.ft8cn.ft8transmit.OnTransmitSuccess;
|
||||
import com.bg7yoz.ft8cn.html.LogHttpServer;
|
||||
import com.bg7yoz.ft8cn.icom.IComWifiRig;
|
||||
import com.bg7yoz.ft8cn.log.QSLCallsignRecord;
|
||||
import com.bg7yoz.ft8cn.log.QSLRecord;
|
||||
import com.bg7yoz.ft8cn.log.SWLQsoList;
|
||||
import com.bg7yoz.ft8cn.rigs.BaseRig;
|
||||
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
|
||||
import com.bg7yoz.ft8cn.rigs.ElecraftRig;
|
||||
|
@ -121,6 +123,8 @@ public class MainViewModel extends ViewModel {
|
|||
public MutableLiveData<Boolean> mutableIsDecoding = new MutableLiveData<>();//会触发频谱图中的标记动作
|
||||
public ArrayList<Ft8Message> currentMessages = null;//本周期解码的消息(用于画到频谱上)
|
||||
|
||||
public MutableLiveData<Boolean> mutableIsFlexRadio=new MutableLiveData<>();//是不是flex电台
|
||||
|
||||
private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool();
|
||||
private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool();
|
||||
private final GetQTHRunnable getQTHRunnable=new GetQTHRunnable(this);
|
||||
|
@ -136,6 +140,8 @@ public class MainViewModel extends ViewModel {
|
|||
//控制电台的方式
|
||||
public OperationBand operationBand = null;
|
||||
|
||||
private SWLQsoList swlQsoList=new SWLQsoList();//用于记录SWL的QSO对象,对SWL QSO做判断,防止重复。
|
||||
|
||||
|
||||
public MutableLiveData<ArrayList<CableSerialPort.SerialPort>> mutableSerialPorts = new MutableLiveData<>();
|
||||
private ArrayList<CableSerialPort.SerialPort> serialPorts;//串口列表
|
||||
|
@ -193,6 +199,8 @@ public class MainViewModel extends ViewModel {
|
|||
public String queryKey = "";//查询的关键字
|
||||
public int queryFilter = 0;//过滤,0全部,1,确认,2,未确认
|
||||
public MutableLiveData<Integer> mutableQueryFilter = new MutableLiveData<>();
|
||||
public ArrayList<QSLCallsignRecord> callsignRecords=new ArrayList<>();
|
||||
//public ArrayList<QSLRecordStr> qslRecords=new ArrayList<>();
|
||||
//********************************************
|
||||
//关注呼号的列表
|
||||
//public ArrayList<String> followCallsign = new ArrayList<>();
|
||||
|
@ -240,6 +248,7 @@ public class MainViewModel extends ViewModel {
|
|||
hamRecorder = new HamRecorder(null);
|
||||
hamRecorder.startRecord();
|
||||
|
||||
mutableIsFlexRadio.setValue(false);
|
||||
|
||||
//创建用于显示时间的计时器
|
||||
utcTimer = new UtcTimer(10, false, new OnUtcTimer() {
|
||||
|
@ -257,6 +266,8 @@ public class MainViewModel extends ViewModel {
|
|||
});
|
||||
utcTimer.start();//启动计时器
|
||||
|
||||
//同步一下时间。microsoft的NTP服务器
|
||||
UtcTimer.syncTime(null);
|
||||
|
||||
mutableFt8MessageList.setValue(ft8Messages);
|
||||
|
||||
|
@ -270,13 +281,10 @@ public class MainViewModel extends ViewModel {
|
|||
@Override
|
||||
public void afterDecode(long utc, float time_sec, int sequential, ArrayList<Ft8Message> messages) {
|
||||
|
||||
for (Ft8Message msg : messages) {
|
||||
msg.isCallMe = msg.inMyCall(GeneralVariables.myCallsign);
|
||||
}
|
||||
synchronized (ft8Messages) {
|
||||
ft8Messages.addAll(messages);//添加消息到列表
|
||||
}
|
||||
GeneralVariables.deleteArrayListMore(ft8Messages);//删除多余的消息
|
||||
GeneralVariables.deleteArrayListMore(ft8Messages);//删除多余的消息,FT8CN限定的可展示消息的总数量
|
||||
|
||||
mutableFt8MessageList.postValue(ft8Messages);//触发添加消息的动作,让界面能观察到
|
||||
mutableTimerOffset.postValue(time_sec);//本次时间偏移量
|
||||
|
@ -292,8 +300,21 @@ public class MainViewModel extends ViewModel {
|
|||
currentMessages = messages;//保存本次解码的消息
|
||||
|
||||
getQTHRunnable.messages=messages;
|
||||
getQTHThreadPool.execute(getQTHRunnable);//用线程池的方式查询
|
||||
getQTHThreadPool.execute(getQTHRunnable);//用线程池的方式查询归属地
|
||||
|
||||
if (GeneralVariables.saveSWLMessage) {
|
||||
databaseOpr.writeMessage(messages);//把SWL消息写到数据库
|
||||
}
|
||||
//检查QSO of SWL,并保存到SWLQSOTable中的通联列表qsoList中
|
||||
if (GeneralVariables.saveSWL_QSO){
|
||||
swlQsoList.findSwlQso(messages, ft8Messages, new SWLQsoList.OnFoundSwlQso() {
|
||||
@Override
|
||||
public void doFound(QSLRecord record) {
|
||||
databaseOpr.addSWL_QSO(record);//把SWL QSO保存到数据库
|
||||
ToastMessage.show(record.swlQSOInfo());
|
||||
}
|
||||
});
|
||||
}
|
||||
//从列表中查找呼号和网格对应关系,并添加到表中
|
||||
getCallsignAndGrid(messages);
|
||||
}
|
||||
|
@ -345,14 +366,14 @@ public class MainViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onAfterGenerate(short[] data) {
|
||||
public void onAfterGenerate(float[] data) {
|
||||
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
|
||||
if (baseRig != null) {
|
||||
if (baseRig.isConnected()) {
|
||||
sendWaveDataRunnable.baseRig=baseRig;
|
||||
sendWaveDataRunnable.data=data;
|
||||
//以线程池的方式执行网络数据包发送
|
||||
sendWaveDataThreadPool.execute(sendWaveDataRunnable);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,7 +382,6 @@ public class MainViewModel extends ViewModel {
|
|||
@Override
|
||||
public void doAfterTransmit(QSLRecord qslRecord) {
|
||||
databaseOpr.addQSL_Callsign(qslRecord);//两个操作,把呼号和QSL记录下来
|
||||
|
||||
if (qslRecord.getToCallsign() != null) {//把通联成功的分区加入到分区列表
|
||||
GeneralVariables.callsignDatabase.getCallsignInformation(qslRecord.getToCallsign()
|
||||
, new OnAfterQueryCallsignLocation() {
|
||||
|
@ -373,7 +393,6 @@ public class MainViewModel extends ViewModel {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -437,7 +456,6 @@ public class MainViewModel extends ViewModel {
|
|||
public void clearTransmittingMessage() {
|
||||
GeneralVariables.transmitMessages.clear();
|
||||
mutableTransmitMessagesCount.postValue(0);
|
||||
//mutableTransmitMessages.postValue(GeneralVariables.transmitMessages);
|
||||
}
|
||||
|
||||
|
||||
|
@ -448,7 +466,7 @@ public class MainViewModel extends ViewModel {
|
|||
*/
|
||||
private void getCallsignAndGrid(ArrayList<Ft8Message> messages) {
|
||||
for (Ft8Message msg : messages) {
|
||||
if (GeneralVariables.checkFun1(msg.extraInfo)) {
|
||||
if (GeneralVariables.checkFun1(msg.extraInfo)) {//检查是不是网格
|
||||
//如果内存表中没有,或不一致,就写入数据库中
|
||||
if (!GeneralVariables.getCallsignHasGrid(msg.getCallsignFrom(), msg.maidenGrid)) {
|
||||
databaseOpr.addCallsignQTH(msg.getCallsignFrom(), msg.maidenGrid);//写数据库
|
||||
|
@ -663,7 +681,7 @@ public class MainViewModel extends ViewModel {
|
|||
baseRig.setOnRigStateChanged(onRigStateChanged);
|
||||
baseRig.setConnector(flexConnector);
|
||||
//
|
||||
new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的,等2秒再设置频率
|
||||
new Handler().postDelayed(new Runnable() {//连接是需要时间的,等2秒再设置频率
|
||||
@Override
|
||||
public void run() {
|
||||
setOperationBand();//设置载波频率
|
||||
|
@ -713,7 +731,7 @@ public class MainViewModel extends ViewModel {
|
|||
case InstructionSet.GUOHE_Q900:
|
||||
baseRig = new GuoHeQ900Rig();//国赫Q900
|
||||
break;
|
||||
case InstructionSet.XIEGUG90S:
|
||||
case InstructionSet.XIEGUG90S://协谷,USB模式
|
||||
baseRig = new XieGuRig(GeneralVariables.civAddress);//协谷G90S
|
||||
break;
|
||||
case InstructionSet.ELECRAFT:
|
||||
|
@ -729,6 +747,9 @@ public class MainViewModel extends ViewModel {
|
|||
baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100
|
||||
break;
|
||||
}
|
||||
|
||||
mutableIsFlexRadio.postValue(GeneralVariables.instructionSet==InstructionSet.FLEX_NETWORK);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -774,7 +795,6 @@ public class MainViewModel extends ViewModel {
|
|||
.getSystemService(Context.AUDIO_SERVICE);
|
||||
if (audioManager == null) return;
|
||||
if (audioManager.isBluetoothScoOn()) {
|
||||
//audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
audioManager.stopBluetoothSco();
|
||||
audioManager.setSpeakerphoneOn(true);//退出耳机模式
|
||||
|
@ -861,11 +881,11 @@ public class MainViewModel extends ViewModel {
|
|||
}
|
||||
private static class SendWaveDataRunnable implements Runnable{
|
||||
BaseRig baseRig;
|
||||
short[] data;
|
||||
float[] data;
|
||||
@Override
|
||||
public void run() {
|
||||
if (baseRig!=null&&data!=null){
|
||||
baseRig.sendWaveData(data);
|
||||
baseRig.sendWaveData(data);//实际生成的数据是12.64+0.04,0.04是生成的0数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* 呼号的哈希码列表。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -12,8 +17,11 @@ public class MessageHashMap extends HashMap<Long,String> {
|
|||
*
|
||||
* @param hashCode 哈希码
|
||||
* @param callsign 呼号
|
||||
* @return false说明已经存在了
|
||||
*/
|
||||
public synchronized void addHash(long hashCode, String callsign) {
|
||||
//if (callsign.length()<2){return;}
|
||||
//if (){return;}
|
||||
if (callsign.equals("CQ")||callsign.equals("QRZ")||callsign.equals("DE")){
|
||||
return;
|
||||
}
|
||||
|
@ -27,7 +35,12 @@ public class MessageHashMap extends HashMap<Long,String> {
|
|||
//检查是否存在这个hash码
|
||||
public boolean checkHash(long hashCode) {
|
||||
return get(hashCode)!=null;
|
||||
|
||||
// for (HashStruct hash : this) {
|
||||
// if (hash.hashCode == hashCode) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
}
|
||||
|
||||
//通过哈希码查呼号
|
||||
|
|
|
@ -7,14 +7,18 @@ import android.os.ParcelUuid;
|
|||
|
||||
import com.bg7yoz.ft8cn.BuildConfig;
|
||||
|
||||
/**
|
||||
* 与蓝牙有关的常量
|
||||
*/
|
||||
|
||||
public class BluetoothConstants {
|
||||
|
||||
// values have to be globally unique
|
||||
static final String INTENT_ACTION_DISCONNECT = BuildConfig.APPLICATION_ID + ".Disconnect";
|
||||
static final String NOTIFICATION_CHANNEL = BuildConfig.APPLICATION_ID + ".Channel";
|
||||
static final String INTENT_CLASS_MAIN_ACTIVITY = BuildConfig.APPLICATION_ID + ".MainActivity";
|
||||
|
||||
|
||||
// values have to be unique within each app
|
||||
static final int NOTIFY_MANAGER_START_FOREGROUND_SERVICE = 1001;
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.bluetooth;
|
||||
|
||||
/**
|
||||
* 蓝牙串口的回调接口
|
||||
* BG7YOZ
|
||||
* 2023-03
|
||||
*/
|
||||
public interface BluetoothSerialListener {
|
||||
void onSerialConnect ();
|
||||
void onSerialConnectError (Exception e);
|
||||
|
|
|
@ -13,7 +13,11 @@ import java.io.IOException;
|
|||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
|
||||
/**
|
||||
* 蓝牙串口有关的服务
|
||||
* BG7YOZ
|
||||
* 2023-03
|
||||
*/
|
||||
public class BluetoothSerialService extends Service implements BluetoothSerialListener {
|
||||
|
||||
public class SerialBinder extends Binder {
|
||||
|
@ -118,11 +122,6 @@ public class BluetoothSerialService extends Service implements BluetoothSerialLi
|
|||
if (connected){
|
||||
disconnect();
|
||||
}
|
||||
//if(connected)
|
||||
// createNotification();
|
||||
// items already in event queue (posted before detach() to mainLooper) will end up in queue1
|
||||
// items occurring later, will be moved directly to queue2
|
||||
// detach() and mainLooper.post run in the main thread, so all items are caught
|
||||
listener = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.bluetooth;
|
||||
/**
|
||||
* 蓝牙串口的SOCKET
|
||||
* BG7YOZ
|
||||
* 2023-03
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.bg7yoz.ft8cn.bluetooth;
|
||||
/**
|
||||
* 蓝牙状态广播类。连接、断开、变化
|
||||
* @author bg7yoz
|
||||
* @writer bg7yoz
|
||||
* @date 2022-07-22
|
||||
*/
|
||||
|
||||
|
@ -92,4 +92,18 @@ public class BluetoothStateBroadcastReceive extends BroadcastReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
// static final int PROFILE_HEADSET = 0;
|
||||
// static final int PROFILE_A2DP = 1;
|
||||
// static final int PROFILE_OPP = 2;
|
||||
// static final int PROFILE_HID = 3;
|
||||
// static final int PROFILE_PANU = 4;
|
||||
// static final int PROFILE_NAP = 5;
|
||||
// static final int PROFILE_A2DP_SINK = 6;
|
||||
//
|
||||
// private boolean checkBluetoothClass(BluetoothClass bluetoothClass,int proFile){
|
||||
// if (proFile==PROFILE_A2DP){
|
||||
// bluetoothClass.hasService(BluetoothClass.Service.RENDER);
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.callsign;
|
||||
/**
|
||||
* 用于呼号归属地查询的数据库操作,数据库采用内存方式。来源是CTY.DAT
|
||||
* @author BG7YOZ
|
||||
* 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
|
@ -182,6 +187,29 @@ public class CallsignDatabase extends SQLiteOpenHelper {
|
|||
@SuppressLint("Range")
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
// String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" +
|
||||
// "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" +
|
||||
// "order by LENGTH(callsign) desc\n" +
|
||||
// "LIMIT 1";
|
||||
//
|
||||
// Cursor cursor = db.rawQuery(querySQL, new String[]{sqlParameter.toUpperCase(), sqlParameter.toUpperCase()});
|
||||
// if (cursor.moveToFirst()) {
|
||||
// CallsignInfo callsignInfo = new CallsignInfo();
|
||||
// callsignInfo.CallSign = sqlParameter.toUpperCase();
|
||||
// callsignInfo.CountryNameEn = cursor.getString(cursor.getColumnIndex("CountryNameEn"));
|
||||
// callsignInfo.CountryNameCN = cursor.getString(cursor.getColumnIndex("CountryNameCN"));
|
||||
// callsignInfo.CQZone = cursor.getInt(cursor.getColumnIndex("CQZone"));
|
||||
// callsignInfo.ITUZone = cursor.getInt(cursor.getColumnIndex("ITUZone"));
|
||||
// callsignInfo.Continent = cursor.getString(cursor.getColumnIndex("Continent"));
|
||||
// callsignInfo.Latitude = cursor.getFloat(cursor.getColumnIndex("Latitude"));
|
||||
// callsignInfo.Longitude = cursor.getFloat(cursor.getColumnIndex("Longitude"));
|
||||
// callsignInfo.GMT_offset = cursor.getFloat(cursor.getColumnIndex("GMT_offset"));
|
||||
// callsignInfo.DXCC = cursor.getString(cursor.getColumnIndex("DXCC"));
|
||||
// if (afterQueryCallsignLocation!=null){
|
||||
// afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo);
|
||||
// }
|
||||
// }
|
||||
// cursor.close();
|
||||
CallsignInfo callsignInfo = getCallsignInfo(db, sqlParameter);
|
||||
if (callsignInfo != null && afterQueryCallsignLocation != null) {
|
||||
afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.callsign;
|
||||
/**
|
||||
* 预处理呼号数据库的文件操作,呼号的来源是CTY.DAT
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.callsign;
|
||||
/**
|
||||
* 呼号信息类,用于归属地查询
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
package com.bg7yoz.ft8cn.callsign;
|
||||
|
||||
/**
|
||||
* 用于查询呼号归属地的回调接口,因为数据库操作采用异步方式
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*
|
||||
*/
|
||||
public interface OnAfterQueryCallsignLocation {
|
||||
void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
|
||||
/**
|
||||
* 用于连接电台的基础类,蓝牙、USB线、FLEX网络、ICOM网络都是继承于此
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.rigs.OnConnectReceiveData;
|
||||
import com.bg7yoz.ft8cn.rigs.OnRigStateChanged;
|
||||
|
@ -69,7 +74,7 @@ public class BaseRigConnector {
|
|||
onConnectReceiveData=receiveData;
|
||||
}
|
||||
|
||||
public void sendWaveData(short[] data){
|
||||
public void sendWaveData(float[] data){
|
||||
//留给网络方式发送音频流
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* 用于蓝牙连接的Connector,继承于BaseRigConnector
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
|
|
|
@ -7,7 +7,10 @@ import com.bg7yoz.ft8cn.database.ControlMode;
|
|||
import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
|
||||
|
||||
/**
|
||||
* 有线连接方式的Connector,这里是指USB方式的
|
||||
* 有线连接方式的Connector,这里是指USB方式的,继承于BaseRigConnector
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class CableConnector extends BaseRigConnector {
|
||||
private static final String TAG="CableConnector";
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* 用于USB串口操作的类。USB串口驱动在serialport目录中,主要是CDC、CH34x、CP21xx、FTDI等。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
|
||||
/**
|
||||
* 连接的模式
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class ConnectMode {
|
||||
public static final int USB_CABLE=0;
|
||||
public static final int BLUE_TOOTH=1;
|
||||
|
|
|
@ -4,10 +4,13 @@ import android.annotation.SuppressLint;
|
|||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.bg7yoz.ft8cn.GeneralVariables;
|
||||
import com.bg7yoz.ft8cn.R;
|
||||
import com.bg7yoz.ft8cn.flex.FlexCommand;
|
||||
import com.bg7yoz.ft8cn.flex.FlexMeters;
|
||||
import com.bg7yoz.ft8cn.flex.FlexMeterInfos;
|
||||
import com.bg7yoz.ft8cn.flex.FlexMeterList;
|
||||
import com.bg7yoz.ft8cn.flex.FlexRadio;
|
||||
import com.bg7yoz.ft8cn.flex.RadioTcpClient;
|
||||
import com.bg7yoz.ft8cn.flex.VITA;
|
||||
|
@ -18,14 +21,19 @@ import java.io.DataInputStream;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Flex的Connector,采用网络连接方式,完善中...
|
||||
* @author BG7YOZ
|
||||
* 有线连接方式的Connector,这里是指USB方式的
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class FlexConnector extends BaseRigConnector {
|
||||
|
||||
public MutableLiveData<FlexMeterList> mutableMeterList=new MutableLiveData<>();
|
||||
public FlexMeterInfos flexMeterInfos =new FlexMeterInfos("");
|
||||
public FlexMeterList meterList=new FlexMeterList();
|
||||
public interface OnWaveDataReceived{
|
||||
void OnDataReceived(int bufferLen,float[] buffer);
|
||||
}
|
||||
public int maxRfPower;
|
||||
public int maxTunePower;
|
||||
|
||||
private static final String TAG = "CableConnector";
|
||||
|
||||
|
@ -37,8 +45,8 @@ public class FlexConnector extends BaseRigConnector {
|
|||
public FlexConnector(Context context, FlexRadio flexRadio, int controlMode) {
|
||||
super(controlMode);
|
||||
this.flexRadio = flexRadio;
|
||||
|
||||
|
||||
maxTunePower=GeneralVariables.flexMaxTunePower;
|
||||
maxRfPower=GeneralVariables.flexMaxRfPower;
|
||||
setFlexRadioInterface();
|
||||
//connect();
|
||||
}
|
||||
|
@ -77,17 +85,11 @@ public class FlexConnector extends BaseRigConnector {
|
|||
|
||||
@Override
|
||||
public void onReceiveMeter(VITA vita) {
|
||||
//mutableVita.postValue(vita.showHeadStr()+"\n"+vita.showPayloadHex());
|
||||
// float val;
|
||||
// byte data[]=new byte[4];
|
||||
// data[0]=vita.payload[0];
|
||||
// data[1]=vita.payload[1];
|
||||
// data[2]=vita.payload[2];
|
||||
// data[3]=vita.payload[3];
|
||||
////
|
||||
// ToastMessage.show(String.format("%s, %d,%e",vita.showPayloadHex()
|
||||
// ,byteDataTo16BitData(data)[1]
|
||||
// ,Float.intBitsToFloat(byteDataTo16BitData(data)[1])));
|
||||
//Log.e(TAG, "onReceiveMeter: "+vita.showPayloadHex() );
|
||||
meterList.setMeters(vita.payload,flexMeterInfos);
|
||||
|
||||
mutableMeterList.postValue(meterList);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,13 +113,15 @@ public class FlexConnector extends BaseRigConnector {
|
|||
Log.e(TAG, "onResponse: "+response.rawData );
|
||||
|
||||
if (response.flexCommand== FlexCommand.METER_LIST){
|
||||
Log.e(TAG, "onResponse: meter exContent:"
|
||||
+response.exContent.substring(response.exContent.indexOf("meter ")+"meter ".length()));
|
||||
|
||||
FlexMeters flexMeters=new FlexMeters(response.exContent);
|
||||
flexMeters.getAllMeters();
|
||||
//FlexMeters flexMeters=new FlexMeters(response.exContent);
|
||||
flexMeterInfos.setMeterInfos(response.exContent);
|
||||
flexRadio.commandSubMeterAll();//显示全部仪表消息
|
||||
//flexMeters.getAllMeters();
|
||||
//Log.e(TAG, "onResponse: ----->>>"+flexMeters.getAllMeters() );
|
||||
}
|
||||
if (response.flexCommand==FlexCommand.STREAM_CREATE_DAX_TX){
|
||||
flexRadio.streamTxId=response.daxTxStreamId;
|
||||
}
|
||||
|
||||
// if (response.flexCommand== FlexCommand.METER_LIST){
|
||||
// Log.e(TAG, "onResponse: ."+response.rawData.replace("#","\n") );
|
||||
|
@ -150,28 +154,35 @@ public class FlexConnector extends BaseRigConnector {
|
|||
flexRadio.commandSubDaxAll();//注册全部DAX流
|
||||
|
||||
|
||||
//flexRadio.commandSubMeterAll();//显示全部仪表消息
|
||||
|
||||
flexRadio.commandClientSetEnforceNetWorkGui();//对网络MTU做设置
|
||||
|
||||
//flexRadio.commandSliceList();//列slice
|
||||
flexRadio.commandSliceCreate();//创建slice
|
||||
flexRadio.commandMeterList();//列一下仪表
|
||||
//flexRadio.commandSubMeterById(5);
|
||||
|
||||
//flexRadio.commandSliceTune(0, "21.074");//设置默认频率
|
||||
flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f));
|
||||
|
||||
|
||||
|
||||
flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX
|
||||
flexRadio.commandUdpPort();//设置UDP端口
|
||||
|
||||
|
||||
flexRadio.commandStreamCreateDaxRx(1);//创建流数据到DAX通道1
|
||||
flexRadio.commandStreamCreateDaxTx(1);//创建流数据到DAX通道1
|
||||
//TODO 是否设置??? dax tx T 或者 dax tx 1
|
||||
flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f));
|
||||
flexRadio.commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式
|
||||
flexRadio.commandSetFilter(0, 0, 3000);//设置滤波为3000HZ
|
||||
|
||||
flexRadio.commandSetRfPower(0);//设置发射功率
|
||||
Log.e(TAG, "onConnectSuccess: ------Meter List" );
|
||||
ToastMessage.show("------");
|
||||
|
||||
flexRadio.commandMeterList();//列一下仪表
|
||||
//flexRadio.commandSubMeterAll();//显示全部仪表消息
|
||||
|
||||
setMaxRfPower(maxRfPower);//设置发射功率
|
||||
setMaxTunePower(maxTunePower);//设置调谐功率
|
||||
|
||||
//flexRadio.commandSubMeterById(5);//列指定的仪表
|
||||
|
||||
//flexRadio.commandSliceSetNR(0, true);
|
||||
//flexRadio.commandSliceSetNB(0, true);
|
||||
|
||||
|
@ -203,7 +214,30 @@ public class FlexConnector extends BaseRigConnector {
|
|||
});
|
||||
|
||||
}
|
||||
public void setMaxRfPower(int power){
|
||||
maxRfPower=power;
|
||||
GeneralVariables.flexMaxRfPower=power;
|
||||
flexRadio.commandSetRfPower(maxRfPower);//设置发射功率
|
||||
|
||||
}
|
||||
public void setMaxTunePower(int power){
|
||||
maxTunePower=power;
|
||||
GeneralVariables.flexMaxTunePower=power;
|
||||
flexRadio.commandSetTunePower(maxTunePower);//设置调谐功率
|
||||
|
||||
}
|
||||
public void startATU(){
|
||||
flexRadio.commandStartATU();
|
||||
}
|
||||
public void tuneOnOff(boolean on){
|
||||
flexRadio.commandTuneTransmitOnOff(on);
|
||||
}
|
||||
public void subAllMeters(){
|
||||
if (flexMeterInfos.size()==0) {
|
||||
flexRadio.commandMeterList();//列一下仪表
|
||||
flexRadio.commandSubMeterAll();//显示全部仪表消息
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendData(byte[] data) {
|
||||
|
@ -213,13 +247,8 @@ public class FlexConnector extends BaseRigConnector {
|
|||
|
||||
@Override
|
||||
public void setPttOn(boolean on) {
|
||||
//只处理RTS和DTR
|
||||
// switch (getControlMode()){
|
||||
// case ControlMode.DTR: cableSerialPort.setDTR_On(on);//打开和关闭DTR
|
||||
// break;
|
||||
// case ControlMode.RTS:cableSerialPort.setRTS_On(on);//打开和关闭RTS
|
||||
// break;
|
||||
// }
|
||||
flexRadio.isPttOn=on;
|
||||
flexRadio.commandPTTOnOff(on);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -227,6 +256,13 @@ public class FlexConnector extends BaseRigConnector {
|
|||
//cableSerialPort.sendData(command);//以CAT指令发送PTT
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendWaveData(float[] data) {
|
||||
Log.e(TAG, "sendWaveData: flexConnector:"+data.length );
|
||||
flexRadio.sendWaveData(data);
|
||||
//super.sendWaveData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() {
|
||||
super.connect();
|
||||
|
@ -252,7 +288,7 @@ public class FlexConnector extends BaseRigConnector {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取单声道的数据,24000hz采样率改为12000采样率
|
||||
* 获取单声道的数据,24000hz采样率改为12000采样率,把立体声改为单声道
|
||||
* @param bytes 原始声音数据
|
||||
* @return 单声道数据
|
||||
*/
|
||||
|
@ -263,13 +299,12 @@ public class FlexConnector extends BaseRigConnector {
|
|||
try {
|
||||
float f1,f2;
|
||||
f1=dis.readFloat();
|
||||
f2=dis.readFloat();//放弃一个声道
|
||||
dis.readFloat();//放弃一个声道
|
||||
f2=dis.readFloat();
|
||||
floats[i] = Math.max(f1,f2);//取最大值
|
||||
dis.readFloat();//放弃1个声道
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "getFloat: ------>>" + e.getMessage());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* ICom网络方式的连接器。
|
||||
* 注:ICom网络方式的音频数据包是Int类型,需要转换成Float类型
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.icom.IComWifiRig;
|
||||
|
||||
|
@ -42,7 +49,7 @@ public class IComWifiConnector extends BaseRigConnector{
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sendWaveData(short[] data) {
|
||||
public void sendWaveData(float[] data) {
|
||||
if (iComWifiRig.opened) {
|
||||
iComWifiRig.sendWaveData(data);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
|
||||
/**
|
||||
* 连接器的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public interface OnConnectorStateChanged {
|
||||
void onDisconnected();
|
||||
void onConnected();
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
package com.bg7yoz.ft8cn.count;
|
||||
/**
|
||||
* 用于通联日志统计的的数据库操作。
|
||||
* 注:目前归属地的统计,是基于网格的,如果基于呼号的前缀统计,虽然准确,但统计速度太慢,用户的交互效果不好。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.database.Cursor;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.count;
|
||||
/**
|
||||
* 通联日志的统计界面
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.count;
|
||||
|
||||
/**
|
||||
* 用于列出统计结果的Adapter
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
|
||||
/**
|
||||
* 控制的模式
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class ControlMode {
|
||||
public static final int VOX=0;
|
||||
public static final int CAT=1;
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
/**
|
||||
* 用于数据库操作的类。绝大多数的操作都是采用异步方式(于HTTP有关的除外)。
|
||||
* 数据库已经经历的多个版本,所以有onUpgrade方法。
|
||||
* 配置信息也保存在数据库中
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -20,6 +29,7 @@ import com.bg7yoz.ft8cn.log.QSLCallsignRecord;
|
|||
import com.bg7yoz.ft8cn.log.QSLRecord;
|
||||
import com.bg7yoz.ft8cn.log.QSLRecordStr;
|
||||
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
|
||||
import com.bg7yoz.ft8cn.timer.UtcTimer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.json.JSONArray;
|
||||
|
@ -33,6 +43,7 @@ import java.util.HashMap;
|
|||
|
||||
public class DatabaseOpr extends SQLiteOpenHelper {
|
||||
private static final String TAG = "DatabaseOpr";
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static DatabaseOpr instance;
|
||||
private final Context context;
|
||||
private SQLiteDatabase db;
|
||||
|
@ -40,7 +51,7 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
|
||||
public static DatabaseOpr getInstance(@Nullable Context context, @Nullable String databaseName) {
|
||||
if (instance == null) {
|
||||
instance = new DatabaseOpr(context, databaseName, null, 12);
|
||||
instance = new DatabaseOpr(context, databaseName, null, 13);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
@ -79,6 +90,9 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
//创建呼号与网格对应关系表
|
||||
createCallsignQTHTables(sqLiteDatabase);
|
||||
|
||||
//创建SWL相关的表
|
||||
createSWLTables(sqLiteDatabase);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -99,6 +113,9 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
//创建呼号与网格对应关系表
|
||||
createCallsignQTHTables(sqLiteDatabase);
|
||||
|
||||
//创建SWL相关的表
|
||||
createSWLTables(sqLiteDatabase);
|
||||
|
||||
//删除DXCC呼号列表中的等号
|
||||
//deleteDxccPrefixEqual(sqLiteDatabase);
|
||||
}
|
||||
|
@ -155,9 +172,11 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
private void deleteDxccPrefixEqual(SQLiteDatabase db){
|
||||
|
||||
private void deleteDxccPrefixEqual(SQLiteDatabase db) {
|
||||
db.execSQL("DELETE from dxcc_prefix where prefix LIKE \"=%\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建通联日志表
|
||||
*/
|
||||
|
@ -324,6 +343,47 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private void createSWLTables(SQLiteDatabase sqLiteDatabase) {
|
||||
if (!checkTableExists(sqLiteDatabase, "SWLMessages")) {
|
||||
sqLiteDatabase.execSQL("CREATE TABLE SWLMessages (\n" +
|
||||
"\tID INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
|
||||
"\tI3 INTEGER,\n" +
|
||||
"\tN3 INTEGER,\n" +
|
||||
"\tProtocol TEXT,\n" +
|
||||
"\tUTC TEXT,\n" +
|
||||
"\tSNR INTEGER,\n" +
|
||||
"\tTIME_SEC REAL,\n" +
|
||||
"\tFREQ INTEGER,\n" +
|
||||
"\tCALL_TO TEXT,\n" +
|
||||
"\tCALL_FROM TEXT,\n" +
|
||||
"\tEXTRAL TEXT,\n" +
|
||||
"\tREPORT INTEGER,\n" +
|
||||
"\tBAND INTEGER\n" +
|
||||
")");
|
||||
sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_CALL_TO_IDX " +
|
||||
"ON SWLMessages (CALL_TO,CALL_FROM)");
|
||||
sqLiteDatabase.execSQL("CREATE INDEX SWLMessages_UTC_IDX ON SWLMessages (UTC)");
|
||||
}
|
||||
if (!checkTableExists(sqLiteDatabase, "SWLQSOTable")) {
|
||||
sqLiteDatabase.execSQL("CREATE TABLE SWLQSOTable (\n" +
|
||||
"\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
|
||||
"\t\"call\" TEXT,\n" +
|
||||
"\tgridsquare TEXT,\n" +
|
||||
"\tmode TEXT,\n" +
|
||||
"\trst_sent TEXT,\n" +
|
||||
"\trst_rcvd TEXT,\n" +
|
||||
"\tqso_date TEXT,\n" +
|
||||
"\ttime_on TEXT,\n" +
|
||||
"\tqso_date_off TEXT,\n" +
|
||||
"\ttime_off TEXT,\n" +
|
||||
"\tband TEXT,\n" +
|
||||
"\tfreq TEXT,\n" +
|
||||
"\tstation_callsign TEXT,\n" +
|
||||
"\tmy_gridsquare TEXT,\n" +
|
||||
"\tcomment TEXT)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void loadItuDataFromFile(SQLiteDatabase db) {
|
||||
AssetManager assetManager = context.getAssets();
|
||||
|
@ -424,7 +484,7 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
dxcc.prefix.add(prefix.getString(j));
|
||||
}
|
||||
dxccObjects.add(dxcc);
|
||||
Log.e(TAG, "loadDataFromFile: id:" + dxcc.id + " dxcc:" + dxcc.dxcc);
|
||||
//Log.e(TAG, "loadDataFromFile: id:" + dxcc.id + " dxcc:" + dxcc.dxcc);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
|
@ -465,8 +525,8 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
new WriteConfig(db, KeyName, Value, onAfterWriteConfig).execute();
|
||||
}
|
||||
|
||||
public void writeMessage(ArrayList<Ft8Message> messages, String callSign) {
|
||||
new WriteMessages(db, messages, callSign).execute();
|
||||
public void writeMessage(ArrayList<Ft8Message> messages) {
|
||||
new WriteMessages(db, messages).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -478,10 +538,23 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
new GetFollowCallSigns(db, onAffterQueryFollowCallsigns).execute();
|
||||
}
|
||||
|
||||
public void getMessageLogTotal(OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) {
|
||||
new GetMessageLogTotal(db, onAffterQueryFollowCallsigns).execute();
|
||||
/**
|
||||
* 查询SWL MESSAGE各BAND的数量
|
||||
* @param onAfterQueryFollowCallsigns 回调
|
||||
*/
|
||||
public void getMessageLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) {
|
||||
new GetMessageLogTotal(db, onAfterQueryFollowCallsigns).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询SWL QSO的在各个月的数量
|
||||
* @param onAfterQueryFollowCallsigns 回调
|
||||
*/
|
||||
public void getSWLQsoLogTotal(OnAfterQueryFollowCallsigns onAfterQueryFollowCallsigns) {
|
||||
new GetSWLQsoTotal(db, onAfterQueryFollowCallsigns).execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 向数据库中添加关注的呼号
|
||||
*
|
||||
|
@ -510,12 +583,24 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
db.execSQL("delete from Messages ");
|
||||
db.execSQL("delete from SWLMessages ");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除SWL QSO日志
|
||||
*/
|
||||
public void clearSWLQsoData() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
db.execSQL("delete from SWLQSOTable ");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
/**
|
||||
* 把通联成功的呼号写到数据库中
|
||||
* 把通联成功的日志和呼号写到数据库中
|
||||
*
|
||||
* @param qslRecord 通联记录
|
||||
*/
|
||||
|
@ -523,6 +608,14 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
new AddQSL_Info(this, qslRecord).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* 把SWL的QSO保存到数据库,SWL的QSO标准:至少要有双方的信号报告。不包含自己的呼号。
|
||||
* @param qslRecord 通联日志记录
|
||||
*/
|
||||
public void addSWL_QSO(QSLRecord qslRecord) {
|
||||
new Add_SWL_QSO_Info(this, qslRecord).execute();
|
||||
}
|
||||
|
||||
//删除数据库中关注的呼号
|
||||
public void deleteFollowCallsign(String callsign) {
|
||||
new DeleteFollowCallsign(db, callsign).execute();
|
||||
|
@ -547,8 +640,8 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
* @param callsign 呼号
|
||||
* @param onQueryQSLCallsign 回调
|
||||
*/
|
||||
public void getQSLCallsignsByCallsign(String callsign, int filter, OnQueryQSLCallsign onQueryQSLCallsign) {
|
||||
new GetQLSCallsignByCallsign(db, callsign, filter, onQueryQSLCallsign).execute();
|
||||
public void getQSLCallsignsByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLCallsign onQueryQSLCallsign) {
|
||||
new GetQLSCallsignByCallsign(showAll,offset,db, callsign, filter, onQueryQSLCallsign).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -567,8 +660,8 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
* @param callsign 呼号
|
||||
* @param onQueryQSLRecordCallsign 回调
|
||||
*/
|
||||
public void getQSLRecordByCallsign(String callsign, int filter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) {
|
||||
new GetQSLByCallsign(db, callsign, filter, onQueryQSLRecordCallsign).execute();
|
||||
public void getQSLRecordByCallsign(boolean showAll,int offset,String callsign, int filter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) {
|
||||
new GetQSLByCallsign(showAll,offset,db, callsign, filter, onQueryQSLRecordCallsign).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -977,26 +1070,24 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
static class WriteMessages extends AsyncTask<Void, Void, Void> {
|
||||
private final SQLiteDatabase db;
|
||||
private ArrayList<Ft8Message> messages;
|
||||
private String myCallsign;
|
||||
|
||||
public WriteMessages(SQLiteDatabase db, ArrayList<Ft8Message> messages, String callsign) {
|
||||
public WriteMessages(SQLiteDatabase db, ArrayList<Ft8Message> messages) {
|
||||
this.db = db;
|
||||
this.messages = messages;
|
||||
this.myCallsign = callsign;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
String sql = "INSERT INTO Messages(I3,N3,Protocol,UTC,SNR,TIME_SEC,FREQ,CALL_FROM" +
|
||||
",CALL_TO,EXTRAL,REPORT,BAND)" +
|
||||
String sql = "INSERT INTO SWLMessages(I3,N3,Protocol,UTC,SNR,TIME_SEC,FREQ,CALL_FROM" +
|
||||
",CALL_TO,EXTRAL,REPORT,BAND)\n" +
|
||||
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
|
||||
for (Ft8Message message : messages) {//只对与我有关的消息做保存
|
||||
if (message.callsignTo.equals(myCallsign) || message.callsignFrom.equals(myCallsign)) {
|
||||
db.execSQL(sql, new Object[]{message.i3, message.n3, "FT8", message.utcTime
|
||||
, message.snr, message.time_sec, Math.round(message.freq_hz)
|
||||
, message.callsignFrom, message.callsignTo, message.extraInfo
|
||||
, message.report, message.band});
|
||||
}
|
||||
db.execSQL(sql, new Object[]{message.i3, message.n3, "FT8"
|
||||
,UtcTimer.getDatetimeYYYYMMDD_HHMMSS(message.utcTime)
|
||||
, message.snr, message.time_sec, Math.round(message.freq_hz)
|
||||
, message.callsignFrom, message.callsignTo, message.extraInfo
|
||||
, message.report, message.band});
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1045,6 +1136,43 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
}
|
||||
|
||||
static class Add_SWL_QSO_Info extends AsyncTask<Void, Void, Void>{
|
||||
private final DatabaseOpr databaseOpr;
|
||||
private QSLRecord qslRecord;
|
||||
public Add_SWL_QSO_Info(DatabaseOpr opr, QSLRecord qslRecord) {
|
||||
this.databaseOpr = opr;
|
||||
this.qslRecord = qslRecord;
|
||||
}
|
||||
@SuppressLint("Range")
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
String querySQL;
|
||||
querySQL = "INSERT INTO SWLQSOTable(call, gridsquare, mode, rst_sent, rst_rcvd, qso_date, " +
|
||||
"time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare," +
|
||||
"comment)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
||||
|
||||
databaseOpr.db.execSQL(querySQL, new String[]{qslRecord.getToCallsign()
|
||||
, qslRecord.getToMaidenGrid()
|
||||
, qslRecord.getMode()
|
||||
, String.valueOf(qslRecord.getSendReport())
|
||||
, String.valueOf(qslRecord.getReceivedReport())
|
||||
, qslRecord.getQso_date()
|
||||
, qslRecord.getTime_on()
|
||||
|
||||
, qslRecord.getQso_date_off()
|
||||
, qslRecord.getTime_off()
|
||||
, qslRecord.getBandLength()//波长//RigOperationConstant.getMeterFromFreq(qslRecord.getBandFreq())
|
||||
, BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq())
|
||||
, qslRecord.getMyCallsign()
|
||||
, qslRecord.getMyMaidenGrid()
|
||||
, qslRecord.getComment()});
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 把QSL成功的呼号写到库中
|
||||
*/
|
||||
|
@ -1128,7 +1256,7 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
@Override
|
||||
@SuppressLint({"Range", "DefaultLocale"})
|
||||
protected Void doInBackground(Void... voids) {
|
||||
String querySQL = "SELECT BAND ,count(*) as c from Messages m group by BAND order by BAND ";
|
||||
String querySQL = "SELECT BAND ,count(*) as c from SWLMessages m group by BAND order by BAND ";
|
||||
Cursor cursor = db.rawQuery(querySQL, new String[]{});
|
||||
ArrayList<String> callsigns = new ArrayList<>();
|
||||
callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total));
|
||||
|
@ -1137,7 +1265,7 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
while (cursor.moveToNext()) {
|
||||
long s = cursor.getLong(cursor.getColumnIndex("BAND")); //获取频段
|
||||
int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量
|
||||
callsigns.add(String.format("%.3fMhz \t %d",s/1000000f, total));
|
||||
callsigns.add(String.format("%.3fMhz \t %d", s / 1000000f, total));
|
||||
sum = sum + total;
|
||||
}
|
||||
callsigns.add(String.format("-----------Total %d -----------", sum));
|
||||
|
@ -1150,6 +1278,43 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
|
||||
|
||||
static class GetSWLQsoTotal extends AsyncTask<Void, Void, Void> {
|
||||
private final SQLiteDatabase db;
|
||||
private final OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns;
|
||||
|
||||
public GetSWLQsoTotal(SQLiteDatabase db, OnAfterQueryFollowCallsigns onAffterQueryFollowCallsigns) {
|
||||
this.db = db;
|
||||
this.onAffterQueryFollowCallsigns = onAffterQueryFollowCallsigns;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint({"Range", "DefaultLocale"})
|
||||
protected Void doInBackground(Void... voids) {
|
||||
String querySQL = "select count(*) as c,substr(qso_date_off,1,6) as t \n" +
|
||||
"from SWLQSOTable s\n" +
|
||||
"group by substr(qso_date_off,1,6)";
|
||||
Cursor cursor = db.rawQuery(querySQL, new String[]{});
|
||||
ArrayList<String> callsigns = new ArrayList<>();
|
||||
//callsigns.add(GeneralVariables.getStringFromResource(R.string.band_total));
|
||||
callsigns.add("---------------------------------------");
|
||||
int sum = 0;
|
||||
while (cursor.moveToNext()) {
|
||||
String date = cursor.getString(cursor.getColumnIndex("t")); //获取频段
|
||||
int total = cursor.getInt(cursor.getColumnIndex("c")); //获取数量
|
||||
callsigns.add(String.format("%s \t %d ", date, total));
|
||||
sum = sum + total;
|
||||
}
|
||||
callsigns.add(String.format("-----------Total %d -----------", sum));
|
||||
cursor.close();
|
||||
if (onAffterQueryFollowCallsigns != null) {
|
||||
onAffterQueryFollowCallsigns.doOnAfterQueryFollowCallsigns(callsigns);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从数据库中获取关注的呼号类
|
||||
*/
|
||||
|
@ -1248,12 +1413,16 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
|
||||
static class GetQSLByCallsign extends AsyncTask<Void, Void, Void> {
|
||||
boolean showAll;
|
||||
int offset;
|
||||
SQLiteDatabase db;
|
||||
String callsign;
|
||||
int filter;
|
||||
OnQueryQSLRecordCallsign onQueryQSLRecordCallsign;
|
||||
|
||||
public GetQSLByCallsign(SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) {
|
||||
public GetQSLByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLRecordCallsign onQueryQSLRecordCallsign) {
|
||||
this.showAll=showAll;
|
||||
this.offset=offset;
|
||||
this.db = db;
|
||||
this.callsign = callsign;
|
||||
this.filter = queryFilter;
|
||||
|
@ -1274,9 +1443,14 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
default:
|
||||
filterStr = "";
|
||||
}
|
||||
String limitStr="";
|
||||
if (!showAll){
|
||||
limitStr="limit 100 offset "+offset;
|
||||
}
|
||||
String querySQL = "select * from QSLTable where ([call] like ?) \n" +
|
||||
filterStr +
|
||||
" order by ID desc";
|
||||
" order by ID desc\n"+
|
||||
limitStr;
|
||||
Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"});
|
||||
ArrayList<QSLRecordStr> records = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
|
@ -1320,8 +1494,12 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
String callsign;
|
||||
int filter;
|
||||
OnQueryQSLCallsign onQueryQSLCallsign;
|
||||
int offset;
|
||||
boolean showAll;
|
||||
|
||||
public GetQLSCallsignByCallsign(SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLCallsign onQueryQSLCallsign) {
|
||||
public GetQLSCallsignByCallsign(boolean showAll,int offset,SQLiteDatabase db, String callsign, int queryFilter, OnQueryQSLCallsign onQueryQSLCallsign) {
|
||||
this.showAll=showAll;
|
||||
this.offset=offset;
|
||||
this.db = db;
|
||||
this.callsign = callsign;
|
||||
this.filter = queryFilter;
|
||||
|
@ -1342,6 +1520,10 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
default:
|
||||
filterStr = "";
|
||||
}
|
||||
String limitStr="";
|
||||
if (!showAll){
|
||||
limitStr="limit 100 offset "+offset;
|
||||
}
|
||||
String querySQL = "select q.[call] as callsign ,q.gridsquare as grid" +
|
||||
",q.band||\"(\"||q.freq||\" Mhz)\" as band \n" +
|
||||
",q.qso_date as last_time ,q.mode ,q.isQSL,q.isLotW_QSL\n" +
|
||||
|
@ -1351,7 +1533,8 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
"group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.band\n" +
|
||||
",q.mode,q.isQSL,q.isLotW_QSL\n" +
|
||||
"HAVING q.qso_date =MAX(q2.qso_date) \n" +
|
||||
"order by q.qso_date desc";
|
||||
"order by q.qso_date desc\n"+
|
||||
limitStr;
|
||||
|
||||
|
||||
Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"});
|
||||
|
@ -1515,9 +1698,6 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部配置信息
|
||||
*/
|
||||
static class GetAllConfigParameter extends AsyncTask<Void, Void, Void> {
|
||||
private final SQLiteDatabase db;
|
||||
private OnAfterQueryConfig onAfterQueryConfig;
|
||||
|
@ -1539,9 +1719,131 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
return result;
|
||||
}
|
||||
|
||||
@SuppressLint("Range")
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
//TODO 此处代码效率较低,已经在0.87中解决。
|
||||
|
||||
String querySQL = "select keyName,Value from config ";
|
||||
Cursor cursor = db.rawQuery(querySQL, null);
|
||||
while (cursor.moveToNext()) {
|
||||
@SuppressLint("Range")
|
||||
//String result = "";
|
||||
String result = cursor.getString(cursor.getColumnIndex("Value"));
|
||||
String name = cursor.getString(cursor.getColumnIndex("KeyName"));
|
||||
|
||||
if (name.equalsIgnoreCase("grid")) {
|
||||
GeneralVariables.setMyMaidenheadGrid(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("callsign")) {
|
||||
GeneralVariables.myCallsign = result;
|
||||
String callsign = GeneralVariables.myCallsign;
|
||||
if (callsign.length() > 0) {
|
||||
Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign);
|
||||
Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign);
|
||||
Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign);
|
||||
}
|
||||
}
|
||||
if (name.equalsIgnoreCase("toModifier")) {
|
||||
GeneralVariables.toModifier = result;
|
||||
}
|
||||
if (name.equalsIgnoreCase("freq")) {
|
||||
float freq = 1000;
|
||||
try {
|
||||
freq = Float.parseFloat(result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "doInBackground: " + e.getMessage());
|
||||
}
|
||||
//GeneralVariables.setBaseFrequency(result.equals("") ? 1000 : Float.parseFloat(result));
|
||||
GeneralVariables.setBaseFrequency(freq);
|
||||
}
|
||||
if (name.equalsIgnoreCase("synFreq")) {
|
||||
GeneralVariables.synFrequency = !(result.equals("") || result.equals("0"));
|
||||
}
|
||||
if (name.equalsIgnoreCase("transDelay")) {
|
||||
if (result.matches("^\\d{1,4}$")) {//正则表达式,1-4位长度的数字
|
||||
GeneralVariables.transmitDelay = Integer.parseInt(result);
|
||||
} else {
|
||||
GeneralVariables.transmitDelay = FT8Common.FT8_TRANSMIT_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
if (name.equalsIgnoreCase("civ")) {
|
||||
GeneralVariables.civAddress = result.equals("") ? 0xa4 : Integer.parseInt(result, 16);
|
||||
}
|
||||
if (name.equalsIgnoreCase("baudRate")) {
|
||||
GeneralVariables.baudRate = result.equals("") ? 19200 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("bandFreq")) {
|
||||
//--todo---把波段的索引数改成频率。用bandFreq值
|
||||
GeneralVariables.band = result.equals("") ? 14074000 : Long.valueOf(result);
|
||||
GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band);
|
||||
}
|
||||
if (name.equalsIgnoreCase("ctrMode")) {
|
||||
GeneralVariables.controlMode = result.equals("") ? ControlMode.VOX : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("model")) {//电台型号
|
||||
GeneralVariables.modelNo = result.equals("") ? 0 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("instruction")) {//指令集
|
||||
GeneralVariables.instructionSet = result.equals("") ? 0 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("launchSupervision")) {//发射监管
|
||||
GeneralVariables.launchSupervision = result.equals("") ?
|
||||
GeneralVariables.DEFAULT_LAUNCH_SUPERVISION : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("noReplyLimit")) {//
|
||||
GeneralVariables.noReplyLimit = result.equals("") ? 0 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("autoFollowCQ")) {//自动关注CQ
|
||||
GeneralVariables.autoFollowCQ = (result.equals("") || result.equals("1"));
|
||||
}
|
||||
if (name.equalsIgnoreCase("autoCallFollow")) {//自动呼叫关注
|
||||
GeneralVariables.autoCallFollow = (result.equals("") || result.equals("1"));
|
||||
}
|
||||
if (name.equalsIgnoreCase("pttDelay")) {//ptt延时设置
|
||||
GeneralVariables.pttDelay = result.equals("") ? 100 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("icomIp")) {//IcomIp地址
|
||||
GeneralVariables.icomIp = result.equals("") ? "255.255.255.255" : result;
|
||||
}
|
||||
if (name.equalsIgnoreCase("icomPort")) {//Icom端口
|
||||
GeneralVariables.icomUdpPort = result.equals("") ? 50001 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("icomUserName")) {//Icom用户名
|
||||
GeneralVariables.icomUserName = result.equals("") ? "ic705" : result;
|
||||
}
|
||||
if (name.equalsIgnoreCase("icomPassword")) {//Icom密码
|
||||
GeneralVariables.icomPassword = result;
|
||||
}
|
||||
if (name.equalsIgnoreCase("volumeValue")) {//输出音量大小
|
||||
GeneralVariables.volumePercent = result.equals("") ? 1.0f : Float.parseFloat(result) / 100f;
|
||||
}
|
||||
if (name.equalsIgnoreCase("excludedCallsigns")) {//排除的呼号
|
||||
GeneralVariables.addExcludedCallsigns(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("flexMaxRfPower")) {//指令集
|
||||
GeneralVariables.flexMaxRfPower = result.equals("") ? 10 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("flexMaxTunePower")) {//指令集
|
||||
GeneralVariables.flexMaxTunePower = result.equals("") ? 10 : Integer.parseInt(result);
|
||||
}
|
||||
if (name.equalsIgnoreCase("saveSWL")) {//保存解码信息
|
||||
GeneralVariables.saveSWLMessage = result.equals("1");
|
||||
}
|
||||
if (name.equalsIgnoreCase("saveSWLQSO")) {//保存解码信息
|
||||
GeneralVariables.saveSWL_QSO = result.equals("1");
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
|
||||
GetAllQSLCallsign.get(db);//获取通联过的呼号
|
||||
|
||||
if (onAfterQueryConfig != null) {
|
||||
onAfterQueryConfig.doOnAfterQueryConfig(null, null);
|
||||
}
|
||||
|
||||
/*
|
||||
String result = "";
|
||||
GeneralVariables.setMyMaidenheadGrid(getConfigByKey("grid"));
|
||||
|
||||
|
@ -1626,7 +1928,7 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
GeneralVariables.icomPassword = result;
|
||||
|
||||
//输出音量大小
|
||||
result = getConfigByKey("volumeValue");//IcomIp地址
|
||||
result = getConfigByKey("volumeValue");
|
||||
GeneralVariables.volumePercent = result.equals("") ? 1.0f : Float.parseFloat(result) / 100f;
|
||||
|
||||
//排除的呼号
|
||||
|
@ -1639,6 +1941,8 @@ public class DatabaseOpr extends SQLiteOpenHelper {
|
|||
onAfterQueryConfig.doOnAfterQueryConfig(null, null);
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
/**
|
||||
* 与DXCC有关的3个表,dxccList、dxcc_grid、dxcc_prefix,用于保存各DXCC分区所在的国家、经纬度、网格等信息
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
|
||||
/**
|
||||
* 配置信息读取完毕的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public interface OnAfterQueryConfig {
|
||||
void doOnBeforeQueryConfig(String KeyName);
|
||||
void doOnAfterQueryConfig(String KeyName,String Value);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
/**
|
||||
* 查询关注的呼号回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
|
||||
/**
|
||||
* 保存配置信息的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public interface OnAfterWriteConfig {
|
||||
void doOnAfterWriteConfig(boolean writeDone);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
|
||||
/**
|
||||
* 查询呼号的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public interface OnGetCallsign {
|
||||
void doOnAfterGetCallSign(boolean exists);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import java.util.ArrayList;
|
|||
|
||||
/**
|
||||
* 用于读取可用的载波波段列表,文件保存在assets/bands.txt中
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
public class OperationBand {
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.database;
|
||||
/**
|
||||
* 各电台信号的列表。文件在rigaddress.txt中
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* 用于区分Flex各命令的枚举
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public enum FlexCommand {
|
||||
UNKNOW,
|
||||
CLIENT_GUI,
|
||||
|
@ -53,5 +58,9 @@ public enum FlexCommand {
|
|||
SUB_DAX_ALL,
|
||||
DISPLAY_PAN,
|
||||
|
||||
TRANSMIT_POWER
|
||||
TRANSMIT_POWER,
|
||||
TRANSMIT_MAX_POWER,
|
||||
AUT_TUNE_MAX_POWER,
|
||||
ATU,
|
||||
PTT_ON
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Meter的ID与定义映射表,启动Flex后,Meter的ID值并不是固定不变的,所以要保存一个Hash表
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class FlexMeterInfos extends HashMap<Integer, FlexMeterInfos.FlexMeterInfo> {
|
||||
private static final String TAG = "FlexMeters";
|
||||
public int sMeterId = -1;
|
||||
public int tempCId = -1;
|
||||
public int swrId = -1;
|
||||
public int pwrId = -1;
|
||||
public int alcId = -1;
|
||||
|
||||
public FlexMeterInfos(String content) {
|
||||
setMeterInfos(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据电台回复的消息,获取每个METER的ID与meter定义的映射表
|
||||
*
|
||||
* @param content 消息
|
||||
*/
|
||||
public synchronized void setMeterInfos(String content) {
|
||||
String[] temp;
|
||||
if (content.length() == 0) return;
|
||||
temp = content.substring(content.indexOf("meter ") + "meter ".length()).split("#");
|
||||
for (int i = 0; i < temp.length; i++) {
|
||||
String[] val = temp[i].split("=");
|
||||
if (val.length == 2) {
|
||||
if (val[0].contains(".")) {
|
||||
int index = Integer.parseInt(val[0].substring(0, val[0].indexOf(".")));
|
||||
FlexMeterInfo meterInfo;
|
||||
|
||||
meterInfo = this.get(index);
|
||||
if (meterInfo == null) {
|
||||
meterInfo = new FlexMeterInfo();
|
||||
this.put(index, meterInfo);
|
||||
}
|
||||
|
||||
|
||||
if (val[0].toLowerCase().contains(".src")) {
|
||||
|
||||
meterInfo.src = val[1];
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".num")) {
|
||||
meterInfo.num = val[1];
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".nam")) {
|
||||
meterInfo.nam = val[1];
|
||||
//为了方便MeterList快速查询
|
||||
if (val[1].toUpperCase().contains("LEVEL")) {
|
||||
sMeterId = index;
|
||||
} else if (val[1].toUpperCase().contains("PATEMP")) {
|
||||
tempCId = index;
|
||||
} else if (val[1].toUpperCase().contains("SWR")) {
|
||||
swrId = index;
|
||||
} else if (val[1].toUpperCase().contains("FWDPWR")) {
|
||||
pwrId = index;
|
||||
} else if (val[1].toUpperCase().contains("ALC")) {
|
||||
alcId = index;
|
||||
}
|
||||
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".low")) {
|
||||
meterInfo.low = Float.parseFloat(val[1]);
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".hi")) {
|
||||
meterInfo.hi = Float.parseFloat(val[1]);
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".desc")) {
|
||||
meterInfo.desc = val[1];
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".unit")) {
|
||||
String s = val[1].toUpperCase();
|
||||
if (s.contains("DB")) {
|
||||
meterInfo.unit = FlexMeterType.dBm;
|
||||
} else if (s.contains("SWR")) {
|
||||
meterInfo.unit = FlexMeterType.swr;
|
||||
} else if (s.contains("DEG")) {
|
||||
meterInfo.unit = FlexMeterType.Temperature;
|
||||
} else if (s.contains("VOLT")) {
|
||||
meterInfo.unit = FlexMeterType.volt;
|
||||
} else {
|
||||
meterInfo.unit = FlexMeterType.other;
|
||||
}
|
||||
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".fps")) {
|
||||
meterInfo.fps = val[1];
|
||||
}
|
||||
if (val[0].toLowerCase().contains(".peak")) {
|
||||
meterInfo.peak = val[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// sMeterId=getMeterId("LEVEL");
|
||||
// tempCId=getMeterId("PATEMP");
|
||||
// swrId=getMeterId("SWR");
|
||||
// pwrId=getMeterId("FWDPWR");
|
||||
// alcId=getMeterId("ALC");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查Meter得id,没有返回-1
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
private int getMeterId(String s) {
|
||||
for (int key : this.keySet()) {
|
||||
if (get(key).nam.equalsIgnoreCase(s)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public static class FlexMeterInfo {
|
||||
public String src;
|
||||
public String num;
|
||||
public String nam;
|
||||
public float low;
|
||||
public float hi;
|
||||
public String desc;
|
||||
public FlexMeterType unit = FlexMeterType.other;
|
||||
public String fps;
|
||||
public String peak;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"src='" + src + '\'' +
|
||||
", num='" + num + '\'' +
|
||||
", nam='" + nam + '\'' +
|
||||
", low='" + low + '\'' +
|
||||
", hi='" + hi + '\'' +
|
||||
", desc='" + desc + '\'' +
|
||||
", unit='" + unit + '\'' +
|
||||
", fps='" + fps + '\'' +
|
||||
", peak='" + peak + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
/**
|
||||
* Meter的哈希表。Meter是2个32位的数,ID+VALUE。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class FlexMeterList extends HashMap<Integer, FlexMeterList.FlexMeter> {
|
||||
public float sMeterVal=-150;//-150~10
|
||||
public float tempCVal=0;//0~100
|
||||
public float alcVal=-150;//-150~20
|
||||
public float swrVal=1;//1~999
|
||||
public float pwrVal=0;//0~100W
|
||||
public synchronized void setMeters(byte[] data, FlexMeterInfos infos) {
|
||||
for (int i = 0; i < data.length / 4; i++) {
|
||||
int val = readShortData(data, i * 4);
|
||||
FlexMeter meter = get(val);
|
||||
if (meter == null) {
|
||||
if (infos.get(val) == null) continue;
|
||||
meter = new FlexMeter();
|
||||
meter.name = infos.get(val).nam;
|
||||
meter.desc = infos.get(val).desc;
|
||||
meter.type = infos.get(val).unit;
|
||||
meter.id = val;
|
||||
}
|
||||
switch (meter.type) {
|
||||
case dBm:
|
||||
case swr:
|
||||
meter.value = readShortData(data, i * 4 + 2) / 128f;
|
||||
if (meter.name.contains("PWR")){//把dBm转换成功率值
|
||||
meter.value=(float) Math.pow(10,meter.value/10f)/1000f;
|
||||
}
|
||||
//节省资源,提前赋值
|
||||
if (meter.id==infos.sMeterId) sMeterVal=meter.value;
|
||||
if (meter.id==infos.swrId) swrVal=meter.value;
|
||||
if (meter.id==infos.pwrId) pwrVal=meter.value;
|
||||
if (meter.id==infos.alcId) alcVal=meter.value;
|
||||
break;
|
||||
case volt:
|
||||
meter.value = readShortData(data, i * 4 + 2) / 256f;
|
||||
break;
|
||||
case Temperature:
|
||||
meter.value = readShortData(data, i * 4 + 2) / 64f;
|
||||
//节省资源,提前赋值
|
||||
if (meter.id==infos.tempCId) tempCVal=meter.value;
|
||||
break;
|
||||
case other:
|
||||
default:
|
||||
meter.value = readShortData(data, i * 4 + 2);
|
||||
}
|
||||
|
||||
put(val, meter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized String getMeters(){
|
||||
StringBuilder temp=new StringBuilder();
|
||||
int i=0;
|
||||
for (int key:this.keySet()) {
|
||||
i++;
|
||||
temp.append(String.format("%-35s",get(key).toString()));
|
||||
if (i%2==0){
|
||||
temp.append("\n");
|
||||
}
|
||||
}
|
||||
return temp.toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 把字节转换成short,不做小端转换!!
|
||||
*
|
||||
* @param data 字节数据
|
||||
* @return short
|
||||
*/
|
||||
public static short readShortData(byte[] data, int start) {
|
||||
if (data.length - start < 2) return 0;
|
||||
return (short) ((short) data[start + 1] & 0xff
|
||||
| ((short) data[start] & 0xff) << 8);
|
||||
}
|
||||
|
||||
public static float readShortFloat(byte[] data, int start) {
|
||||
if (data.length - start < 2) return 0.0f;
|
||||
int accum = 0;
|
||||
accum = accum | (data[start] & 0xff) << 0;
|
||||
accum = accum | (data[start + 1] & 0xff) << 8;
|
||||
return Float.intBitsToFloat(accum);
|
||||
}
|
||||
|
||||
|
||||
public static class FlexMeter {
|
||||
public int id;
|
||||
public float value;
|
||||
public String name;
|
||||
public String desc;
|
||||
public FlexMeterType type=FlexMeterType.other;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%02d.%s : %.1f",id,name,value);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* meter的常用类型
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public enum FlexMeterType {
|
||||
dBm,swr,Temperature,volt,other
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* @AUTHOR BG7YOZ
|
||||
* Flex的操作,命令使用TCP,数据流使用UDP。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
@ -21,17 +22,22 @@ import java.io.IOException;
|
|||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashSet;
|
||||
|
||||
|
||||
public class FlexRadio {
|
||||
|
||||
public enum FlexMode { LSB, USB, AM, CW, DIGL, DIGU, SAM, FM, NFM, DFM, RTTY, RAW, ARQ,UNKNOW }
|
||||
public enum AntMode { ANT1, ANT2, RX_A, XVTA,UNKNOW }
|
||||
public enum FlexMode {LSB, USB, AM, CW, DIGL, DIGU, SAM, FM, NFM, DFM, RTTY, RAW, ARQ, UNKNOW}
|
||||
|
||||
public enum AntMode {ANT1, ANT2, RX_A, XVTA, UNKNOW}
|
||||
|
||||
|
||||
private static final String TAG = "FlexRadio";
|
||||
private static int streamPort = 7051;
|
||||
private int flexStreamPort = 4993;
|
||||
public boolean isPttOn = false;
|
||||
public long streamTxId = 0x084000000;
|
||||
|
||||
public static int getStreamPort() {//获取用于流传输的UDP端口,防止重复,采用自增方式
|
||||
return ++streamPort;
|
||||
|
@ -88,6 +94,7 @@ public class FlexRadio {
|
|||
private boolean allFlexRadioStatusEvent = false;
|
||||
private String clientID = "";
|
||||
private long daxAudioStreamId = 0;
|
||||
private long daxTxAudioStreamId = 0;
|
||||
private long panadapterStreamId = 0;
|
||||
private final HashSet<Long> streamIdSet = new HashSet<>();
|
||||
|
||||
|
@ -361,14 +368,16 @@ public class FlexRadio {
|
|||
RadioUdpClient.OnUdpEvents onUdpEvents = new RadioUdpClient.OnUdpEvents() {
|
||||
@Override
|
||||
public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) {
|
||||
if (flexStreamPort != packet.getPort()) flexStreamPort = packet.getPort();
|
||||
|
||||
VITA vita = new VITA(data);
|
||||
addStreamIdToSet(vita.streamId);
|
||||
|
||||
//Log.e(TAG, String.format("OnReceiveData: stream id:0x%x,class id:0x%x",vita.streamId,vita.classId) );
|
||||
switch (vita.classId) {
|
||||
case VITA.FLEX_DAX_AUDIO_CLASS_ID://音频数据
|
||||
//Log.e(TAG, String.format("FLEX_DAX_AUDIO_CLASS_ID stream id:0x%x",vita.streamId ));
|
||||
doReceiveAudio(vita.payload);
|
||||
|
||||
//Log.e(TAG, "OnReceiveData: audio:"+vita.payload.length );
|
||||
break;
|
||||
case VITA.FLEX_DAX_IQ_CLASS_ID://IQ数据
|
||||
doReceiveIQ(vita.payload);
|
||||
|
@ -378,6 +387,7 @@ public class FlexRadio {
|
|||
//Log.e(TAG, String.format("OnReceiveData: FFT:%d,STREAM ID:0x%x",vita.payload.length,vita.streamId));
|
||||
break;
|
||||
case VITA.FLEX_METER_CLASS_ID://仪表数据
|
||||
//Log.e(TAG, String.format("FLEX_METER_CLASS_ID: stream id:0x%x",vita.streamId ));
|
||||
doReceiveMeter(vita);
|
||||
//Log.e(TAG, String.format("OnReceiveData: METER class id:0x%x,stream id:0x%x,length:%d\n%s"
|
||||
// ,vita.classId,vita.streamId,vita.payload.length,vita.showPayload() ));
|
||||
|
@ -428,6 +438,106 @@ public class FlexRadio {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* flexRadio要把12000采样率改为24000采样率,还要把单声道改为立体声
|
||||
* @param data 音频
|
||||
*/
|
||||
public void sendWaveData(float[] data) {
|
||||
float[] temp = new float[data.length * 4];
|
||||
for (int i = 0; i < data.length; i++) {//转成立体声,12000采样率转24000采样率
|
||||
temp[i * 4] = data[i];
|
||||
temp[i * 4 + 1] = data[i];
|
||||
temp[i * 4 + 2] = data[i];
|
||||
temp[i * 4 + 3] = data[i];
|
||||
}
|
||||
//port=4991;
|
||||
//streamTxId=0x084000001;
|
||||
Log.e(TAG, String.format("sendWaveData: streamid:0x%x,ip:%s,port:%d",streamTxId,ip, port) );
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
VITA vita = new VITA();
|
||||
|
||||
int count = 0;
|
||||
int packetCount=0;
|
||||
while (count<temp.length){
|
||||
long now = System.currentTimeMillis() - 1;//获取当前时间
|
||||
float[] voice=new float[64];
|
||||
for (int j = 0; j <3 ; j++) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
voice[i] = temp[count];
|
||||
count++;
|
||||
if (count > temp.length) break;
|
||||
}
|
||||
|
||||
byte[] send = vita.audioDataToVita(packetCount, streamTxId, voice);
|
||||
packetCount++;
|
||||
try {
|
||||
streamClient.sendData(send, ip, port);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (count>temp.length) break;
|
||||
}
|
||||
while (isPttOn) {
|
||||
if (System.currentTimeMillis() - now >= 41) {//40毫秒一个周期,每个周期3个包,每个包64个float。
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isPttOn){
|
||||
Log.e(TAG, String.format("count:%d,temp.length:%d",count,temp.length ));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for (int i = 0; i < (temp.length / (24 * 2 * 40)); i++) {//40毫秒的数据量
|
||||
// if (!isPttOn) return;
|
||||
// long now = System.currentTimeMillis() - 1;//获取当前时间
|
||||
//
|
||||
// float[] voice = new float[24 * 2 * 10];
|
||||
// for (int j = 0; j < 24 * 2 *10; j++) {
|
||||
// voice[j] = temp[i * 24 * 2 * 10 + j];
|
||||
// }
|
||||
// //Log.e(TAG, "sendWaveData: "+floatToStr(voice) );
|
||||
// //streamTxId=0x84000001;
|
||||
// byte[] send = vita.audioDataToVita(count, streamTxId, voice);
|
||||
// count++;
|
||||
//
|
||||
// try {
|
||||
// streamClient.sendData(send, ip, port);
|
||||
// } catch (UnknownHostException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
//
|
||||
// while (isPttOn) {
|
||||
// if (System.currentTimeMillis() - now >= 41) {//40毫秒一个周期,每个周期3个包,每个包64个float。
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
//设置发送音频包
|
||||
//streamClient.sendData();
|
||||
}
|
||||
public static String byteToStr(byte[] data) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
s.append(String.format("%02x ", data[i] & 0xff));
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
public static String floatToStr(float[] data) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
s.append(String.format("%f ", data[i]));
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
/**
|
||||
* 电台是否连接
|
||||
*
|
||||
|
@ -524,6 +634,11 @@ public class FlexRadio {
|
|||
if (response.panadapterStreamId != 0) {
|
||||
this.panadapterStreamId = response.panadapterStreamId;
|
||||
}
|
||||
if (response.daxTxStreamId != 0) {
|
||||
this.daxTxAudioStreamId = response.daxTxStreamId;
|
||||
Log.e(TAG, String.format("doReceiveLineEvent: txStreamID:0x%x", daxTxAudioStreamId));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -697,6 +812,13 @@ public class FlexRadio {
|
|||
sendCommand(FlexCommand.STREAM_CREATE_DAX_RX, String.format("stream create type=dax_rx dax_channel=%d", channel));
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public synchronized void commandStreamCreateDaxTx(int channel) {
|
||||
//sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx dax_channel=%d", channel));
|
||||
//sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=dax_tx compression=none"));
|
||||
sendCommand(FlexCommand.STREAM_CREATE_DAX_TX, String.format("stream create type=remote_audio_tx"));
|
||||
}
|
||||
|
||||
public synchronized void commandRemoveDaxStream() {
|
||||
sendCommand(FlexCommand.STREAM_REMOVE, String.format("stream remove 0x%x", getDaxAudioStreamId()));
|
||||
}
|
||||
|
@ -712,6 +834,11 @@ public class FlexRadio {
|
|||
sendCommand(FlexCommand.FILT_SET, String.format("filt %d %d %d", sliceOrder, filt_low, filt_high));
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public synchronized void commandStartATU() {
|
||||
sendCommand(FlexCommand.FILT_SET, "atu start");
|
||||
}
|
||||
|
||||
public synchronized void commandGetInfo() {
|
||||
sendCommand(FlexCommand.INFO, "info");
|
||||
}
|
||||
|
@ -780,8 +907,32 @@ public class FlexRadio {
|
|||
sendCommand(FlexCommand.SUB_DAX_ALL, "sub dax all");
|
||||
}
|
||||
|
||||
public synchronized void commandSetRfPower(int power){
|
||||
sendCommand(FlexCommand.TRANSMIT_POWER,String.format("transmit set rfpower=%d",power));
|
||||
@SuppressLint("DefaultLocale")
|
||||
public synchronized void commandSetRfPower(int power) {
|
||||
sendCommand(FlexCommand.TRANSMIT_MAX_POWER, String.format("transmit set max_power_level=%d", power));
|
||||
sendCommand(FlexCommand.TRANSMIT_POWER, String.format("transmit set rfpower=%d", power));
|
||||
//sendCommand(FlexCommand.TRANSMIT_MAX_POWER,"info");
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public synchronized void commandSetTunePower(int power) {
|
||||
sendCommand(FlexCommand.AUT_TUNE_MAX_POWER, String.format("transmit set tunepower=%d", power));
|
||||
}
|
||||
|
||||
public synchronized void commandPTTOnOff(boolean on) {
|
||||
if (on) {
|
||||
sendCommand(FlexCommand.PTT_ON, "xmit 1");
|
||||
} else {
|
||||
sendCommand(FlexCommand.PTT_ON, "xmit 0");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void commandTuneTransmitOnOff(boolean on) {
|
||||
if (on) {
|
||||
sendCommand(FlexCommand.PTT_ON, "transmit tune on");
|
||||
} else {
|
||||
sendCommand(FlexCommand.PTT_ON, "transmit tune off");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -864,6 +1015,7 @@ public class FlexRadio {
|
|||
public String version;//版本信息
|
||||
public int message_num;//消息号,32位,16进制。其中位24-25包含消息的严重性(0=信息,1=警告,2=错误,3=致命错误)
|
||||
public long daxStreamId = 0;
|
||||
public long daxTxStreamId = 0;
|
||||
public long panadapterStreamId = 0;
|
||||
public FlexCommand flexCommand = FlexCommand.UNKNOW;
|
||||
public long resultValue = 0;
|
||||
|
@ -901,6 +1053,9 @@ public class FlexRadio {
|
|||
case PANADAPTER_CREATE:
|
||||
this.panadapterStreamId = getStreamId(line);
|
||||
break;
|
||||
case STREAM_CREATE_DAX_TX:
|
||||
this.daxTxStreamId = getStreamId(line);
|
||||
break;
|
||||
}
|
||||
resultValue = Integer.parseInt(content, 16);//取命令的返回值
|
||||
|
||||
|
@ -962,12 +1117,12 @@ public class FlexRadio {
|
|||
}
|
||||
}
|
||||
|
||||
private int getStreamId(String line) {
|
||||
private long getStreamId(String line) {
|
||||
String[] lines = line.split("\\|");
|
||||
if (lines.length > 2) {
|
||||
if (lines[1].equals("0")) {
|
||||
try {
|
||||
return Integer.parseInt(lines[2], 16);//stream id,16进制
|
||||
return Long.parseLong(lines[2], 16);//stream id,16进制
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "getDaxStreamId exception: " + e.getMessage());
|
||||
|
@ -994,10 +1149,10 @@ public class FlexRadio {
|
|||
|
||||
}
|
||||
|
||||
if (temp.length>2){
|
||||
exContent=temp[2];
|
||||
}else {
|
||||
exContent="";
|
||||
if (temp.length > 2) {
|
||||
exContent = temp[2];
|
||||
} else {
|
||||
exContent = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* @author BG7YOZ
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
|
@ -31,6 +27,8 @@ enum VitaTokens {
|
|||
* RadioFactory: 实例化这个类来创建一个 Radio Factory,它将为网络上发现的无线电维护FlexRadio列表flexRadios。
|
||||
*
|
||||
* 通过Upd协议,在4992端口的广播数据中获取vita协议数据,并解析出序列号,用于更新电台列表flexRadios。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class FlexRadioFactory {
|
||||
private static final String TAG="FlexRadioFactory";
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* Flex的应答类型
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public enum FlexResponseStyle {
|
||||
STATUS,//状态信息,S+HANDLE
|
||||
RESPONSE,//命令的响应,R+客户端命令序列号
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
/**
|
||||
* 简单封装的Tcp类,用于Flex的命令操作
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
/**
|
||||
* 简单的udp封装,用于数据流的操作
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -29,18 +34,33 @@ public class RadioUdpClient {
|
|||
this.port = port;
|
||||
}
|
||||
|
||||
public synchronized void sendData(byte[] data, String ip) throws UnknownHostException {
|
||||
public synchronized void sendData(byte[] data, String ip,int port) throws UnknownHostException {
|
||||
if (!activated) return;
|
||||
|
||||
//Log.e(TAG, "sendData: "+byteToStr(data) );
|
||||
//Log.e(TAG, String.format("sendData: ip: %s,port:%d ",ip,port) );
|
||||
InetAddress address = InetAddress.getByName(ip);
|
||||
sendDataRunnable.data=data;
|
||||
sendDataRunnable.address=address;
|
||||
sendDataRunnable.port=port;
|
||||
sendDataThreadPool.execute(sendDataRunnable);
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
|
||||
// try {
|
||||
// sendSocket.send(packet);
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// Log.e(TAG, "run: " + e.getMessage());
|
||||
// }
|
||||
// }
|
||||
// }).start();
|
||||
}
|
||||
|
||||
private static class SendDataRunnable implements Runnable{
|
||||
byte[] data;
|
||||
InetAddress address;
|
||||
int port;
|
||||
RadioUdpClient client;
|
||||
|
||||
public SendDataRunnable(RadioUdpClient client) {
|
||||
|
@ -49,7 +69,7 @@ public class RadioUdpClient {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length, address, client.port);
|
||||
DatagramPacket packet = new DatagramPacket(data, data.length, address,port);
|
||||
try {
|
||||
client.sendSocket.send(packet);
|
||||
} catch (IOException e) {
|
||||
|
@ -84,6 +104,31 @@ public class RadioUdpClient {
|
|||
|
||||
private void receiveData() {
|
||||
receiveThreadPool.execute(receiveRunnable);
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// while (activated) {
|
||||
// byte[] data = new byte[MAX_BUFFER_SIZE];
|
||||
// DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||
// try {
|
||||
// sendSocket.receive(packet);
|
||||
// if (onUdpEvents != null) {
|
||||
// byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength());
|
||||
// onUdpEvents.OnReceiveData(sendSocket, packet, temp);
|
||||
// }
|
||||
// //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName());
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// Log.e(TAG, "receiveData: error:" + e.getMessage());
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// Log.e(TAG, "udpClient: is exit!");
|
||||
// sendSocket.close();
|
||||
// sendSocket = null;
|
||||
// }
|
||||
// }).start();
|
||||
|
||||
}
|
||||
private static class ReceiveRunnable implements Runnable{
|
||||
RadioUdpClient client;
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
package com.bg7yoz.ft8cn.flex;
|
||||
|
||||
/**
|
||||
* VITA协议处理工具
|
||||
* @author BG7YOZ
|
||||
* VITA49协议的简单解包和封包操作。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
/*
|
||||
public static intVH_PKT_TYPE(x) ((x & 0xF0000000) >> 28)
|
||||
public static intVH_C(x) ((x & 0x08000000) >> 26)
|
||||
public static intVH_T(x) ((x & 0x04000000) >> 25)
|
||||
public static intVH_TSI(x) ((x & 0x00c00000) >> 21)
|
||||
public static intVH_TSF(x) ((x & 0x00300000) >> 19)
|
||||
public static intVH_PKT_CNT(x) ((x & 0x000f0000) >> 16)
|
||||
public static intVH_PKT_SIZE(x) (x & 0x0000ffff)
|
||||
*/
|
||||
|
||||
|
||||
// Enumerates for field values
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -23,9 +34,7 @@ enum VitaPacketType {
|
|||
|
||||
//时间戳的类型
|
||||
//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间,
|
||||
//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,
|
||||
// 第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,
|
||||
// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年
|
||||
//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年
|
||||
//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用,
|
||||
//所有的时间带来都是在以一个采样数据为该reference-point 时间
|
||||
enum VitaTSI {
|
||||
|
@ -34,6 +43,7 @@ enum VitaTSI {
|
|||
TSI_GPS,//GPS time
|
||||
TSI_OTHER//Other
|
||||
};
|
||||
|
||||
//时间戳小数部分类型
|
||||
//小数部分主要有三种:
|
||||
// 一种是sample-count ,以采样周期为最小分辨率,
|
||||
|
@ -51,7 +61,7 @@ enum VitaTSF {
|
|||
};
|
||||
|
||||
public class VITA {
|
||||
private static final String TAG="VITA";
|
||||
private static final String TAG = "VITA";
|
||||
|
||||
// 最小有效的VITA包长度
|
||||
private static final int VITAmin = 28;
|
||||
|
@ -97,7 +107,7 @@ public class VITA {
|
|||
public int classId;//FLEX应该是0x534CFFF,是informationClassCode与packetClassCode合并的
|
||||
public byte[] payload = null;
|
||||
public long trailer;
|
||||
public boolean isAvailable=false;//电台对象是否有效。
|
||||
public boolean isAvailable = false;//电台对象是否有效。
|
||||
|
||||
public boolean streamIdPresent;//是否有流字符
|
||||
|
||||
|
@ -113,113 +123,248 @@ public class VITA {
|
|||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 生成音频流的VITA数据包,id应当是电台create stream是赋给的
|
||||
*
|
||||
* @param id streamId
|
||||
* @param data 音频流数据
|
||||
* @return vita数据包
|
||||
*/
|
||||
public byte[] audioDataToVita(int count,long id, float[] data) {
|
||||
byte[] result = new byte[data.length*4 + 28];//一个float占用4个字节,28字节是包头的长度7个word
|
||||
//packetType = VitaPacketType.EXT_DATA_WITH_STREAM;
|
||||
packetType = VitaPacketType.IF_DATA_WITH_STREAM;
|
||||
classIdPresent = true;
|
||||
trailerPresent = false;//没有尾巴
|
||||
tsi = VitaTSI.TSI_NONE;//
|
||||
// tsi = VitaTSI.TSI_OTHER;//
|
||||
//tsf = VitaTSF.TSF_SAMPLE_COUNT;//--TODO---查一下这个数字是不是变化
|
||||
tsf = VitaTSF.TSF_NONE;//--TODO---查一下这个数字是不是变化
|
||||
//packetCount动态变化
|
||||
//packetCount=?应该是这个全部音频流的总包数
|
||||
|
||||
//packetSize是以word(32位,4字节)为单位,
|
||||
//packetSize值为263居多估计以音频,还有其它的长度,263是包含7个word(28字节)的头长度。
|
||||
packetSize = (data.length ) + 7;//7个word是VITA的包头
|
||||
//----以上是Header,32位,第一个word-------
|
||||
|
||||
streamId = id;//第二个word,此id是电台赋给的。经常是0x40000xx。
|
||||
|
||||
oui = 0x00001c2d;//第三个word,FlexRadio Systems OUI
|
||||
classId = 0x534c0123;//第四个word,64位
|
||||
//classId = 0x534c03e3;//第四个word,64位
|
||||
|
||||
//integerTimestamp =0;// System.currentTimeMillis() / 1000;//第五个word,时间戳的整数部分,以秒为单位。应该是取当前时间
|
||||
//fracTimeStamp = 0;//第六七个word,时间戳的小数部分,64位,此处为0。
|
||||
//fracTimeStamp = frac;//第六七个word,时间戳的小数部分,64位,此处为0。
|
||||
|
||||
byte temp = 0;
|
||||
if (classIdPresent) {
|
||||
temp = 0x08;
|
||||
}
|
||||
if (trailerPresent) {
|
||||
temp |= 0x04;
|
||||
}
|
||||
//----HEADER--No.1 word------
|
||||
// result[0]=0x18;
|
||||
result[0] = (byte) (packetType.ordinal() << 4);//packetType
|
||||
result[0] |= temp;//其实就是0011 1000,0x38//CTRR,classIdPresent、trailerPresent、R、R
|
||||
result[0] |= 0x03c0;//CTRR,classIdPresent、trailerPresent、R、R
|
||||
|
||||
result[1]=(byte) 0xd0;
|
||||
result[1]|=(byte)(count&0xf);//packet count
|
||||
result[1] = (byte) (tsi.ordinal() << 6);//TSI
|
||||
result[1] |= (byte) (tsf.ordinal() << 4);//TSF
|
||||
result[1] |= (byte) (packetCount & 0xff);//packetCount
|
||||
|
||||
//packetSize默认263(words)
|
||||
result[2] = (byte) ((packetSize >> 8) & 0xff);//packetSize 1(高8位)
|
||||
result[3] = (byte) (packetSize & 0xff);//packetSize 2(低8位)
|
||||
|
||||
//-----Stream Identifier--No.2 word----
|
||||
//streamId=id;//最后两位应当是Dax编号
|
||||
result[4] = (byte) ((streamId& 0x00ff000000 >> 24) & 0xff);
|
||||
result[5] = (byte) (((streamId & 0x00ff0000) >> 16) & 0xff);
|
||||
result[6] = (byte) (((streamId & 0x0000ff00) >> 8) & 0xff);
|
||||
result[7] = (byte) (streamId & 0x000000ff);
|
||||
|
||||
//----OUI--No.3 words----
|
||||
//OUI = 0x001C2D
|
||||
result[8] = 0x00;
|
||||
result[9] = 0x00;
|
||||
result[10] = 0x1c;
|
||||
result[11] = 0x2d;
|
||||
//---Class Identifier--No.4 word----
|
||||
//class id=0x534c0123
|
||||
result[12] = 0x53;
|
||||
result[13] = 0x4c;
|
||||
result[14] = (byte) 0x01;
|
||||
result[15] = (byte) 0x23;
|
||||
|
||||
//---Timestamp--No.5 word----
|
||||
//integerTimestamp=0x01020304
|
||||
|
||||
// result[16] = (byte) 0x01;
|
||||
// result[17] = (byte) 0x02;
|
||||
// result[18] = (byte) 0x03;
|
||||
// result[19] = (byte) 0x04;
|
||||
|
||||
//---FracTimeStamp No.5~6 words----
|
||||
//fracTimeStamp=0x10200300506070c0
|
||||
// result[20] = 0x10;
|
||||
// result[21] = 0x20;
|
||||
// result[22] = 0x03;
|
||||
// result[23] = 0x00;
|
||||
// result[24] = 0x50;
|
||||
// result[25] = 0x60;
|
||||
// result[26] = 0x70;
|
||||
// result[27] = (byte) 0xc0;
|
||||
// result[20] = (byte) ((fracTimeStamp >> 56) & 0x000000ff);
|
||||
// result[21] = (byte) ((fracTimeStamp >> 48) & 0x000000ff);
|
||||
// result[22] = (byte) ((fracTimeStamp >> 40) & 0x000000ff);
|
||||
// result[23] = (byte) ((fracTimeStamp >> 32) & 0x000000ff);
|
||||
//
|
||||
// result[24] = (byte) ((fracTimeStamp >> 24) & 0x000000ff);
|
||||
// result[25] = (byte) ((fracTimeStamp >> 16) & 0x000000ff);
|
||||
// result[26] = (byte) ((fracTimeStamp >> 8) & 0x000000ff);
|
||||
// result[27] = (byte) (fracTimeStamp & 0x000000ff);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
byte[] bytes=ByteBuffer.allocate(4).putFloat(data[i]).array();//float转byte[]
|
||||
result[i*4+28]= bytes[0];
|
||||
result[i*4+29]= bytes[1];
|
||||
result[i*4+30]= bytes[2];
|
||||
result[i*4+31]= bytes[3];
|
||||
}
|
||||
|
||||
/*
|
||||
也就是payload的长度+28字节:byte[] result=new byte[data.length+28];
|
||||
streamIdPresent=true;
|
||||
streamIdPresent=packetType==VitaPacketType.IF_DATA_WITH_STREAM
|
||||
||packetType==VitaPacketType.EXT_DATA_WITH_STREAM;
|
||||
streamId:0x4000008,此处应该是STREAM_CREATE_DAX_TX的值
|
||||
classIdPresent:0x534c03e3,packetSize:263
|
||||
integerTimestamp=now/1000;以秒为单位
|
||||
fracTimeStamp=0;
|
||||
*/
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public VITA() {
|
||||
}
|
||||
|
||||
public VITA(byte[] data) {
|
||||
this.buffer = data;
|
||||
//如果包的长度太小,或包为空,就退出计算
|
||||
if (data == null) return;
|
||||
if (data.length<VITAmin)return;
|
||||
if (data.length < VITAmin) return;
|
||||
|
||||
isAvailable=true;//数据长度达到28个字节,说明是有效的。
|
||||
packetType=VitaPacketType.values()[(data[0]>>4)&0x0f];
|
||||
classIdPresent=(data[0]&0x8)==0x8;//指示数据包中是否包含类标识符(类ID)字段
|
||||
trailerPresent=(data[0]&0x4)==0x4;//指示数据包是否包含尾部。
|
||||
tsi=VitaTSI.values()[(data[1]>>6)&0x3];//如果有时间戳的话指示时间戳的整数部分是啥类型的
|
||||
tsf=VitaTSF.values()[(data[1]>>4)&0x3];
|
||||
packetCount=data[1]&0x0f;
|
||||
packetSize=((((int)data[2])&0x00ff)<<8)|((int)data[3])&0x00ff;
|
||||
isAvailable = true;//数据长度达到28个字节,说明是有效的。
|
||||
packetType = VitaPacketType.values()[(data[0] >> 4) & 0x0f];
|
||||
classIdPresent = (data[0] & 0x8) == 0x8;//指示数据包中是否包含类标识符(类ID)字段
|
||||
trailerPresent = (data[0] & 0x4) == 0x4;//指示数据包是否包含尾部。
|
||||
tsi = VitaTSI.values()[(data[1] >> 6) & 0x3];//如果有时间戳的话指示时间戳的整数部分是啥类型的
|
||||
tsf = VitaTSF.values()[(data[1] >> 4) & 0x3];
|
||||
packetCount = data[1] & 0x0f;
|
||||
packetSize = ((((int) data[2]) & 0x00ff) << 8) | ((int) data[3]) & 0x00ff;
|
||||
|
||||
int offset=4;//定位
|
||||
int offset = 4;//定位
|
||||
//检查是否有流字符
|
||||
streamIdPresent=packetType==VitaPacketType.IF_DATA_WITH_STREAM
|
||||
||packetType==VitaPacketType.EXT_DATA_WITH_STREAM;
|
||||
if (streamIdPresent){//是否有流ID,获取流ID,32位
|
||||
streamId=((((long)data[offset])&0x00ff)<<24)|((((int)data[offset+1])&0x00ff)<<16)
|
||||
|((((int)data[offset+2])&0x00ff)<<8)|((int)data[offset+3])&0x00ff;
|
||||
offset+=4;
|
||||
streamIdPresent = packetType == VitaPacketType.IF_DATA_WITH_STREAM
|
||||
|| packetType == VitaPacketType.EXT_DATA_WITH_STREAM;
|
||||
|
||||
if (streamIdPresent) {//是否有流ID,获取流ID,32位
|
||||
streamId = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16)
|
||||
| ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff;
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
if (classIdPresent){
|
||||
if (classIdPresent) {
|
||||
//只取24位,前8位保留
|
||||
oui=((((int)data[offset+1])&0x00ff)<<16)
|
||||
|((((int)data[offset+2])&0x00ff)<<8)|((int)data[offset+3])&0x00ff;
|
||||
oui = ((((int) data[offset + 1]) & 0x00ff) << 16)
|
||||
| ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff;
|
||||
|
||||
informationClassCode=((((int)data[offset+4])&0x00ff)<<8)|((int)data[offset+5])&0x00ff;
|
||||
packetClassCode=((((int)data[offset+6])&0x00ff)<<8)|((int)data[offset+7])&0x00ff;
|
||||
informationClassCode = ((((int) data[offset + 4]) & 0x00ff) << 8) | ((int) data[offset + 5]) & 0x00ff;
|
||||
packetClassCode = ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff;
|
||||
|
||||
classId=((((int)data[offset+4])&0x00ff)<<24)|((((int)data[offset+5])&0x00ff)<<16)
|
||||
|((((int)data[offset+6])&0x00ff)<<8)|((int)data[offset+7])&0x00ff;
|
||||
offset+=8;
|
||||
classId = ((((int) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16)
|
||||
| ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff;
|
||||
offset += 8;
|
||||
}
|
||||
//Log.e(TAG, "VITA: "+String.format("id: 0x%x, classIdPresent:0x%x,packetSize:%d",streamId,classId,packetSize) );
|
||||
|
||||
//获取时间戳,以秒为单位的时间戳,32位。
|
||||
//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间,
|
||||
//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,
|
||||
// 第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,
|
||||
// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年
|
||||
//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年
|
||||
//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用,
|
||||
//所有的时间带来都是在以一个采样数据为该reference-point 时间
|
||||
if (tsi!=VitaTSI.TSI_NONE){//32位,
|
||||
integerTimestamp=((((long)data[offset])&0x00ff)<<24)|((((int)data[offset+1])&0x00ff)<<16)
|
||||
|((((int)data[offset+2])&0x00ff)<<8)|((int)data[offset+3])&0x00ff;
|
||||
offset+=4;
|
||||
if (tsi != VitaTSI.TSI_NONE) {//32位,
|
||||
integerTimestamp = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16)
|
||||
| ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff;
|
||||
offset += 4;
|
||||
}
|
||||
//获取时间戳的小数部分,64位。
|
||||
if (tsf != VitaTSF.TSF_NONE) {
|
||||
fracTimeStamp = ((((long) data[offset]) & 0x00ff) << 56) | ((((long) data[offset + 1]) & 0x00ff) << 48)
|
||||
| ((((long) data[offset + 2]) & 0x00ff) << 36) | ((int) data[offset + 3]) & 0x00ff
|
||||
| ((((long) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16)
|
||||
| ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff;
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
//获取时间戳的小数部分,64位。
|
||||
if (tsf!=VitaTSF.TSF_NONE){
|
||||
fracTimeStamp=((((long)data[offset])&0x00ff)<<56)|((((long)data[offset+1])&0x00ff)<<48)
|
||||
|((((long)data[offset+2])&0x00ff)<<36)|((int)data[offset+3])&0x00ff
|
||||
|((((long)data[offset+4])&0x00ff)<<24)|((((int)data[offset+5])&0x00ff)<<16)
|
||||
|((((int)data[offset+6])&0x00ff)<<8)|((int)data[offset+7])&0x00ff;
|
||||
offset+=8;
|
||||
}
|
||||
|
||||
//Log.e(TAG, String.format("VITA: data length:%d,offset:%d",data.length,offset) );
|
||||
if (offset<data.length){
|
||||
payload=new byte[data.length-offset-(trailerPresent?2:0)];//如果有尾部,就减去一个word的位置
|
||||
System.arraycopy(data,offset,payload,0,payload.length);
|
||||
if (offset < data.length) {
|
||||
payload = new byte[data.length - offset - (trailerPresent ? 2 : 0)];//如果有尾部,就减去一个word的位置
|
||||
System.arraycopy(data, offset, payload, 0, payload.length);
|
||||
}
|
||||
if (trailerPresent){
|
||||
trailer=((((int)data[data.length-2])&0x00ff)<<8)|((int)data[data.length-1])&0x00ff;
|
||||
if (trailerPresent) {
|
||||
trailer = ((((int) data[data.length - 2]) & 0x00ff) << 8) | ((int) data[data.length - 1]) & 0x00ff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取payload的长度,如果没有数据,payload长度为0;
|
||||
*
|
||||
* @return payload长度
|
||||
*/
|
||||
public int getPayloadLength(){
|
||||
if (buffer==null){
|
||||
public int getPayloadLength() {
|
||||
if (buffer == null) {
|
||||
return 0;
|
||||
}else {
|
||||
} else {
|
||||
return buffer.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示扩展数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public String showPayload(){
|
||||
if (payload!=null) {
|
||||
return new String(payload).replace(" ","\n");
|
||||
}else {
|
||||
public String showPayload() {
|
||||
if (payload != null) {
|
||||
return new String(payload).replace(" ", "\n");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String showPayloadHex(){
|
||||
if (payload!=null){
|
||||
public String showPayloadHex() {
|
||||
if (payload != null) {
|
||||
return byteToStr(payload);
|
||||
}else {
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示VITA 49的包头信息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
public String showHeadStr(){
|
||||
public String showHeadStr() {
|
||||
return String.format("包类型(packetType): %s\n" +
|
||||
"包数量(packetCount): %d\n" +
|
||||
"包大小(packetSize): %d\n" +
|
||||
|
@ -227,8 +372,8 @@ public class VITA {
|
|||
"流ID(streamId): 0x%X\n" +
|
||||
"是否有类ID(classIdPresent): %s\n" +
|
||||
"类ID(classId): 0x%X\n" +
|
||||
"类高位(informationClassCode): 0x%X\n"+
|
||||
"类低位(packetClassCode): 0x%X\n"+
|
||||
"类高位(informationClassCode): 0x%X\n" +
|
||||
"类低位(packetClassCode): 0x%X\n" +
|
||||
"公司标识码(oui): 0x%X\n" +
|
||||
"时间戳类型(tsi): %s\n" +
|
||||
"时间戳整数部分(integerTimestamp):%s\n" +
|
||||
|
@ -237,47 +382,46 @@ public class VITA {
|
|||
"负载长度(payloadLength): %d\n" +
|
||||
"是否有尾部(trailerPresent): %s\n"
|
||||
|
||||
,packetType.toString()
|
||||
,packetCount
|
||||
,packetSize
|
||||
,streamIdPresent?"是":"否"
|
||||
,streamId
|
||||
,classIdPresent?"是":"否"
|
||||
,classId
|
||||
,informationClassCode
|
||||
,packetClassCode
|
||||
,oui
|
||||
,tsi.toString()
|
||||
,timestampToDateStr(integerTimestamp*1000)
|
||||
,tsf.toString()
|
||||
,fracTimeStamp
|
||||
,(payload==null?0:payload.length)
|
||||
,trailerPresent?"是":"否"
|
||||
, packetType.toString()
|
||||
, packetCount
|
||||
, packetSize
|
||||
, streamIdPresent ? "是" : "否"
|
||||
, streamId
|
||||
, classIdPresent ? "是" : "否"
|
||||
, classId
|
||||
, informationClassCode
|
||||
, packetClassCode
|
||||
, oui
|
||||
, tsi.toString()
|
||||
, timestampToDateStr(integerTimestamp * 1000)
|
||||
, tsf.toString()
|
||||
, fracTimeStamp
|
||||
, (payload == null ? 0 : payload.length)
|
||||
, trailerPresent ? "是" : "否"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示VITA 49 包数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s负载(payload):\n%s\n"
|
||||
,showHeadStr()
|
||||
,(payload==null?"":new String(payload))
|
||||
, showHeadStr()
|
||||
, (payload == null ? "" : new String(payload))
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static String timestampToDateStr(Long timestamp){
|
||||
public static String timestampToDateStr(Long timestamp) {
|
||||
//final String DATETIME_CONVENTIONAL_CN = "yyyy-MM-dd HH:mm:ss";
|
||||
//SimpleDateFormat sdf = new SimpleDateFormat(DATETIME_CONVENTIONAL_CN);
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String sd = sdf.format(new Date(timestamp)); // 时间戳转换日期
|
||||
//System.out.println(sd);
|
||||
return sd;
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.floatview;
|
||||
/**
|
||||
* FloatButton的主界面
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -18,10 +23,7 @@ import androidx.constraintlayout.widget.Constraints;
|
|||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 自定义悬浮快捷按钮控件
|
||||
* @author BG7YOZ
|
||||
*/
|
||||
|
||||
public class FloatView extends ConstraintLayout {
|
||||
private static final String TAG = "FloatView";
|
||||
|
||||
|
@ -76,13 +78,19 @@ public class FloatView extends ConstraintLayout {
|
|||
}
|
||||
|
||||
public FloatViewButton addButton(String name, int imageResourceId, OnClickListener onClickListener) {
|
||||
FloatViewButton floatViewButton = addButton(View.generateViewId(), imageResourceId, onClickListener);
|
||||
FloatViewButton floatViewButton=getButtonByName(name);
|
||||
if (floatViewButton==null){
|
||||
floatViewButton =addButton(View.generateViewId(), imageResourceId, onClickListener);
|
||||
}
|
||||
floatViewButton.setName(name);
|
||||
return floatViewButton;
|
||||
}
|
||||
|
||||
public FloatViewButton addButton(int id, String name, int imageResourceId, OnClickListener onClickListener) {
|
||||
FloatViewButton floatViewButton = addButton(id, imageResourceId, onClickListener);
|
||||
FloatViewButton floatViewButton=getButtonByName(name);
|
||||
if (floatViewButton==null){
|
||||
floatViewButton = addButton(id, imageResourceId, onClickListener);
|
||||
}
|
||||
floatViewButton.setName(name);
|
||||
return floatViewButton;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.floatview;
|
||||
/**
|
||||
* 自定义FloatButton
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.ft8listener;
|
||||
/**
|
||||
* 用于监听音频的类。监听通过时钟UtcTimer来控制周期,通过OnWaveDataListener接口来读取音频数据。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -18,7 +24,7 @@ public class FT8SignalListener {
|
|||
private static final String TAG = "FT8SignalListener";
|
||||
private final UtcTimer utcTimer;
|
||||
//private HamRecorder hamRecorder;
|
||||
private OnFt8Listen onFt8Listen;//当开始监听,解码结束后触发的事件
|
||||
private final OnFt8Listen onFt8Listen;//当开始监听,解码结束后触发的事件
|
||||
//private long band;
|
||||
public MutableLiveData<Long> decodeTimeSec = new MutableLiveData<>();//解码的时长
|
||||
private OnWaveDataListener onWaveDataListener;
|
||||
|
@ -74,7 +80,7 @@ public class FT8SignalListener {
|
|||
/**
|
||||
* 获取当前时间的偏移量,这里包括总的时钟偏移,也包括本实例的偏移
|
||||
*
|
||||
* @return
|
||||
* @return int
|
||||
*/
|
||||
public int time_Offset() {
|
||||
return utcTimer.getTime_sec() + UtcTimer.delay;
|
||||
|
@ -99,91 +105,77 @@ public class FT8SignalListener {
|
|||
}
|
||||
});
|
||||
}
|
||||
//
|
||||
// hamRecorder.getVoiceData(FT8Common.FT8_SLOT_TIME_MILLISECOND, true
|
||||
// , new OnGetVoiceDataDone() {
|
||||
// @Override
|
||||
// public void onGetDone(float[] data) {
|
||||
// Log.d(TAG, "开始解码...");
|
||||
// //testFt8(utc, HamRecorder.byteDataTo16BitData(data));
|
||||
// //decodeFt8(utc, HamRecorder.getFloatFromBytes(data));
|
||||
// decodeFt8(utc, data);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
public void decodeFt8(long utc, float[] voiceData) {
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
if (onFt8Listen != null) {
|
||||
onFt8Listen.beforeListen(utc);
|
||||
}
|
||||
ArrayList<Ft8Message> ft8Messages = new ArrayList<>();
|
||||
//long ft8Decoder;
|
||||
|
||||
Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE);
|
||||
|
||||
ft8Message.utcTime = utc;
|
||||
ft8Message.band = GeneralVariables.band;
|
||||
|
||||
//此处,改为reset,是因为在cpp部分,改为一个变量,不是以指针,申请新内存的方式处理了。
|
||||
//其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了
|
||||
long ft8Decoder = InitDecoder(ft8Message.utcTime, FT8Common.SAMPLE_RATE
|
||||
, voiceData.length, true);
|
||||
|
||||
//DecoderFt8Reset(ft8Decoder, ft8Message.utcTime, voiceData.length);
|
||||
|
||||
DecoderMonitorPressFloat(voiceData, ft8Decoder);
|
||||
|
||||
|
||||
int num_candidates = DecoderFt8FindSync(ft8Decoder);
|
||||
float dt = 0;
|
||||
int dtAverage = 0;
|
||||
for (int idx = 0; idx < num_candidates; ++idx) {
|
||||
|
||||
try {//做一下解码失败保护
|
||||
if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) {
|
||||
if (ft8Message.isValid) {
|
||||
Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉
|
||||
if (checkMessageSame(ft8Messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
dt += ft8Message.time_sec;
|
||||
dtAverage++;
|
||||
//ft8Messages.add(new Ft8Message(ft8Message));
|
||||
ft8Messages.add(msg);
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
if (onFt8Listen != null) {
|
||||
onFt8Listen.beforeListen(utc);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "run: " + e.getMessage());
|
||||
ArrayList<Ft8Message> ft8Messages = new ArrayList<>();
|
||||
|
||||
Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE);
|
||||
|
||||
ft8Message.utcTime = utc;
|
||||
ft8Message.band = GeneralVariables.band;
|
||||
|
||||
//此处,改为reset,是因为在cpp部分,改为一个变量,不是以指针,申请新内存的方式处理了。
|
||||
//其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了
|
||||
long ft8Decoder = InitDecoder(ft8Message.utcTime, FT8Common.SAMPLE_RATE
|
||||
, voiceData.length, true);
|
||||
|
||||
//DecoderFt8Reset(ft8Decoder, ft8Message.utcTime, voiceData.length);
|
||||
|
||||
DecoderMonitorPressFloat(voiceData, ft8Decoder);
|
||||
|
||||
|
||||
int num_candidates = DecoderFt8FindSync(ft8Decoder);
|
||||
float dt = 0;
|
||||
int dtAverage = 0;
|
||||
for (int idx = 0; idx < num_candidates; ++idx) {
|
||||
|
||||
try {//做一下解码失败保护
|
||||
if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) {
|
||||
if (ft8Message.isValid) {
|
||||
Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉
|
||||
if (checkMessageSame(ft8Messages, msg)) {
|
||||
continue;
|
||||
}
|
||||
dt += ft8Message.time_sec;
|
||||
dtAverage++;
|
||||
//ft8Messages.add(new Ft8Message(ft8Message));
|
||||
ft8Messages.add(msg);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "run: " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
float time_sec = 0f;
|
||||
if (dtAverage != 0) {
|
||||
time_sec = dt / dtAverage;
|
||||
//utcTimer.setTime_sec(Math.round(time_sec * 1000));
|
||||
} else {//当检测不到时,不要偏移时间
|
||||
utcTimer.setTime_sec(Math.round(0));
|
||||
}
|
||||
|
||||
//移到finalize() 方法中调用了
|
||||
DeleteDecoder(ft8Decoder);
|
||||
|
||||
|
||||
if (onFt8Listen != null) {
|
||||
onFt8Listen.afterDecode(utc, time_sec, UtcTimer.sequential(utc), ft8Messages);
|
||||
}
|
||||
|
||||
decodeTimeSec.postValue(System.currentTimeMillis() - time);//解码耗时
|
||||
|
||||
Log.d(TAG, String.format("解码耗时:%d毫秒", System.currentTimeMillis() - time));
|
||||
}
|
||||
|
||||
}
|
||||
float time_sec = 0f;
|
||||
if (dtAverage != 0) {
|
||||
time_sec = dt / dtAverage;
|
||||
//utcTimer.setTime_sec(Math.round(time_sec * 1000));
|
||||
} else {//当检测不到时,不要偏移时间
|
||||
utcTimer.setTime_sec(Math.round(0));
|
||||
}
|
||||
|
||||
//移到finalize() 方法中调用了
|
||||
DeleteDecoder(ft8Decoder);
|
||||
|
||||
|
||||
if (onFt8Listen != null) {
|
||||
onFt8Listen.afterDecode(utc, time_sec, UtcTimer.sequential(utc), ft8Messages);
|
||||
}
|
||||
|
||||
decodeTimeSec.postValue(System.currentTimeMillis() - time);//解码耗时
|
||||
|
||||
db.writeMessage(ft8Messages, GeneralVariables.myCallsign);//把消息写到数据库
|
||||
|
||||
Log.d(TAG, String.format("解码耗时:%d毫秒", System.currentTimeMillis() - time));
|
||||
// }
|
||||
// }).start();
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
|
@ -192,7 +184,7 @@ public class FT8SignalListener {
|
|||
*
|
||||
* @param ft8Messages 消息列表
|
||||
* @param ft8Message 消息
|
||||
* @return
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean checkMessageSame(ArrayList<Ft8Message> ft8Messages, Ft8Message ft8Message) {
|
||||
for (Ft8Message msg : ft8Messages) {
|
||||
|
@ -256,7 +248,7 @@ public class FT8SignalListener {
|
|||
* @param idx 中标信号的序号
|
||||
* @param decoder 解码器的地址
|
||||
* @param ft8Message 解出来的消息
|
||||
* @return
|
||||
* @return boolean
|
||||
*/
|
||||
public native boolean DecoderFt8Analysis(int idx, long decoder, Ft8Message ft8Message);
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8listener;
|
||||
/**
|
||||
* 监听音频的回调,当结束解码后,调用afterDecode来通知解码的消息
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package com.bg7yoz.ft8cn.ft8signal;
|
||||
/**
|
||||
* 按照FT8协议打包符号。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8;
|
||||
|
||||
/**
|
||||
* 生成FT8符号工具
|
||||
* @author BG7YOZ
|
||||
*/
|
||||
public class FT8Package {
|
||||
private static final String TAG = "FT8Package";
|
||||
public static final int NTOKENS = 2063592;
|
||||
|
@ -370,6 +371,7 @@ public class FT8Package {
|
|||
|
||||
public static native int getHash12(String callsign);
|
||||
|
||||
|
||||
public static native int getHash10(String callsign);
|
||||
|
||||
public static native int getHash22(String callsign);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* 与发射信号有关的类。包括分析通联过程的自动程序。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.AudioAttributes;
|
||||
|
@ -178,7 +183,66 @@ public class FT8TransmitSignal {
|
|||
}
|
||||
Log.d(TAG, "doTransmit: 开始发射...");
|
||||
doTransmitThreadPool.execute(doTransmitRunnable);
|
||||
|
||||
// new Thread(new Runnable() {
|
||||
// @SuppressLint("DefaultLocale")
|
||||
// @Override
|
||||
// public void run() {
|
||||
// //--todo----
|
||||
// //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -302,7 +366,7 @@ public class FT8TransmitSignal {
|
|||
}
|
||||
|
||||
|
||||
private void playFT8Signal(short[] buffer) {
|
||||
private void playFT8Signal(float[] buffer) {
|
||||
|
||||
//todo--实现网络发送模式
|
||||
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//网络方式就不播放音频了
|
||||
|
@ -329,16 +393,16 @@ public class FT8TransmitSignal {
|
|||
Log.d(TAG, "playFT8Signal: 准备声卡播放....");
|
||||
attributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
//.setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build();
|
||||
|
||||
myFormat = new AudioFormat.Builder().setSampleRate(FT8Common.SAMPLE_RATE)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
//.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
|
||||
int mySession = 0;
|
||||
audioTrack = new AudioTrack(attributes, myFormat
|
||||
, 12000 * 15 * 2, AudioTrack.MODE_STATIC
|
||||
, 12000 * 15 * 4, AudioTrack.MODE_STATIC
|
||||
, mySession);
|
||||
|
||||
|
||||
|
@ -943,9 +1007,9 @@ public class FT8TransmitSignal {
|
|||
}
|
||||
//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];
|
||||
// short[] buffer = new short[(int) (0.5f +
|
||||
// GenerateFT8.num_tones * GenerateFT8.symbol_period
|
||||
// * GenerateFT8.sample_rate)]; // 数据信号中的采样数0.5+79*0.16*12000];
|
||||
|
||||
|
||||
transmitSignal.isTransmitting = true;
|
||||
|
@ -955,8 +1019,9 @@ public class FT8TransmitSignal {
|
|||
transmitSignal.mutableTransmittingMessage.postValue(String.format(" (%.0fHz) %s"
|
||||
, GeneralVariables.getBaseFrequency()
|
||||
, msg.getMessageText()));
|
||||
if (!GenerateFT8.generateFt8(msg
|
||||
, GeneralVariables.getBaseFrequency(), buffer)) {
|
||||
//生成信号
|
||||
float[] buffer=GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency());
|
||||
if (buffer==null) {
|
||||
return;
|
||||
}
|
||||
;
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* FT8通联的6步
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* FT8信号生成器
|
||||
* @author BG7YOZ
|
||||
* 生成FT8音频信号的类。音频数据是32位的浮点数组。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
import com.bg7yoz.ft8cn.GeneralVariables;
|
||||
import com.bg7yoz.ft8cn.R;
|
||||
|
@ -25,6 +28,7 @@ public class GenerateFT8 {
|
|||
public static final float symbol_period = FT8_SYMBOL_PERIOD;//FT8_SYMBOL_PERIOD=0.160f
|
||||
private static final float symbol_bt = FT8_SYMBOL_BT;//FT8_SYMBOL_BT=2.0f
|
||||
private static final float slot_time = FT8_SLOT_TIME;//FT8_SLOT_TIME=15f
|
||||
//public static int sample_rate = 48000;//采样率
|
||||
public static int sample_rate = 12000;//采样率
|
||||
|
||||
|
||||
|
@ -117,10 +121,10 @@ public class GenerateFT8 {
|
|||
}
|
||||
|
||||
|
||||
public static boolean generateFt8(Ft8Message msg, float frequency, short[] buffer) {
|
||||
public static float[] generateFt8(Ft8Message msg, float frequency) {
|
||||
if (msg.callsignFrom.length()<3){
|
||||
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error));
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
// 首先,将文本数据打包为二进制消息,共12个字节
|
||||
byte[] packed = new byte[FTX_LDPC_K_BYTES];
|
||||
|
@ -156,6 +160,7 @@ public class GenerateFT8 {
|
|||
packFreeTextTo77(msg.getMessageText(), packed);
|
||||
}
|
||||
|
||||
|
||||
// 其次,将二进制消息编码为FSK音调序列,79个字节
|
||||
byte[] tones = new byte[num_tones]; // 79音调(符号)数组
|
||||
ft8_encode(packed, tones);
|
||||
|
@ -164,21 +169,31 @@ public class GenerateFT8 {
|
|||
|
||||
int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000
|
||||
|
||||
|
||||
|
||||
//int num_silence = (int) ((slot_time * sample_rate - num_samples) / 2); // 两端填充静音到15秒(15*12000-num_samples)/2(1.18秒的样本数)
|
||||
//int num_total_samples = num_silence + num_samples + num_silence; // 填充信号中的样本数2.36秒+12.64秒=15秒的样本数
|
||||
|
||||
//float[] signal = new float[Ft8num_samples];
|
||||
float[] signal = new float[num_samples];
|
||||
|
||||
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0);
|
||||
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float x = signal[i];
|
||||
if (x > 1.0)
|
||||
x = 1.0f;
|
||||
else if (x < -1.0)
|
||||
x = -1.0f;
|
||||
buffer[i] = (short) (0.5 + (x * 32767.0));
|
||||
//Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000
|
||||
//for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。
|
||||
for (int i = 0; i < num_samples; i++)//把数据全部静音。
|
||||
{
|
||||
signal[i] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0);
|
||||
for (int i = 0; i < num_samples; i++)//把数据全部静音。
|
||||
{
|
||||
if (signal[i]>1.0||signal[i]<-1.0){
|
||||
Log.e(TAG, "generateFt8: "+signal[i] );
|
||||
}
|
||||
}
|
||||
return signal;
|
||||
|
||||
}
|
||||
|
||||
private static native int packFreeTextTo77(String msg, byte[] c77);
|
||||
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* 发射的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
|
||||
public interface OnDoTransmitted {
|
||||
void onBeforeTransmit(Ft8Message message,int functionOder);
|
||||
void onAfterTransmit(Ft8Message message, int functionOder);
|
||||
void onAfterGenerate(short[] data);
|
||||
void onAfterGenerate(float[] data);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* 发射结束后的回调
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.log.QSLRecord;
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
|
||||
/**
|
||||
* 记录QSO的类,用于保存数据库。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class QSLRecord {
|
||||
private long startTime;//起始时间
|
||||
private long endTime;//结束时间
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* 通联记录的列表
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.log.QSLRecord;
|
||||
|
||||
|
@ -41,7 +46,14 @@ public class QslRecordList extends ArrayList<QSLRecord> {
|
|||
*/
|
||||
public QSLRecord addQSLRecord(QSLRecord record){
|
||||
if (record.getToCallsign().equals("CQ")) return null;
|
||||
|
||||
//清除已经保存过的通联记录
|
||||
//for (int i = this.size()-1; i >=0 ; i--) {
|
||||
// if (this.get(i).getToCallsign().equals(record.getToCallsign())){
|
||||
// if (this.get(i).saved){
|
||||
// this.remove(i);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//找一下看有没有已经在列表中,但还没有保存的记录
|
||||
QSLRecord oldRecord= getRecordByCallsign(record.getToCallsign());
|
||||
if (oldRecord==null){
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.ft8transmit;
|
||||
/**
|
||||
* 呼叫过程所记录的呼号信息
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.grid_tracker;
|
||||
/**
|
||||
* 网格追踪中每个连线的窗口界面。包含各类型分区图标。与我有关的通联,文字是红色的。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Paint;
|
||||
|
@ -20,15 +25,7 @@ import org.osmdroid.views.overlay.infowindow.InfoWindow;
|
|||
|
||||
public class GridInfoWindow extends InfoWindow {
|
||||
public static final int UNDEFINED_RES_ID = 0;
|
||||
// static int mTitleId = 0;
|
||||
// static int mDescriptionId = 0;
|
||||
// static int mSubDescriptionId = 0;
|
||||
// static int fromDxccImageId = 0;
|
||||
// static int fromItuImageId = 0;
|
||||
// static int fromCqImageId = 0;
|
||||
// static int toDxccImageId = 0;
|
||||
// static int toItuImageId = 0;
|
||||
// static int toCqImageId = 0;
|
||||
|
||||
private final TextView titleView;
|
||||
private final TextView descriptionView;
|
||||
private final TextView subDescriptionView;
|
||||
|
@ -68,7 +65,7 @@ public class GridInfoWindow extends InfoWindow {
|
|||
boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom());
|
||||
|
||||
//是否有与我呼号有关的消息
|
||||
if (msg.inMyCall(GeneralVariables.myCallsign)) {
|
||||
if (msg.inMyCall()) {
|
||||
layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style));
|
||||
titleView.setTextColor(mapView.getResources().getColor(
|
||||
R.color.message_in_my_call_text_color));
|
||||
|
@ -102,7 +99,7 @@ public class GridInfoWindow extends InfoWindow {
|
|||
}
|
||||
|
||||
if (this.mView == null) {
|
||||
Log.e("GridInfoWindow", "Error trapped, BasicInfoWindow.open, mView is null!");
|
||||
Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!");
|
||||
} else {
|
||||
titleView.setText(title);
|
||||
String snippet = overlay.getSnippet();
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.grid_tracker;
|
||||
/**
|
||||
* 网格追踪中Marker(网格)的消息窗口。包括各分区的图标,点击呼叫的按钮。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Paint;
|
||||
|
@ -67,7 +72,7 @@ public class GridMarkerInfoWindow extends InfoWindow {
|
|||
boolean otherBandIsQso = GeneralVariables.checkQSLCallsign_OtherBand(msg.getCallsignFrom());
|
||||
|
||||
//是否有与我呼号有关的消息
|
||||
if (msg.inMyCall(GeneralVariables.myCallsign)) {
|
||||
if (msg.inMyCall()) {
|
||||
layout.setBackground(mView.getResources().getDrawable(R.drawable.tracker_new_cq_info_win_style));
|
||||
titleView.setTextColor(mapView.getResources().getColor(
|
||||
R.color.message_in_my_call_text_color));
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.grid_tracker;
|
||||
/**
|
||||
* OsmMapView中画通联线、画网格等操作。地图是sqlite模式,采用离线方式(nightUSGS4Layer)。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import static java.lang.Math.PI;
|
||||
import static java.lang.Math.asin;
|
||||
|
@ -22,6 +27,7 @@ import com.bg7yoz.ft8cn.GeneralVariables;
|
|||
import com.bg7yoz.ft8cn.MainViewModel;
|
||||
import com.bg7yoz.ft8cn.R;
|
||||
import com.bg7yoz.ft8cn.database.DatabaseOpr;
|
||||
import com.bg7yoz.ft8cn.log.QSLRecordStr;
|
||||
import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid;
|
||||
import com.google.android.gms.maps.model.LatLng;
|
||||
|
||||
|
@ -75,8 +81,8 @@ public class GridOsmMapView {
|
|||
// private final ArrayList<OverlayItem> markerItems = new ArrayList<>();
|
||||
private final ArrayList<GridPolyLine> gridLines = new ArrayList<>();
|
||||
private GridPolyLine selectedLine = null;
|
||||
private static final int TIME_OUT=3;
|
||||
private int selectLineTimeOut=TIME_OUT;//被选择的画线,停留的周期数
|
||||
private static final int TIME_OUT = 3;
|
||||
private int selectLineTimeOut = TIME_OUT;//被选择的画线,停留的周期数
|
||||
private final ArrayList<GridPolygon> gridPolygons = new ArrayList<>();
|
||||
private final ArrayList<GridMarker> gridMarkers = new ArrayList<>();
|
||||
|
||||
|
@ -127,12 +133,13 @@ public class GridOsmMapView {
|
|||
|
||||
/**
|
||||
* 缩放到线路的范围之内
|
||||
*
|
||||
* @param line 线
|
||||
*/
|
||||
public void zoomToLineBound(GridPolyLine line) {
|
||||
BoundingBox boundingBox = new BoundingBox();
|
||||
selectedLine = line;
|
||||
selectLineTimeOut=TIME_OUT;
|
||||
selectLineTimeOut = TIME_OUT;
|
||||
line.getOutlinePaint().setColor(gridMapView.getResources().getColor(
|
||||
R.color.tracker_select_line_color));
|
||||
line.getOutlinePaint().setStrokeWidth(6);
|
||||
|
@ -143,15 +150,14 @@ public class GridOsmMapView {
|
|||
GeoPoint westSouthPoint = new GeoPoint(line.getActualPoints().get(1).getLatitude()
|
||||
, line.getActualPoints().get(1).getLongitude());
|
||||
|
||||
if (Math.abs(westSouthPoint.getLongitude()-eastNorthPoint.getLongitude())>180){
|
||||
if (eastNorthPoint.getLongitude() > westSouthPoint.getLongitude())
|
||||
{
|
||||
if (Math.abs(westSouthPoint.getLongitude() - eastNorthPoint.getLongitude()) > 180) {
|
||||
if (eastNorthPoint.getLongitude() > westSouthPoint.getLongitude()) {
|
||||
double temp = westSouthPoint.getLongitude();
|
||||
westSouthPoint.setLongitude(eastNorthPoint.getLongitude());
|
||||
eastNorthPoint.setLongitude(temp);
|
||||
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
if (eastNorthPoint.getLongitude() < westSouthPoint.getLongitude()) {
|
||||
double temp = westSouthPoint.getLongitude();
|
||||
westSouthPoint.setLongitude(eastNorthPoint.getLongitude());
|
||||
|
@ -174,6 +180,7 @@ public class GridOsmMapView {
|
|||
|
||||
/**
|
||||
* 显示CQ的位置
|
||||
*
|
||||
* @param marker CQ的标记
|
||||
* @param offset 是否偏移
|
||||
*/
|
||||
|
@ -182,7 +189,7 @@ public class GridOsmMapView {
|
|||
if (offset) {
|
||||
geoPoint.setLongitude(geoPoint.getLongitude() - 40f);
|
||||
}
|
||||
gridMapView.getController().animateTo(geoPoint,2.5,500l);
|
||||
gridMapView.getController().animateTo(geoPoint, 2.5, 500L);
|
||||
}
|
||||
|
||||
|
||||
|
@ -206,27 +213,29 @@ public class GridOsmMapView {
|
|||
gridMapView.invalidate();
|
||||
}
|
||||
|
||||
public GridPolyLine getSelectedLine(){
|
||||
public GridPolyLine getSelectedLine() {
|
||||
return selectedLine;
|
||||
}
|
||||
public void clearSelectedLines(){
|
||||
if (selectedLine!=null){
|
||||
|
||||
public void clearSelectedLines() {
|
||||
if (selectedLine != null) {
|
||||
selectedLine.closeInfoWindow();
|
||||
gridMapView.getOverlays().remove(selectedLine);
|
||||
selectedLine=null;
|
||||
selectedLine = null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除线条
|
||||
*/
|
||||
public synchronized void clearLines() {
|
||||
|
||||
boolean isOpening=false;
|
||||
boolean isOpening = false;
|
||||
|
||||
if (selectedLine !=null){
|
||||
if (selectedLine != null) {
|
||||
selectLineTimeOut--;
|
||||
isOpening=selectedLine.isInfoWindowOpen();
|
||||
isOpening = selectedLine.isInfoWindowOpen();
|
||||
selectedLine.closeInfoWindow();
|
||||
gridMapView.getOverlays().remove(selectedLine);
|
||||
}
|
||||
|
@ -235,7 +244,7 @@ public class GridOsmMapView {
|
|||
gridMapView.getOverlays().remove(line);
|
||||
}
|
||||
gridLines.clear();
|
||||
if (selectedLine != null&& selectLineTimeOut>0) {
|
||||
if (selectedLine != null && selectLineTimeOut > 0) {
|
||||
gridMapView.getOverlays().add(selectedLine);
|
||||
if (isOpening) selectedLine.showInfoWindow();
|
||||
}
|
||||
|
@ -295,6 +304,54 @@ public class GridOsmMapView {
|
|||
return gridPolygon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记、更新新发生消息的网格
|
||||
*
|
||||
* @param recordStr 历史记录
|
||||
* @return 网格对象
|
||||
*/
|
||||
public GridPolygon upgradeGridInfo(QSLRecordStr recordStr) {
|
||||
GridPolygon gridPolygon = getGridPolygon(recordStr.getGridsquare());
|
||||
if (gridPolygon == null) {
|
||||
if (recordStr.isQSL) {
|
||||
gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSL);
|
||||
} else {
|
||||
gridPolygon = addGridPolygon(recordStr.getGridsquare(), GridMode.QSO);
|
||||
}
|
||||
}
|
||||
|
||||
gridPolygon.setSnippet(String.format(String.format("%s %s",
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq)
|
||||
, recordStr.getFreq()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_band)
|
||||
, recordStr.getBand()))));
|
||||
|
||||
gridPolygon.setSubDescription(String.format("%s\n%s\n%s %s\n%s %s",
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time)
|
||||
, recordStr.getTime_on()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time)
|
||||
, recordStr.getTime_off()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd)
|
||||
, recordStr.getRst_rcvd()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent)
|
||||
, recordStr.getRst_sent()),
|
||||
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode)
|
||||
, recordStr.getMode()),
|
||||
recordStr.getComment()
|
||||
));
|
||||
gridPolygon.setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容
|
||||
gridPolygon.setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, gridMapView));
|
||||
return gridPolygon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新地图
|
||||
*/
|
||||
public void mapUpdate(){
|
||||
gridMapView.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级网格状态,如果没有,说明是新的,就添加网格。返回false。如果有,返回true。
|
||||
*
|
||||
|
@ -385,6 +442,23 @@ public class GridOsmMapView {
|
|||
return line;
|
||||
}
|
||||
|
||||
public synchronized GridPolyLine drawLine(QSLRecordStr recordStr) {
|
||||
LatLng fromLatLng = MaidenheadGrid.gridToLatLng(recordStr.getGridsquare());
|
||||
LatLng toLatLng = MaidenheadGrid.gridToLatLng(recordStr.getMy_gridsquare());
|
||||
if (fromLatLng == null) {
|
||||
//todo 把呼号转为国家的经纬度
|
||||
return null;
|
||||
//fromLatLng = message.fromLatLng;
|
||||
}
|
||||
|
||||
if (toLatLng == null) {
|
||||
//todo 把呼号转为国家的经纬度
|
||||
return null;
|
||||
//toLatLng = message.toLatLng;
|
||||
}
|
||||
final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, recordStr);
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定地图的离线来源
|
||||
|
@ -468,8 +542,48 @@ public class GridOsmMapView {
|
|||
//public String fromGrid;
|
||||
//public String toGrid;
|
||||
public Ft8Message msg;
|
||||
public QSLRecordStr recorder;
|
||||
//public boolean marked = false;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, QSLRecordStr recordStr) {
|
||||
super(mapView);
|
||||
this.recorder = recordStr;
|
||||
setSnippet(String.format(String.format("%s %s",
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq)
|
||||
, recordStr.getFreq()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_band)
|
||||
, recordStr.getBand()))));
|
||||
|
||||
setSubDescription(String.format("%s\n%s\n%s %s\n%s %s",
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time)
|
||||
, recordStr.getTime_on()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time)
|
||||
, recordStr.getTime_off()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd)
|
||||
, recordStr.getRst_rcvd()),
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent)
|
||||
, recordStr.getRst_sent()),
|
||||
|
||||
String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode)
|
||||
, recordStr.getMode()),
|
||||
recordStr.getComment()
|
||||
));
|
||||
setTitle(String.format("%s--%s", recordStr.getCall(), recordStr.getStation_callsign()));//显示消息内容
|
||||
this.mOutlinePaint = getStrokePaint(
|
||||
mapView.getResources().getColor(
|
||||
R.color.tracker_history_line_color), 3);
|
||||
List<GeoPoint> pts = new ArrayList<>();
|
||||
pts.add(GridOsmMapView.LatLng2GeoPoint(fromLatLng));
|
||||
pts.add(GridOsmMapView.LatLng2GeoPoint(toLatLng));
|
||||
|
||||
|
||||
setPoints(pts);
|
||||
setGeodesic(true);
|
||||
setInfoWindow(new GridRecordInfoWindow(R.layout.tracker_record_info_win, mapView));
|
||||
mapView.getOverlayManager().add(this);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public GridPolyLine(MapView mapView, LatLng fromLatLng, LatLng toLatLng, Ft8Message msg) {
|
||||
super(mapView);
|
||||
|
@ -480,11 +594,11 @@ public class GridOsmMapView {
|
|||
, msg.snr, msg.time_sec
|
||||
, MaidenheadGrid.getDistLatLngStr(fromLatLng, toLatLng)));
|
||||
setTitle(msg.getMessageText());//显示消息内容
|
||||
if (msg.inMyCall(GeneralVariables.myCallsign)) {
|
||||
if (msg.inMyCall()) {
|
||||
this.mOutlinePaint = getStrokePaint(
|
||||
mapView.getResources().getColor(
|
||||
R.color.tracker_in_my_line_color), 3);
|
||||
}else {
|
||||
} else {
|
||||
this.mOutlinePaint = getStrokePaint(mapView.getResources().getColor(
|
||||
R.color.tracker_line_color), 3);
|
||||
}
|
||||
|
@ -551,10 +665,16 @@ public class GridOsmMapView {
|
|||
}
|
||||
|
||||
public void showNewInfo() {
|
||||
if ((msg.fromDxcc || msg.fromItu || msg.fromCq)
|
||||
&& !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) {
|
||||
if (msg != null) {
|
||||
if ((msg.fromDxcc || msg.fromItu || msg.fromCq)
|
||||
&& !GeneralVariables.checkQSLCallsign(msg.callsignFrom)) {
|
||||
showInfoWindow();
|
||||
}
|
||||
}
|
||||
if (recorder != null) {
|
||||
showInfoWindow();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,7 +737,8 @@ public class GridOsmMapView {
|
|||
private final Ft8Message msg;
|
||||
|
||||
@SuppressLint({"UseCompatLoadingForDrawables", "DefaultLocale"})
|
||||
public GridMarker(Context context, MainViewModel mainViewModel, MapView mapView, String grid, Ft8Message msg) {
|
||||
public GridMarker(Context context, MainViewModel mainViewModel, MapView mapView
|
||||
, String grid, Ft8Message msg) {
|
||||
super(mapView);
|
||||
this.grid = grid;
|
||||
this.context = context;
|
||||
|
@ -798,13 +919,13 @@ public class GridOsmMapView {
|
|||
/**
|
||||
* 根据当前时间画灰线
|
||||
*/
|
||||
public void setGrayLine(){
|
||||
double[] lats= computeDayNightTerminator(System.currentTimeMillis());
|
||||
LatLng[] grayLine=new LatLng[lats.length*3];
|
||||
public void setGrayLine() {
|
||||
double[] lats = computeDayNightTerminator(System.currentTimeMillis());
|
||||
LatLng[] grayLine = new LatLng[lats.length * 3];
|
||||
for (int i = 0; i < lats.length; i++) {
|
||||
grayLine[i]=new LatLng((lats[i]-90),i);
|
||||
grayLine[lats.length+i]=new LatLng((lats[i]-90),i);
|
||||
grayLine[lats.length*2+i]=new LatLng((lats[i]-90),i);
|
||||
grayLine[i] = new LatLng((lats[i] - 90), i);
|
||||
grayLine[lats.length + i] = new LatLng((lats[i] - 90), i);
|
||||
grayLine[lats.length * 2 + i] = new LatLng((lats[i] - 90), i);
|
||||
}
|
||||
|
||||
Polyline line = new Polyline(gridMapView);
|
||||
|
@ -812,7 +933,7 @@ public class GridOsmMapView {
|
|||
line.setColor(context.getColor(R.color.tracker_gray_line_color));
|
||||
|
||||
List<GeoPoint> pts = new ArrayList<>();
|
||||
for (int i = 0; i <grayLine.length ; i++) {
|
||||
for (int i = 0; i < grayLine.length; i++) {
|
||||
pts.add(GridOsmMapView.LatLng2GeoPoint(grayLine[i]));
|
||||
}
|
||||
line.setInfoWindow(null);
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package com.bg7yoz.ft8cn.grid_tracker;
|
||||
/**
|
||||
* 通联日志的提示窗口。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Paint;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
import com.bg7yoz.ft8cn.GeneralVariables;
|
||||
import com.bg7yoz.ft8cn.R;
|
||||
|
||||
import org.osmdroid.views.MapView;
|
||||
import org.osmdroid.views.overlay.OverlayWithIW;
|
||||
import org.osmdroid.views.overlay.infowindow.InfoWindow;
|
||||
|
||||
public class GridRecordInfoWindow extends InfoWindow {
|
||||
public static final int UNDEFINED_RES_ID = 0;
|
||||
// static int mTitleId = 0;
|
||||
// static int mDescriptionId = 0;
|
||||
// static int mSubDescriptionId = 0;
|
||||
// static int fromDxccImageId = 0;
|
||||
// static int fromItuImageId = 0;
|
||||
// static int fromCqImageId = 0;
|
||||
// static int toDxccImageId = 0;
|
||||
// static int toItuImageId = 0;
|
||||
// static int toCqImageId = 0;
|
||||
private final TextView titleView;
|
||||
private final TextView descriptionView;
|
||||
private final TextView subDescriptionView;
|
||||
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
public GridRecordInfoWindow(int layoutResId, MapView mapView) {
|
||||
super(layoutResId, mapView);
|
||||
//setResIds(mapView.getContext());
|
||||
titleView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_title);
|
||||
descriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_description);
|
||||
subDescriptionView = (TextView) this.mView.findViewById(R.id.tracker_rec_info_bubble_subdescription);
|
||||
|
||||
ConstraintLayout layout=(ConstraintLayout) mView.findViewById(R.id.trackerGridRecInfoConstraintLayout);
|
||||
|
||||
|
||||
this.mView.setOnTouchListener(new View.OnTouchListener() {
|
||||
public boolean onTouch(View v, MotionEvent e) {
|
||||
if (e.getAction() == 1) {
|
||||
GridRecordInfoWindow.this.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onOpen(Object item) {
|
||||
OverlayWithIW overlay = (OverlayWithIW) item;
|
||||
String title = overlay.getTitle();
|
||||
if (title == null) {
|
||||
title = "";
|
||||
}
|
||||
|
||||
if (this.mView == null) {
|
||||
Log.w("OsmDroid", "Error trapped, BasicInfoWindow.open, mView is null!");
|
||||
} else {
|
||||
titleView.setText(title);
|
||||
String snippet = overlay.getSnippet();
|
||||
//Spanned snippetHtml = Html.fromHtml(snippet);
|
||||
descriptionView.setText(snippet);
|
||||
String subDesc = overlay.getSubDescription();
|
||||
subDescriptionView.setText(subDesc);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,9 +1,15 @@
|
|||
package com.bg7yoz.ft8cn.grid_tracker;
|
||||
/**
|
||||
* 网格追踪的主窗口。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
|
@ -12,6 +18,7 @@ import android.graphics.drawable.Drawable;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
@ -22,6 +29,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
@ -37,12 +45,15 @@ import com.bg7yoz.ft8cn.databinding.ActivityGridTrackerMainBinding;
|
|||
import com.bg7yoz.ft8cn.floatview.FloatView;
|
||||
import com.bg7yoz.ft8cn.floatview.FloatViewButton;
|
||||
import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign;
|
||||
import com.bg7yoz.ft8cn.log.OnQueryQSLRecordCallsign;
|
||||
import com.bg7yoz.ft8cn.log.QSLRecordStr;
|
||||
import com.bg7yoz.ft8cn.timer.UtcTimer;
|
||||
import com.bg7yoz.ft8cn.ui.CallingListAdapter;
|
||||
import com.bg7yoz.ft8cn.ui.FreqDialog;
|
||||
import com.bg7yoz.ft8cn.ui.SetVolumeDialog;
|
||||
import com.bg7yoz.ft8cn.ui.ToastMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -62,9 +73,12 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
private CallingListAdapter callingListAdapter;
|
||||
private boolean messageListIsClose = false;
|
||||
private boolean configBarIsClose = false;
|
||||
private QSLRecordStr qlsRecorder = null;//用于历史显示消息
|
||||
private MutableLiveData<ArrayList<QSLRecordStr>> qslRecordList = new MutableLiveData<>();
|
||||
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
protected void onCreateDelay() {
|
||||
protected void doAfterCreate() {
|
||||
//设置消息列表
|
||||
callingListAdapter.notifyDataSetChanged();
|
||||
callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1);
|
||||
|
@ -72,12 +86,38 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
setTipsRadioGroupClickerListener();//显示模式Group radio动作
|
||||
setShowTipsSwitchClickerListener();//显示提示开关动作
|
||||
readConfig();
|
||||
|
||||
//读取调用本activity的参数,如果不为空,说明要画参数中的消息
|
||||
//画在日志界面中被选择的消息
|
||||
Intent intentGet = getIntent();
|
||||
qlsRecorder = (QSLRecordStr) intentGet.getSerializableExtra("qslList");
|
||||
if (qlsRecorder != null) {
|
||||
GridOsmMapView.GridPolyLine line = drawMessage(qlsRecorder);//在地图上画每一个消息
|
||||
if (line != null) {
|
||||
line.showInfoWindow();
|
||||
}
|
||||
}
|
||||
//画日志界面查询出的全部消息
|
||||
String queryKey = intentGet.getStringExtra("qslAll");
|
||||
int queryFilter=intentGet.getIntExtra("queryFilter",0);
|
||||
if (queryKey != null) {
|
||||
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tracker_query_qso_info));
|
||||
mainViewModel.databaseOpr.getQSLRecordByCallsign(true, 0, queryKey, queryFilter
|
||||
, new OnQueryQSLRecordCallsign() {
|
||||
@Override
|
||||
public void afterQuery(ArrayList<QSLRecordStr> records) {
|
||||
qslRecordList.postValue(records);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
//禁止休眠
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
@ -95,7 +135,7 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
|
||||
gridOsmMapView = new GridOsmMapView(getBaseContext(), binding.osmMap, mainViewModel);
|
||||
|
||||
//---todo ---转移1
|
||||
|
||||
callMessagesRecyclerView = binding.callMessagesRecyclerView;
|
||||
callingListAdapter = new CallingListAdapter(this, mainViewModel
|
||||
, mainViewModel.ft8Messages, CallingListAdapter.ShowMode.TRACKER);
|
||||
|
@ -165,15 +205,7 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
gridOsmMapView.clearLines();
|
||||
gridOsmMapView.clearMarkers();
|
||||
for (Ft8Message msg : mainViewModel.currentMessages) {
|
||||
gridOsmMapView.upgradeGridInfo(
|
||||
msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg.getMessageText()
|
||||
, String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec));
|
||||
gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr);
|
||||
if (msg.checkIsCQ()) {
|
||||
gridOsmMapView.addGridMarker(
|
||||
msg.getMaidenheadGrid(mainViewModel.databaseOpr)
|
||||
, msg);
|
||||
}
|
||||
drawMessage(msg);//在地图上画每一个消息
|
||||
}
|
||||
gridOsmMapView.showInfoWindows();
|
||||
}
|
||||
|
@ -283,18 +315,52 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
});
|
||||
gridOsmMapView.initMap(GeneralVariables.getMyMaidenhead4Grid(), true);
|
||||
|
||||
qslRecordList.observe(this, new Observer<ArrayList<QSLRecordStr>>() {
|
||||
@Override
|
||||
public void onChanged(ArrayList<QSLRecordStr> qslRecordStrs) {
|
||||
for (QSLRecordStr record : qslRecordStrs) {
|
||||
drawMessage(record);//在地图上画每一个消息
|
||||
}
|
||||
gridOsmMapView.mapUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onCreateDelay();
|
||||
//onCreateDelay();
|
||||
closeMessages();
|
||||
closeConfigBar();
|
||||
doAfterCreate();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
setContentView(binding.getRoot());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在地图上画消息,包括收发消息和CQ消息
|
||||
*
|
||||
* @param msg 消息
|
||||
*/
|
||||
@SuppressLint("DefaultLocale")
|
||||
private void drawMessage(Ft8Message msg) {
|
||||
gridOsmMapView.upgradeGridInfo(
|
||||
msg.getMaidenheadGrid(mainViewModel.databaseOpr), msg.getMessageText()
|
||||
, String.format("%d dBm , %.1f ms", msg.snr, msg.time_sec));
|
||||
gridOsmMapView.drawLine(msg, mainViewModel.databaseOpr);
|
||||
if (msg.checkIsCQ()) {
|
||||
gridOsmMapView.addGridMarker(
|
||||
msg.getMaidenheadGrid(mainViewModel.databaseOpr)
|
||||
, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private GridOsmMapView.GridPolyLine drawMessage(QSLRecordStr recordStr) {
|
||||
gridOsmMapView.upgradeGridInfo(recordStr);
|
||||
return gridOsmMapView.drawLine(recordStr);
|
||||
}
|
||||
|
||||
private void setShowTipsSwitchClickerListener() {
|
||||
|
||||
binding.trackerShowQsxSwitch.setOnClickListener(new View.OnClickListener() {
|
||||
|
@ -685,9 +751,7 @@ public class GridTrackerMainActivity extends AppCompatActivity {
|
|||
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView
|
||||
, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY
|
||||
, int actionState, boolean isCurrentlyActive) {
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
Ft8Message message = callingListAdapter.getMessageByViewHolder(viewHolder);
|
||||
//制作呼叫背景的图标显示
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.html;
|
||||
/**
|
||||
* Http服务内容的出框架。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.database.Cursor;
|
||||
|
||||
|
@ -73,8 +78,12 @@ public class HtmlContext {
|
|||
+GeneralVariables.getStringFromResource(R.string.html_trace_callsign_and_grid_correspondence_table)
|
||||
+"</a></td></tr>" +
|
||||
"<tr><td class=\"default\" colspan=\"15\"><a href=/message>"
|
||||
+GeneralVariables.getStringFromResource(R.string.html_query_communication_message)
|
||||
+GeneralVariables.getStringFromResource(R.string.html_query_swl_message)
|
||||
+"</a></td></tr>" +
|
||||
"<tr><td class=\"default\" colspan=\"15\"><a href=/QSOSWLMSG>"
|
||||
+GeneralVariables.getStringFromResource(R.string.html_query_qso_swl)
|
||||
+"</a></td></tr>" +
|
||||
|
||||
"<tr><td class=\"default\" colspan=\"15\"><a href=/config>"
|
||||
+GeneralVariables.getStringFromResource(R.string.html_query_configuration_information)
|
||||
+"</a></td></tr>" +
|
||||
|
@ -130,7 +139,7 @@ public class HtmlContext {
|
|||
order++;
|
||||
}
|
||||
result.append("</table>");
|
||||
|
||||
cursor.close();
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,8 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
|
||||
/**
|
||||
* iCom电台各种数据包类型工具
|
||||
* @author BG7YOZ
|
||||
* ICom各数据包的解包和封包。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class IComPacketTypes {
|
||||
private static final String TAG = "IComPacketTypes";
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* WIFI模式下电台操作。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFormat;
|
||||
|
@ -99,7 +104,7 @@ public class IComWifiRig {
|
|||
controlUdp.civUdp.sendCivData(data);
|
||||
}
|
||||
|
||||
public void sendWaveData(short[] data){//发送音频数据到电台
|
||||
public void sendWaveData(float[] data){//发送音频数据到电台
|
||||
controlUdp.sendWaveData(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* 处理ICom的音频流。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -31,10 +36,59 @@ public class IcomAudioUdp extends IcomUdpBase {
|
|||
}
|
||||
|
||||
|
||||
public void sendTxAudioData(short[] audioData) {
|
||||
doTXAudioRunnable.audioData=audioData;
|
||||
doTXThreadPool.execute(doTXAudioRunnable);
|
||||
public void sendTxAudioData(float[] audioData) {
|
||||
if (audioData==null) return;
|
||||
|
||||
short[] temp=new short[audioData.length];
|
||||
//要做一下浮点到16位int的转换
|
||||
for (int i = 0; i < audioData.length; i++) {
|
||||
float x = audioData[i];
|
||||
if (x > 1.0)
|
||||
x = 1.0f;
|
||||
else if (x < -1.0)
|
||||
x = -1.0f;
|
||||
temp[i] = (short) (0.5 + (x * 32767.0));
|
||||
}
|
||||
doTXAudioRunnable.audioData=temp;
|
||||
doTXThreadPool.execute(doTXAudioRunnable);
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// final int partialLen = IComPacketTypes.TX_BUFFER_SIZE * 2;//数据包的长度
|
||||
// //要转换一下到BYTE,小端模式
|
||||
//
|
||||
// //byte[] data = new byte[audioData.length * 2 + partialLen * 4];//多出一点空声音放在前后各20ms*2共80ms
|
||||
// //先播放,是给出空的声音,for i 循环,做了一个判断,是给前面的空声音,for j循环,做得判断,是让后面发送空声音
|
||||
// byte[] audioPacket = new byte[partialLen];
|
||||
// for (int i = 0; i < (audioData.length / IComPacketTypes.TX_BUFFER_SIZE) + 8; i++) {//多出6个周期,前面3个,后面3个多
|
||||
// if (!isPttOn) break;
|
||||
// long now = System.currentTimeMillis() - 1;//获取当前时间
|
||||
//
|
||||
// sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket
|
||||
// , (short) 0, localId, remoteId, innerSeq));
|
||||
// innerSeq++;
|
||||
//
|
||||
// Arrays.fill(audioPacket,(byte)0x00);
|
||||
// if (i>=3) {//让前两个空数据发送出去
|
||||
// for (int j = 0; j < IComPacketTypes.TX_BUFFER_SIZE; j++) {
|
||||
// if ((i-3) * IComPacketTypes.TX_BUFFER_SIZE + j < audioData.length) {
|
||||
// System.arraycopy(IComPacketTypes.shortToBigEndian((short)
|
||||
// (audioData[(i-3) * IComPacketTypes.TX_BUFFER_SIZE + j]
|
||||
// * GeneralVariables.volumePercent))
|
||||
// , 0, audioPacket, j * 2, 2);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// while (isPttOn) {
|
||||
// if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Log.e(TAG, "run: 音频发送完毕!!" );
|
||||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
// }).start();
|
||||
}
|
||||
private static class DoTXAudioRunnable implements Runnable{
|
||||
IcomAudioUdp icomAudioUdp;
|
||||
|
@ -68,7 +122,7 @@ public class IcomAudioUdp extends IcomUdpBase {
|
|||
if ((i-3) * IComPacketTypes.TX_BUFFER_SIZE + j < audioData.length) {
|
||||
System.arraycopy(IComPacketTypes.shortToBigEndian((short)
|
||||
(audioData[(i-3) * IComPacketTypes.TX_BUFFER_SIZE + j]
|
||||
* GeneralVariables.volumePercent))
|
||||
* GeneralVariables.volumePercent))//乘以信号量的比率
|
||||
, 0, audioPacket, j * 2, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* 处理ICom的CIV流。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* ICom的控制流。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -180,7 +185,7 @@ public class IcomControlUdp extends IcomUdpBase {
|
|||
* 发送音频数据到电台
|
||||
* @param data 数据
|
||||
*/
|
||||
public void sendWaveData(short[] data){
|
||||
public void sendWaveData(float[] data){
|
||||
audioUdp.sendTxAudioData(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* ICom指令数据的缓存。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* 简单封装的udp流处理。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.icom;
|
||||
/**
|
||||
* 简单封装的udp协议处理
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
@ -43,7 +48,23 @@ public class IcomUdpClient {
|
|||
sendDataRunnable.data=data;
|
||||
sendDataRunnable.port=port;
|
||||
sendDataThreadPool.execute(sendDataRunnable);
|
||||
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
|
||||
// synchronized (this) {
|
||||
// try {
|
||||
// sendSocket.send(packet);
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// Log.e(TAG, "IComUdpClient: " + e.getMessage());
|
||||
// if (onUdpEvents!=null){
|
||||
// onUdpEvents.OnUdpSendIOException(e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }).start();
|
||||
}
|
||||
private static class SendDataRunnable implements Runnable{
|
||||
byte[] data;
|
||||
|
@ -107,6 +128,31 @@ public class IcomUdpClient {
|
|||
|
||||
private void receiveData() {
|
||||
doReceiveThreadPool.execute(doReceiveRunnable);
|
||||
// new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// while (activated) {
|
||||
// byte[] data = new byte[MAX_BUFFER_SIZE];
|
||||
// DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||
// try {
|
||||
// sendSocket.receive(packet);
|
||||
// if (onUdpEvents != null) {
|
||||
// byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength());
|
||||
// onUdpEvents.OnReceiveData(sendSocket, packet, temp);
|
||||
// }
|
||||
// //Log.d(TAG, "receiveData:host ip: " + packet.getAddress().getHostName());
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// Log.e(TAG, "receiveData: error:" + e.getMessage());
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// Log.e(TAG, "udpClient: is exit!");
|
||||
// sendSocket.close();
|
||||
// sendSocket = null;
|
||||
// }
|
||||
// }).start();
|
||||
|
||||
}
|
||||
|
||||
public void setOnUdpEvents(OnUdpEvents onUdpEvents) {
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
/**
|
||||
* 用于记录SWL QSO所用的哈希表类型 ,因为要记录上方的呼号,所以要有2个String KEY,HashMap并不合适,
|
||||
* 这里采用谷歌的guava:31.1-jre库
|
||||
*
|
||||
* BG7YOZ
|
||||
* 2023-03-20
|
||||
*
|
||||
*/
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class HashTable implements Table {
|
||||
@Override
|
||||
public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsRow(@Nullable Object rowKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsColumn(@Nullable Object columnKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(@Nullable Object value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @org.checkerframework.checker.nullness.qual.Nullable Object get(@Nullable Object rowKey, @Nullable Object columnKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @org.checkerframework.checker.nullness.qual.Nullable Object put(Object rowKey, Object columnKey, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Table table) {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public @org.checkerframework.checker.nullness.qual.Nullable Object remove(@Nullable Object rowKey, @Nullable Object columnKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map row(Object rowKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map column(Object columnKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Cell> cellSet() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set rowKeySet() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set columnKeySet() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection values() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map rowMap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map columnMap() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
/**
|
||||
* 日志中通联呼号的列表
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -22,7 +27,7 @@ import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid;
|
|||
import java.util.ArrayList;
|
||||
|
||||
public class LogCallsignAdapter extends RecyclerView.Adapter<LogCallsignAdapter.LogCallsignItemHolder> {
|
||||
private ArrayList<QSLCallsignRecord> callsignRecords=new ArrayList<>();
|
||||
//private ArrayList<QSLCallsignRecord> callsignRecords=new ArrayList<>();
|
||||
private final MainViewModel mainViewModel;
|
||||
private final Context context;
|
||||
|
||||
|
@ -48,7 +53,7 @@ public class LogCallsignAdapter extends RecyclerView.Adapter<LogCallsignAdapter.
|
|||
* @return 记录
|
||||
*/
|
||||
public QSLCallsignRecord getRecord(int position){
|
||||
return callsignRecords.get(position);
|
||||
return mainViewModel.callsignRecords.get(position);
|
||||
}
|
||||
/**
|
||||
* 返回查询结题
|
||||
|
@ -56,10 +61,18 @@ public class LogCallsignAdapter extends RecyclerView.Adapter<LogCallsignAdapter.
|
|||
*/
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setQSLCallsignList(ArrayList<QSLCallsignRecord> records){
|
||||
callsignRecords=records;
|
||||
mainViewModel.callsignRecords.addAll(records);
|
||||
//mainViewModel.callsignRecords=records;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空记录
|
||||
*/
|
||||
public void clearRecords(){
|
||||
mainViewModel.callsignRecords.clear();
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull LogCallsignItemHolder holder, int position) {
|
||||
|
@ -68,7 +81,7 @@ public class LogCallsignAdapter extends RecyclerView.Adapter<LogCallsignAdapter.
|
|||
}else {
|
||||
holder.logCallSignQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style);
|
||||
}
|
||||
holder.record=callsignRecords.get(position);
|
||||
holder.record=mainViewModel.callsignRecords.get(position);
|
||||
|
||||
|
||||
if (holder.record.isQSL||holder.record.isLotW_QSL){
|
||||
|
@ -147,7 +160,7 @@ public class LogCallsignAdapter extends RecyclerView.Adapter<LogCallsignAdapter.
|
|||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return callsignRecords.size();
|
||||
return mainViewModel.callsignRecords.size();
|
||||
}
|
||||
|
||||
static class LogCallsignItemHolder extends RecyclerView.ViewHolder{
|
||||
|
|
|
@ -13,7 +13,9 @@ import java.util.HashMap;
|
|||
* getFileContext是获取全部文件内容。
|
||||
* getLogBody是获取日志文件中全部的原始记录内容,也就是全部以<eoh>后面的数据
|
||||
* getLogRecords是获取拆解后的全部记录列表,记录是以HashMap方式保存的,其中HashMap的Key是字段名(大写),value是实际的值
|
||||
* @author BG7YOZ
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
public class LogFileImport {
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
/**
|
||||
* 通联日志的列表。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -21,13 +26,13 @@ import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation;
|
|||
import java.util.ArrayList;
|
||||
|
||||
public class LogQSLAdapter extends RecyclerView.Adapter<LogQSLAdapter.LogQSLItemHolder> {
|
||||
private ArrayList<QSLRecordStr> records=new ArrayList<>();
|
||||
private ArrayList<QSLRecordStr> qslRecords=new ArrayList<>();
|
||||
private final MainViewModel mainViewModel;
|
||||
private final Context context;
|
||||
|
||||
public LogQSLAdapter(Context context,MainViewModel mainViewModel) {
|
||||
public LogQSLAdapter(Context context, MainViewModel mainViewModel) {
|
||||
this.mainViewModel = mainViewModel;
|
||||
this.context=context;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -40,96 +45,105 @@ public class LogQSLAdapter extends RecyclerView.Adapter<LogQSLAdapter.LogQSLItem
|
|||
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setQSLList(ArrayList<QSLRecordStr> list){
|
||||
records=list;
|
||||
public void setQSLList(ArrayList<QSLRecordStr> list) {
|
||||
qslRecords.addAll(list);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日志
|
||||
* @param position 在列表中的位置
|
||||
* 清空记录列表
|
||||
*/
|
||||
public void deleteRecord(int position){
|
||||
mainViewModel.databaseOpr.deleteQSLByID(records.get(position).id);
|
||||
records.remove(position);
|
||||
public void clearRecords(){
|
||||
qslRecords.clear();
|
||||
}
|
||||
|
||||
public QSLRecordStr getRecord(int position){
|
||||
return records.get(position);
|
||||
/**
|
||||
* 删除日志
|
||||
*
|
||||
* @param position 在列表中的位置
|
||||
*/
|
||||
public void deleteRecord(int position) {
|
||||
mainViewModel.databaseOpr.deleteQSLByID(qslRecords.get(position).id);
|
||||
qslRecords.remove(position);
|
||||
}
|
||||
|
||||
public QSLRecordStr getRecord(int position) {
|
||||
return qslRecords.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改手工确认项
|
||||
*
|
||||
* @param position 列表位置
|
||||
* @param b 状态
|
||||
* @param b 状态
|
||||
*/
|
||||
public void setRecordIsQSL(int position,boolean b){
|
||||
records.get(position).isQSL=b;
|
||||
mainViewModel.databaseOpr.setQSLTableIsQSL(b,records.get(position).id);
|
||||
public void setRecordIsQSL(int position, boolean b) {
|
||||
qslRecords.get(position).isQSL = b;
|
||||
mainViewModel.databaseOpr.setQSLTableIsQSL(b, qslRecords.get(position).id);
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint({"DefaultLocale", "SetTextI18n"})
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull LogQSLItemHolder holder, int position) {
|
||||
holder.record=records.get(position);
|
||||
holder.record = qslRecords.get(position);
|
||||
|
||||
if ((position%2)==0){
|
||||
if ((position % 2) == 0) {
|
||||
holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_0_style);
|
||||
}else {
|
||||
} else {
|
||||
holder.logQSLHolderConstraintLayout.setBackgroundResource(R.drawable.calling_list_cell_1_style);
|
||||
}
|
||||
holder.logQSLCallsignTextView.setText(holder.record.getCall());
|
||||
if (!holder.record.getGridsquare().equals("")) {
|
||||
holder.logQSOGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid)
|
||||
, holder.record.getGridsquare()));
|
||||
}else {
|
||||
} else {
|
||||
holder.logQSOGridTextView.setText("");
|
||||
}
|
||||
holder.logQSLMyCallsignTextView.setText(holder.record.getStation_callsign());
|
||||
if (!holder.record.getMy_gridsquare().equals("")) {
|
||||
holder.logQSLMyGridTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_grid)
|
||||
, holder.record.getMy_gridsquare()));
|
||||
}else {
|
||||
} else {
|
||||
holder.logQSLMyGridTextView.setText("");
|
||||
}
|
||||
holder.logQSOStartTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_start_time)
|
||||
, holder.record.getTime_on()));
|
||||
holder.logQSOEndTimeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_end_time)
|
||||
,holder.record.getTime_off()));
|
||||
, holder.record.getTime_off()));
|
||||
holder.logQSOReceiveTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_rcvd)
|
||||
,holder.record.getRst_rcvd()));
|
||||
, holder.record.getRst_rcvd()));
|
||||
holder.logQSOSendTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_rst_sent)
|
||||
,holder.record.getRst_sent()));
|
||||
, holder.record.getRst_sent()));
|
||||
holder.logQSLBandTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_band)
|
||||
,holder.record.getBand()));
|
||||
, holder.record.getBand()));
|
||||
holder.logQSLFreqTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_freq)
|
||||
,holder.record.getFreq()));
|
||||
, holder.record.getFreq()));
|
||||
holder.logQSOModeTextView.setText(String.format(GeneralVariables.getStringFromResource(R.string.qsl_mode)
|
||||
,holder.record.getMode()));
|
||||
, holder.record.getMode()));
|
||||
holder.logQSOcCommentTextView.setText(holder.record.getComment());
|
||||
|
||||
if (holder.record.isLotW_QSL){
|
||||
if (holder.record.isLotW_QSL) {
|
||||
holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_lotw_confirmation));
|
||||
holder.logIsQSLTextView.setTextColor(context.getResources().getColor(
|
||||
R.color.is_qsl_text_color));
|
||||
}else if(holder.record.isQSL){
|
||||
} else if (holder.record.isQSL) {
|
||||
holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation));
|
||||
holder.logIsQSLTextView.setTextColor(context.getResources().getColor(
|
||||
R.color.is_qsl_text_color));
|
||||
|
||||
}else
|
||||
{
|
||||
} else {
|
||||
holder.logIsQSLTextView.setText(GeneralVariables.getStringFromResource(R.string.qsl_unconfirmed));
|
||||
holder.logIsQSLTextView.setTextColor(context.getResources().getColor(
|
||||
R.color.is_not_qsl_text_color));
|
||||
}
|
||||
|
||||
//查呼号的位置
|
||||
if (holder.record.where==null){
|
||||
if (holder.record.where == null) {
|
||||
setQueryHolderCallsign(holder);
|
||||
}else if (holder.record.where.equals("")){
|
||||
} else if (holder.record.where.equals("")) {
|
||||
setQueryHolderCallsign(holder);
|
||||
}else {
|
||||
} else {
|
||||
holder.logQSLWhereTextView.setText(holder.record.where);
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +160,7 @@ public class LogQSLAdapter extends RecyclerView.Adapter<LogQSLAdapter.LogQSLItem
|
|||
if (GeneralVariables.isChina) {
|
||||
holder.logQSLWhereTextView.setText(callsignInfo.CountryNameCN);
|
||||
holder.record.where = callsignInfo.CountryNameCN;
|
||||
}else {
|
||||
} else {
|
||||
holder.logQSLWhereTextView.setText(callsignInfo.CountryNameEn);
|
||||
holder.record.where = callsignInfo.CountryNameEn;
|
||||
}
|
||||
|
@ -158,37 +172,37 @@ public class LogQSLAdapter extends RecyclerView.Adapter<LogQSLAdapter.LogQSLItem
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return records.size();
|
||||
return qslRecords.size();
|
||||
}
|
||||
|
||||
public ArrayList<QSLRecordStr> getRecords() {
|
||||
return qslRecords;
|
||||
}
|
||||
|
||||
static class LogQSLItemHolder extends RecyclerView.ViewHolder {
|
||||
QSLRecordStr record;
|
||||
ConstraintLayout logQSLHolderConstraintLayout;
|
||||
TextView logQSLCallsignTextView,logQSOGridTextView,logQSOStartTimeTextView
|
||||
,logQSOEndTimeTextView,logQSOReceiveTextView,logQSOSendTextView
|
||||
,logQSLBandTextView,logQSLFreqTextView,logQSOModeTextView
|
||||
,logQSOcCommentTextView,logQSLMyCallsignTextView
|
||||
,logQSLMyGridTextView,logQSLWhereTextView,logIsQSLTextView;
|
||||
TextView logQSLCallsignTextView, logQSOGridTextView, logQSOStartTimeTextView, logQSOEndTimeTextView, logQSOReceiveTextView, logQSOSendTextView, logQSLBandTextView, logQSLFreqTextView, logQSOModeTextView, logQSOcCommentTextView, logQSLMyCallsignTextView, logQSLMyGridTextView, logQSLWhereTextView, logIsQSLTextView;
|
||||
|
||||
public LogQSLItemHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
logQSLHolderConstraintLayout=itemView.findViewById(R.id.logQSLHolderConstraintLayout) ;
|
||||
logQSLCallsignTextView=itemView.findViewById(R.id.logQSLCallsignTextView) ;
|
||||
logQSOGridTextView=itemView.findViewById(R.id.logQSOGridTextView) ;
|
||||
logQSOStartTimeTextView=itemView.findViewById(R.id.logQSOStartTimeTextView) ;
|
||||
logQSOEndTimeTextView=itemView.findViewById(R.id.logQSOEndTimeTextView) ;
|
||||
logQSOReceiveTextView=itemView.findViewById(R.id.logQSOReceiveTextView) ;
|
||||
logQSOSendTextView=itemView.findViewById(R.id.logQSOSendTextView) ;
|
||||
logQSLBandTextView=itemView.findViewById(R.id.logQSLBandTextView) ;
|
||||
logQSLFreqTextView=itemView.findViewById(R.id.logQSLFreqTextView) ;
|
||||
logQSOModeTextView=itemView.findViewById(R.id.logQSOModeTextView) ;
|
||||
logQSOcCommentTextView=itemView.findViewById(R.id.logQSOcCommentTextView) ;
|
||||
logQSLMyCallsignTextView=itemView.findViewById(R.id.logQSLMyCallsignTextView) ;
|
||||
logQSLMyGridTextView=itemView.findViewById(R.id.logQSLMyGridTextView) ;
|
||||
logQSLWhereTextView=itemView.findViewById(R.id.logQSLWhereTextView) ;
|
||||
logIsQSLTextView=itemView.findViewById(R.id.logIsQSLTextView) ;
|
||||
logQSLHolderConstraintLayout = itemView.findViewById(R.id.logQSLHolderConstraintLayout);
|
||||
logQSLCallsignTextView = itemView.findViewById(R.id.logQSLCallsignTextView);
|
||||
logQSOGridTextView = itemView.findViewById(R.id.logQSOGridTextView);
|
||||
logQSOStartTimeTextView = itemView.findViewById(R.id.logQSOStartTimeTextView);
|
||||
logQSOEndTimeTextView = itemView.findViewById(R.id.logQSOEndTimeTextView);
|
||||
logQSOReceiveTextView = itemView.findViewById(R.id.logQSOReceiveTextView);
|
||||
logQSOSendTextView = itemView.findViewById(R.id.logQSOSendTextView);
|
||||
logQSLBandTextView = itemView.findViewById(R.id.logQSLBandTextView);
|
||||
logQSLFreqTextView = itemView.findViewById(R.id.logQSLFreqTextView);
|
||||
logQSOModeTextView = itemView.findViewById(R.id.logQSOModeTextView);
|
||||
logQSOcCommentTextView = itemView.findViewById(R.id.logQSOcCommentTextView);
|
||||
logQSLMyCallsignTextView = itemView.findViewById(R.id.logQSLMyCallsignTextView);
|
||||
logQSLMyGridTextView = itemView.findViewById(R.id.logQSLMyGridTextView);
|
||||
logQSLWhereTextView = itemView.findViewById(R.id.logQSLWhereTextView);
|
||||
logIsQSLTextView = itemView.findViewById(R.id.logIsQSLTextView);
|
||||
|
||||
itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
|
||||
@Override
|
||||
|
@ -196,18 +210,25 @@ public class LogQSLAdapter extends RecyclerView.Adapter<LogQSLAdapter.LogQSLItem
|
|||
, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
||||
view.setTag(getAdapterPosition());
|
||||
//添加菜单的参数i1:组,i2:id值,i3:显示顺序
|
||||
if (record.isQSL){
|
||||
contextMenu.add(0,0,0
|
||||
,String.format(GeneralVariables.getStringFromResource(R.string.qsl_cancel_confirmation)
|
||||
,record.getCall())).setActionView(view);
|
||||
}else {
|
||||
contextMenu.add(0,1,0
|
||||
,String.format(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation_s)
|
||||
,record.getCall())).setActionView(view);
|
||||
if (record.isQSL) {
|
||||
contextMenu.add(0, 0, 0
|
||||
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_cancel_confirmation)
|
||||
, record.getCall())).setActionView(view);
|
||||
} else {
|
||||
contextMenu.add(0, 1, 0
|
||||
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_manual_confirmation_s)
|
||||
, record.getCall())).setActionView(view);
|
||||
}
|
||||
contextMenu.add(0, 2, 0
|
||||
, String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s)
|
||||
, record.getCall())).setActionView(view);
|
||||
|
||||
if (record.getGridsquare() != null && !record.getGridsquare().equals("")
|
||||
&& record.getMy_gridsquare() != null && !record.getMy_gridsquare().equals("")) {
|
||||
contextMenu.add(0, 3, 0
|
||||
, GeneralVariables.getStringFromResource(R.string.log_menu_location))
|
||||
.setActionView(view);
|
||||
}
|
||||
contextMenu.add(0,2,0
|
||||
,String.format(GeneralVariables.getStringFromResource(R.string.qsl_qrz_confirmation_s)
|
||||
,record.getCall())).setActionView(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
/**
|
||||
* 查询呼号日志的回调。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
/**
|
||||
* 查询通联日志的回调。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
|
||||
/**
|
||||
* 通联过的呼号的日志。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class QSLCallsignRecord {
|
||||
private String callsign;
|
||||
private String mode;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
import com.bg7yoz.ft8cn.GeneralVariables;
|
||||
import com.bg7yoz.ft8cn.R;
|
||||
import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid;
|
||||
|
@ -17,11 +17,12 @@ import java.util.Objects;
|
|||
* isLotW_import是指是否是外部的数据导入,因为用户可能使用了JTDX等软件通联,这样可以把通联的结果导入到FT8CN
|
||||
* isLotW_QSL是指是否被平台确认。
|
||||
* isQSL是指是否被手工确认
|
||||
* @author BG7YOZ
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class QSLRecord {
|
||||
private static final String TAG = "QSLRecord";
|
||||
public long id=-1;
|
||||
public long id = -1;
|
||||
//private long startTime;//起始时间
|
||||
private String qso_date;
|
||||
private String time_on;
|
||||
|
@ -29,7 +30,7 @@ public class QSLRecord {
|
|||
private String time_off;
|
||||
//private long endTime;//结束时间
|
||||
|
||||
private String myCallsign;//我的呼号
|
||||
private final String myCallsign;//我的呼号
|
||||
private String myMaidenGrid;//我的网格
|
||||
private String toCallsign;//对方的呼号
|
||||
private String toMaidenGrid;//对方的网格
|
||||
|
@ -44,7 +45,24 @@ public class QSLRecord {
|
|||
public boolean isLotW_import = false;//是否是从外部数据导入的,此项需要在数据库中比对才能设定
|
||||
public boolean isLotW_QSL = false;//是否是lotw确认的
|
||||
|
||||
public boolean saved=false;//是否被保存到数据库中
|
||||
public boolean saved = false;//是否被保存到数据库中
|
||||
|
||||
/**
|
||||
* 用于SWL QSO记录,记录SWL QSO的条件是收听到双方的信号报告
|
||||
* @param msg FT8消息
|
||||
*/
|
||||
public QSLRecord(Ft8Message msg) {
|
||||
this.qso_date_off = UtcTimer.getYYYYMMDD(msg.utcTime);
|
||||
this.time_off = UtcTimer.getTimeHHMMSS(msg.utcTime);
|
||||
this.myCallsign = msg.callsignFrom;
|
||||
this.toCallsign = msg.callsignTo;
|
||||
wavFrequency=Math.round(msg.freq_hz);
|
||||
sendReport=-100;
|
||||
receivedReport=-100;
|
||||
bandLength=BaseRigOperation.getMeterFromFreq(GeneralVariables.band);//获取波长
|
||||
bandFreq=GeneralVariables.band;
|
||||
comment="SWL By FT8CN";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建通联成功的对象
|
||||
|
@ -87,7 +105,7 @@ public class QSLRecord {
|
|||
String.format("Distance: %s, QSO by FT8CN", distance);
|
||||
}
|
||||
|
||||
public void update(QSLRecord record){
|
||||
public void update(QSLRecord record) {
|
||||
this.qso_date_off = record.qso_date_off;
|
||||
this.time_off = record.time_off;
|
||||
this.toMaidenGrid = record.toMaidenGrid;
|
||||
|
@ -96,19 +114,19 @@ public class QSLRecord {
|
|||
}
|
||||
|
||||
public QSLRecord(HashMap<String, String> map) {
|
||||
isLotW_import=true;//说明是外部导入的数据
|
||||
isLotW_import = true;//说明是外部导入的数据
|
||||
if (map.containsKey("CALL")) {//对方呼号
|
||||
toCallsign = map.get("CALL");
|
||||
}
|
||||
if (map.containsKey("STATION_CALLSIGN")) {//我的呼号
|
||||
myCallsign = map.get("STATION_CALLSIGN");
|
||||
}else {
|
||||
myCallsign="";
|
||||
} else {
|
||||
myCallsign = "";
|
||||
}
|
||||
if (map.containsKey("BAND")) {//载波波长
|
||||
bandLength = map.get("BAND");
|
||||
}else {
|
||||
bandLength="";
|
||||
} else {
|
||||
bandLength = "";
|
||||
}
|
||||
|
||||
if (map.containsKey("FREQ")) {//载波频率
|
||||
|
@ -122,8 +140,8 @@ public class QSLRecord {
|
|||
}
|
||||
if (map.containsKey("MODE")) {//模式
|
||||
mode = map.get("MODE");
|
||||
}else {
|
||||
mode="";
|
||||
} else {
|
||||
mode = "";
|
||||
}
|
||||
if (map.containsKey("QSO_DATE")) {//通联日期
|
||||
qso_date = map.get("QSO_DATE");
|
||||
|
@ -132,8 +150,8 @@ public class QSLRecord {
|
|||
}
|
||||
if (map.containsKey("TIME_ON")) {//通联起始时间
|
||||
time_on = map.get("TIME_ON");
|
||||
}else {
|
||||
time_on="";
|
||||
} else {
|
||||
time_on = "";
|
||||
}
|
||||
if (map.containsKey("QSO_DATE_OFF")) {//通联结束日期,此字段只在JTDX中有。
|
||||
qso_date_off = map.get("QSO_DATE_OFF");
|
||||
|
@ -142,8 +160,8 @@ public class QSLRecord {
|
|||
}
|
||||
if (map.containsKey("TIME_OFF")) {//通联结束时间,n1mm、Log32、JTDX有,Lotw没有
|
||||
time_off = map.get("TIME_OFF");
|
||||
}else {
|
||||
time_off="";
|
||||
} else {
|
||||
time_off = "";
|
||||
}
|
||||
if (map.containsKey("QSL_RCVD")) {//通联互认,lotw中有。
|
||||
isLotW_QSL = Objects.requireNonNull(map.get("QSL_RCVD")).equalsIgnoreCase("Y");
|
||||
|
@ -157,18 +175,17 @@ public class QSLRecord {
|
|||
|
||||
if (map.containsKey("MY_GRIDSQUARE")) {//我的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格
|
||||
myMaidenGrid = map.get("MY_GRIDSQUARE");
|
||||
}else {
|
||||
myMaidenGrid="";
|
||||
} else {
|
||||
myMaidenGrid = "";
|
||||
}
|
||||
|
||||
if (map.containsKey("GRIDSQUARE")) {//对方的网格(lotw,log32有,lotw根据设置不同,也可能没有)N1MM没有网格
|
||||
toMaidenGrid = map.get("GRIDSQUARE");
|
||||
}else {
|
||||
toMaidenGrid="";
|
||||
} else {
|
||||
toMaidenGrid = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (map.containsKey("RST_RCVD")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有
|
||||
try {//要把float转成Long
|
||||
receivedReport = Integer.parseInt(Objects.requireNonNull(map.get("RST_RCVD").trim()));
|
||||
|
@ -176,8 +193,8 @@ public class QSLRecord {
|
|||
e.printStackTrace();
|
||||
Log.e(TAG, "QSLRecord: RST_RCVD:" + e.getMessage());
|
||||
}
|
||||
}else {
|
||||
receivedReport=-120;
|
||||
} else {
|
||||
receivedReport = -120;
|
||||
}
|
||||
|
||||
if (map.containsKey("RST_SENT")) {//接收到的报告。信号报告n1mm,log32,jtdx有,Lotw没有
|
||||
|
@ -187,8 +204,8 @@ public class QSLRecord {
|
|||
e.printStackTrace();
|
||||
Log.e(TAG, "QSLRecord: RST_SENT:" + e.getMessage());
|
||||
}
|
||||
}else {
|
||||
sendReport=-120;
|
||||
} else {
|
||||
sendReport = -120;
|
||||
}
|
||||
if (map.containsKey("COMMENT")) {//注释,JTDX中有
|
||||
comment = map.get("COMMENT");
|
||||
|
@ -200,6 +217,13 @@ public class QSLRecord {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* SWL QSO的提示
|
||||
* @return 提示
|
||||
*/
|
||||
public String swlQSOInfo(){
|
||||
return String.format("QSO of SWL:%s<--%s",toCallsign,myCallsign);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QSLRecord{" +
|
||||
|
@ -225,8 +249,9 @@ public class QSLRecord {
|
|||
", comment='" + comment + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String toHtmlString() {
|
||||
String ss=saved?"<font color=red>, saved=true</font>":", saved=false";
|
||||
String ss = saved ? "<font color=red>, saved=true</font>" : ", saved=false";
|
||||
return "QSLRecord{" +
|
||||
"id=" + id +
|
||||
", qso_date='" + qso_date + '\'' +
|
||||
|
@ -246,10 +271,11 @@ public class QSLRecord {
|
|||
", isQSL=" + isQSL +
|
||||
", isLotW_import=" + isLotW_import +
|
||||
", isLotW_QSL=" + isLotW_QSL +
|
||||
ss+
|
||||
ss +
|
||||
", comment='" + comment + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getBandLength() {
|
||||
return bandLength;
|
||||
}
|
||||
|
@ -283,6 +309,10 @@ public class QSLRecord {
|
|||
return myMaidenGrid;
|
||||
}
|
||||
|
||||
public void setMyMaidenGrid(String myMaidenGrid) {
|
||||
this.myMaidenGrid = myMaidenGrid;
|
||||
}
|
||||
|
||||
public int getSendReport() {
|
||||
return sendReport;
|
||||
}
|
||||
|
@ -331,4 +361,12 @@ public class QSLRecord {
|
|||
public void setReceivedReport(int receivedReport) {
|
||||
this.receivedReport = receivedReport;
|
||||
}
|
||||
|
||||
public void setQso_date(String qso_date) {
|
||||
this.qso_date = qso_date;
|
||||
}
|
||||
|
||||
public void setTime_on(String time_on) {
|
||||
this.time_on = time_on;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用于在ADAPTER中显示内容,此数据在数据库的查询中生成
|
||||
* @author BG7YOZ
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class QSLRecordStr {
|
||||
public class QSLRecordStr implements Serializable {
|
||||
public int id;
|
||||
private String call="";
|
||||
private String gridsquare="";
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package com.bg7yoz.ft8cn.log;
|
||||
|
||||
import com.bg7yoz.ft8cn.Ft8Message;
|
||||
import com.bg7yoz.ft8cn.GeneralVariables;
|
||||
import com.bg7yoz.ft8cn.timer.UtcTimer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 用于计算和处理SWL消息中的QSO记录。
|
||||
* QSO的计算方法:把FT8通联的6个阶段分成3部分:
|
||||
* 1.CQ C1 grid
|
||||
* 2.C1 C2 grid
|
||||
* ---------第一部分---
|
||||
* 3.C2 C1 report
|
||||
* 4.C1 C2 r-report
|
||||
* --------第二部分----
|
||||
* 5.C2 C1 RR73(RRR)
|
||||
* 6.C1 C2 73
|
||||
* --------第三部分----
|
||||
* <p>
|
||||
* 一个基本的QSO,必须有自己的结束点(第三部分),双方的信号报告(在第二部分判断),网格报告可有可无(第一部分)
|
||||
* 以RR73、RRR、73为检查点,符合以上第一、二部分
|
||||
* swlQsoList是个双key的HashMap,用于防止重复记录QSO。
|
||||
* C1与C2顺序不同,代表不同的呼叫方。体现在station_callsign和call字段上
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-07
|
||||
*/
|
||||
public class SWLQsoList {
|
||||
private static final String TAG = "SWLQsoList";
|
||||
//通联成功的列表,防止重复,两个KEY顺序分别是:station_callsign和call,Boolean=true,已经QSO
|
||||
private final HashTable qsoList =new HashTable();
|
||||
|
||||
public SWLQsoList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查有没有QSO消息
|
||||
*
|
||||
* @param newMessages 新的FT8消息
|
||||
* @param allMessages 全部的FT8消息
|
||||
* @param onFoundSwlQso 当有发现的回调
|
||||
*/
|
||||
public void findSwlQso(ArrayList<Ft8Message> newMessages, ArrayList<Ft8Message> allMessages
|
||||
, OnFoundSwlQso onFoundSwlQso) {
|
||||
for (int i = 0; i < newMessages.size(); i++) {
|
||||
Ft8Message msg = newMessages.get(i);
|
||||
if (msg.inMyCall()) continue;//对包含我自己的消息不处理
|
||||
|
||||
if (GeneralVariables.checkFun4_5(msg.extraInfo)//结束标识RRR、RR73、73
|
||||
&& !qsoList.contains(msg.callsignFrom, msg.callsignTo)) {//没有QSO记录
|
||||
|
||||
QSLRecord qslRecord = new QSLRecord(msg);
|
||||
|
||||
if (checkPart2(allMessages, qslRecord)) {//找双方的信号报告,一个基本的QSO,必须有双方的信号报告
|
||||
|
||||
checkPart1(allMessages, qslRecord);//找双方的网格报告,顺便更新time_on的时间
|
||||
|
||||
if (onFoundSwlQso != null) {//触发回调,用于记录到数据库
|
||||
qsoList.put(msg.callsignFrom, msg.callsignTo, true);//把QSO记录保存下来
|
||||
onFoundSwlQso.doFound(qslRecord);//触发找到QSO的动作
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查第2部分是否存在,顺便把信号报告保存到QSLRecord中
|
||||
*
|
||||
* @param allMessages 消息列表
|
||||
* @param record QSLRecord
|
||||
* @return 返回值 没有发现:0,存在:2
|
||||
*/
|
||||
private boolean checkPart2(ArrayList<Ft8Message> allMessages, QSLRecord record) {
|
||||
boolean foundFromReport = false;
|
||||
boolean foundToReport = false;
|
||||
long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间
|
||||
for (int i = allMessages.size() - 1; i >= 0; i--) {
|
||||
Ft8Message msg = allMessages.get(i);
|
||||
if (msg.callsignFrom.equals(record.getMyCallsign())
|
||||
&& msg.callsignTo.equals(record.getToCallsign())
|
||||
&& !foundFromReport) {//callsignFrom发出的信号报告
|
||||
int report = GeneralVariables.checkFun2_3(msg.extraInfo);
|
||||
|
||||
if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间
|
||||
if (report != -100) {
|
||||
record.setSendReport(report);
|
||||
foundFromReport = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.callsignFrom.equals(record.getToCallsign())
|
||||
&& msg.callsignTo.equals(record.getMyCallsign())
|
||||
&& !foundToReport) {//callsignTo发出的信号报告
|
||||
int report = GeneralVariables.checkFun2_3(msg.extraInfo);
|
||||
if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间
|
||||
if (report != -100) {
|
||||
record.setReceivedReport(report);
|
||||
foundToReport = true;
|
||||
}
|
||||
}
|
||||
if (foundToReport && foundFromReport) {//如果双方的信号报告都找到了,就退出循环
|
||||
record.setQso_date(UtcTimer.getYYYYMMDD(time_on));
|
||||
record.setTime_on(UtcTimer.getTimeHHMMSS(time_on));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return foundToReport && foundFromReport;//双方的信号报告都有,才算一个QSO
|
||||
}
|
||||
|
||||
/**
|
||||
* 查第2部分是否存在,顺便把网格报告保存到QSLRecord中
|
||||
*
|
||||
* @param allMessages 消息列表
|
||||
* @param record QSLRecord
|
||||
*/
|
||||
private void checkPart1(ArrayList<Ft8Message> allMessages, QSLRecord record) {
|
||||
boolean foundFromGrid = false;
|
||||
boolean foundToGrid = false;
|
||||
long time_on = System.currentTimeMillis();//先把当前的时间作为最早时间
|
||||
for (int i = allMessages.size() - 1; i >= 0; i--) {
|
||||
Ft8Message msg = allMessages.get(i);
|
||||
if (!foundFromGrid
|
||||
&& msg.callsignFrom.equals(record.getMyCallsign())
|
||||
&& (msg.callsignTo.equals(record.getToCallsign()) || msg.checkIsCQ())) {//callsignFrom的网格报告
|
||||
|
||||
if (GeneralVariables.checkFun1_6(msg.extraInfo)) {
|
||||
record.setMyMaidenGrid(msg.extraInfo.trim());
|
||||
foundFromGrid = true;
|
||||
}
|
||||
if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间
|
||||
}
|
||||
|
||||
if (!foundToGrid
|
||||
&& msg.callsignFrom.equals(record.getToCallsign())
|
||||
&& (msg.callsignTo.equals(record.getMyCallsign())|| msg.checkIsCQ())) {//callsignTo发出的信号报告
|
||||
if (GeneralVariables.checkFun1_6(msg.extraInfo)) {
|
||||
record.setToMaidenGrid(msg.extraInfo.trim());
|
||||
foundToGrid = true;
|
||||
}
|
||||
if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间
|
||||
}
|
||||
if (foundToGrid && foundFromGrid) {//如果双方的信号报告都找到了,就退出循环
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundFromGrid || foundToGrid) {//发现网格报告,至少一个方向的
|
||||
record.setQso_date(UtcTimer.getYYYYMMDD(time_on));
|
||||
record.setTime_on(UtcTimer.getTimeHHMMSS(time_on));
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnFoundSwlQso {
|
||||
void doFound(QSLRecord record);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
package com.bg7yoz.ft8cn.maidenhead;
|
||||
|
||||
/**
|
||||
* 梅登海德网格的处理。包括经纬度换算、距离计算。
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -12,11 +16,6 @@ import com.google.android.gms.maps.model.LatLng;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 计算梅登海德网格工具
|
||||
*
|
||||
* @author BG7YOZ
|
||||
*/
|
||||
public class MaidenheadGrid {
|
||||
private static final String TAG = "MaidenheadGrid";
|
||||
private static final double EARTH_RADIUS = 6371393; // 平均半径,单位:m;不是赤道半径。赤道为6378左右
|
||||
|
@ -221,6 +220,10 @@ public class MaidenheadGrid {
|
|||
latLngs[2] = new LatLng(lat2,lng2);
|
||||
latLngs[3] = new LatLng(lat2,lng1);
|
||||
|
||||
// Log.e(TAG, "gridToPolygon: latLng0"+latLngs[0].toString() );
|
||||
// Log.e(TAG, "gridToPolygon: latLng1"+latLngs[1].toString() );
|
||||
// Log.e(TAG, "gridToPolygon: latLng2"+latLngs[2].toString() );
|
||||
// Log.e(TAG, "gridToPolygon: latLng3"+latLngs[3].toString() );
|
||||
return latLngs;
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue