diff --git a/ft8cn/app/build.gradle b/ft8cn/app/build.gradle index 510807d..5ef74ba 100644 --- a/ft8cn/app/build.gradle +++ b/ft8cn/app/build.gradle @@ -22,7 +22,7 @@ android { minSdk 23 targetSdk 33 versionCode 1 - versionName '0.91' + versionName '0.92' //testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dataBinding{ @@ -83,13 +83,10 @@ dependencies { implementation 'com.google.guava:guava:31.1-jre'//用于HashTable(多key的HashMap) - //testImplementation 'junit:junit:4.13.2' - //androidTestImplementation 'androidx.test.ext:junit:1.1.3' - //androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation files('src/libs/MPAndroidChartv_3.1.0.jar') implementation files('src/libs/commons-net-3.6.jar')////用于时间同步 implementation files('src/libs/nanohttpd-2.2.0.jar') implementation files('src/libs/osmdroid-android-6.1.14.aar')//地图控件 - //implementation files('src/libs/resample-release.aar')//DS1UFX 提供的(tr)uSDX中的需要重采样库 + } \ No newline at end of file diff --git a/ft8cn/app/libs/arm64-v8a/libft8cn.so b/ft8cn/app/libs/arm64-v8a/libft8cn.so index 3126cb2..0ad86e6 100644 Binary files a/ft8cn/app/libs/arm64-v8a/libft8cn.so and b/ft8cn/app/libs/arm64-v8a/libft8cn.so differ diff --git a/ft8cn/app/libs/armeabi-v7a/libft8cn.so b/ft8cn/app/libs/armeabi-v7a/libft8cn.so index e8c605d..a1e5310 100644 Binary files a/ft8cn/app/libs/armeabi-v7a/libft8cn.so and b/ft8cn/app/libs/armeabi-v7a/libft8cn.so differ diff --git a/ft8cn/app/libs/x86/libft8cn.so b/ft8cn/app/libs/x86/libft8cn.so index 5c2054b..591dacd 100644 Binary files a/ft8cn/app/libs/x86/libft8cn.so and b/ft8cn/app/libs/x86/libft8cn.so differ diff --git a/ft8cn/app/libs/x86_64/libft8cn.so b/ft8cn/app/libs/x86_64/libft8cn.so index 0775b40..5402364 100644 Binary files a/ft8cn/app/libs/x86_64/libft8cn.so and b/ft8cn/app/libs/x86_64/libft8cn.so differ diff --git a/ft8cn/app/src/main/assets/readme.txt b/ft8cn/app/src/main/assets/readme.txt index 39e73d1..1a54a0a 100644 --- a/ft8cn/app/src/main/assets/readme.txt +++ b/ft8cn/app/src/main/assets/readme.txt @@ -14,7 +14,20 @@ Please click "FAQ" if you have good suggestions or questions . BG7YOZ 2022-07-01 - 2023-09-10(0.91) + 2024-01-22(0.92) + 1.增加瀑布图中消息的已通联标识。 + 2.新增支持的电台型号。 + 3.增加串口参数设置。 + 4.新增解码的消息类型,支持全部FT8消息类型。 + 5.修正串口错误提示只有中文的问题。 + 6.完善SWL QSO日志记录。 + 7.完善消息中带有/P或/R后缀呼号的通联程序。 + + + 2023-09-14(0.91 patch 1) + 1.修正Yaesu FT-891/991 选择USB-DATA模式错误。 + + 2023-09-11(0.91) 1.修正发射非标准消息(i3=4)RR73被误发成73的问题。 2.修正因多重解码模式下,无法及时回复上一周期解码的消息以及漏发73的问题。 3.修正生成双方都是复合呼号的消息时,发送方的呼号可能不正确的问题。 @@ -308,4 +321,5 @@ BG7YOZ BG2EFX,提供大数据量的日志用于测试。 DS1UFX,贡献(tr)uSDX audio over cat代码。 BG8HT,提供某型号电台进行测试。 + UB6LUM,帮助解决某型号电台的操作模式设置。 diff --git a/ft8cn/app/src/main/assets/rigaddress.txt b/ft8cn/app/src/main/assets/rigaddress.txt index 164b458..630672a 100644 --- a/ft8cn/app/src/main/assets/rigaddress.txt +++ b/ft8cn/app/src/main/assets/rigaddress.txt @@ -5,11 +5,11 @@ ICOM IC-7200,76,19200,0 ICOM IC-7300,94,115200,0 ICOM IC-7400,66,19200,0 ICOM IC-7410,80,19200,0 -ICOM IC-746,56,19200,0 -ICOM IC-746PRO,66,19200,0 -ICOM IC-756PRO,5C,19200,0 -ICOM IC-756PRO2,64,19200,0 -ICOM IC-756PRO3,6E,19200,0 +ICOM IC-746,56,19200,22 +ICOM IC-746PRO,66,19200,22 +ICOM IC-756PRO,5C,19200,22 +ICOM IC-756PRO2,64,19200,22 +ICOM IC-756PRO3,6E,19200,22 ICOM IC-7600,7A,19200,0 ICOM IC-7610,98,115200,0 ICOM IC-7700,74,19200,0 @@ -21,9 +21,22 @@ ICOM IC-9700,A2,115200,0 ICOM IC-R8600,96,115200,0 ICOM ID-52A,A6,115200,0 ICOM IC-706MKIIG,58,19200,0 +ICOM IC-706MKII,4E,19200,0 +ICOM IC-703,68,19200,0 +ICOM IC-707(725A),3E,19200,0 +ICOM IC-718,5E,19200,0 +ICOM IC-725,28,19200,0 +ICOM IC-746PRO,66,19200,0 +ICOM IC-756PRO3,6E,19200,0 +ICOM IC-775,46,19200,0 +ICOM IC-910H,60,19200,0 +ICOM IC-78,62,19200,0 +#XIEGU(协谷) X6100(ft8cns),A4,19200,20 XIEGU(协谷) X6100(U-DIG),A4,19200,13 XIEGU(协谷) G90S(U-DIG),70,19200,13 XIEGU(协谷) G90S(USB),70,19200,9 +XIEGU(协谷) G106C(U-DIG),70,19200,13 +XIEGU(协谷) G106C(USB),70,19200,9 XIEGU(协谷) X5105,70,19200,9 XIEGU(协谷) X108,70,19200,9 GUOHE(国赫) Q900,00,19200,8 @@ -31,6 +44,7 @@ GUOHE(国赫) PMR-171,00,19200,8 YAESU FT-450(D),00,4800,4 YAESU FT-817,00,4800,1 YAESU FT-818,00,4800,1 +YAESU FT-847,00,4800,21 YAESU FT-857,00,4800,1 YAESU FT-891/991(USB),00,4800,2 YAESU FT-891/991(DATA-USB),00,4800,19 @@ -53,4 +67,5 @@ FX-4CR,00,115200,7 Qrp Labs QDX,00,9600,7 UA3REO Wolf SDR(DIGU),00,4800,15 UA3REO Wolf SDR(USB),00,4800,16 -(tr)uSDX,00,115200,17 \ No newline at end of file +(tr)uSDX (audio over cat),00,115200,17 +(tr)uSDX (TS-480),00,9600,7 \ No newline at end of file diff --git a/ft8cn/app/src/main/assets/serial_help.txt b/ft8cn/app/src/main/assets/serial_help.txt new file mode 100644 index 0000000..5ad2539 --- /dev/null +++ b/ft8cn/app/src/main/assets/serial_help.txt @@ -0,0 +1,5 @@ +关于串口设置 +数据位:一般可以是6位、7位或8位,多数是8位。 +停止位:一般可以是1位、1.5位或2位,多数是1位。 +校验位:校验位一般用来判断接收的数据位有无错误,一般采用奇偶校验或无校验。 +注:FT8CN默认的串口设置是数据位8、停止位1、无校验。不同的电台的串口设置可能不尽相同,具体设置请与电台的参数一致。 \ No newline at end of file diff --git a/ft8cn/app/src/main/assets/serial_help_en.txt b/ft8cn/app/src/main/assets/serial_help_en.txt new file mode 100644 index 0000000..0d223cf --- /dev/null +++ b/ft8cn/app/src/main/assets/serial_help_en.txt @@ -0,0 +1,7 @@ +About Serial Connection Settings: +Data bits: can be 6 bits, 7 bits, or 8 bits (8 bits is most common) +Stop bits: can be 1 bit, 1.5 bits, or 2 bits (1 bit is most common) +Parity bit: Parity bit is used to check for errors in received data and can be odd, even, or no parity. + +Note: The default serial port settings for FT8CN are 8 data bits, 1 stop bit, and no parity. +The serial connection settings may vary for different radios. Please ensure it matches the settings in your radio. \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java index fe6519a..eaf1ba8 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java @@ -12,6 +12,7 @@ package com.bg7yoz.ft8cn; */ import android.annotation.SuppressLint; +import android.util.Log; import androidx.annotation.NonNull; @@ -47,6 +48,15 @@ public class Ft8Message { public String extraInfo = null; public String maidenGrid = null; + + public String rtty_state =null;//RTTY RU(i3=3类型)的地区名,两位字母如:CA、AL + public int r_flag=0;//RTTY RU,EU VHF(i3=3,i3=5类型)的R标志 + public int rtty_tu;//RTTY RU(i3=3类型)的TU;标志 + public int eu_serial;//EU VHF i3=5中的序列号 + public String arrl_rac;//Field day 消息,Arrl rac + public String arrl_class;//Field day 发射级别 + public String dx_call_to2;//DXpediton 消息中第二个接收的呼号 + public int report = -100;//当-100时,意味着没有信号报告 public long callFromHash10 = 0;//12位长度的哈希码 public long callFromHash12 = 0;//12位长度的哈希码 @@ -80,6 +90,7 @@ public class Ft8Message { + @NonNull @SuppressLint({"SimpleDateFormat", "DefaultLocale"}) @Override @@ -172,6 +183,16 @@ public class Ft8Message { hashList.addHash(callFromHash12, callsignFrom); hashList.addHash(callFromHash22, callsignFrom); + //rtty ru(i3=3)消息新增的 + rtty_tu = message.rtty_tu; + rtty_state = message.rtty_state; + r_flag =message.r_flag; + eu_serial =message.eu_serial; + //field day 增加的 + arrl_class = message.arrl_class; + arrl_rac = message.arrl_rac; + dx_call_to2 = message.dx_call_to2; + //Log.d(TAG, String.format("i3:%d,n3:%d,From:%s,To:%s", i3, n3, getCallsignFrom(), getCallsignTo())); } @@ -200,6 +221,7 @@ public class Ft8Message { * * @return String */ + @SuppressLint("DefaultLocale") public String getMessageText() { if (i3 == 0 && n3 == 0) {//说明是自由文本 @@ -209,6 +231,49 @@ public class Ft8Message { return extraInfo.toUpperCase().substring(0, 13); } } + if (i3 == 0 && (n3 == 3 || n3 == 4)) {//说明是野外日 + return String.format("%s %s %s%d%s %s" + ,callsignTo + ,callsignFrom + ,r_flag==0?"":"R " + ,eu_serial + ,arrl_class + ,arrl_rac + ); + } + + if (i3 == 0 && (n3 == 1)) {//说明是DXpedition + + return String.format("%s RR73; %s %s %s%d" + ,callsignTo + ,dx_call_to2 + ,hashList.getCallsign(new long[]{callFromHash10}) + ,report > 0 ? "+" : "-" + ,report + ); + } + + if (i3 == 3){//说明是RTTY RU消息 + return String.format("%s%s %s %s%d %s" + ,rtty_tu==0?"":"TU; " + ,callsignTo + ,callsignFrom + ,r_flag==0?"":"R " + ,report + ,rtty_state); + } + + if (i3 == 5){//说明是EU VHF R 570007 JO22DB + return String.format("%s %s %s%d%04d %s" + , callsignTo + , callsignFrom + , r_flag == 0?"":"R " + , report + , eu_serial + , maidenGrid + ).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(); @@ -217,15 +282,6 @@ public class Ft8Message { return String.format("%s %s %s", callsignTo, callsignFrom, extraInfo).trim(); } - /** - * 返回解码消息带信噪比的内容 - * - * @return 内容 - */ - @SuppressLint("DefaultLocale") - public String getMessageTextWithDb() { - return String.format("%d %s %s %s", snr, callsignTo, callsignFrom, extraInfo).trim(); - } /** * 返回消息的延迟时间。可能不一定对,待研究清楚解码算法后在确定 @@ -288,10 +344,13 @@ public class Ft8Message { * @return boolean */ public boolean inMyCall() { - if (GeneralVariables.myCallsign.length() == 0) return false; - return this.callsignFrom.contains(GeneralVariables.myCallsign) - || this.callsignTo.contains(GeneralVariables.myCallsign); - //return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals("")); + //判断是不是自己,有时候,我的呼号带/P或/R,对方可能会丢掉这个后缀 +// if (GeneralVariables.myCallsign.length() == 0) return false; + + return GeneralVariables.checkIsMyCallsign(this.callsignFrom) + ||GeneralVariables.checkIsMyCallsign(this.callsignTo); +// return this.callsignFrom.contains(GeneralVariables.myCallsign) +// || this.callsignTo.contains(GeneralVariables.myCallsign); } /* i3.n3类型 基本目的 消息范例 位字段标签 @@ -433,7 +492,9 @@ t71 遥感数据,最多18位十六进制数字 case 2: return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.std_msg)); case 5: + return String.format(format, i, 0, "EU VHF"); case 3: + return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.rtty_ru_msg)); case 4: return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg)); case 0: diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java index becafd5..89687b8 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java @@ -6,6 +6,7 @@ package com.bg7yoz.ft8cn; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; import androidx.lifecycle.MutableLiveData; @@ -18,6 +19,7 @@ import com.bg7yoz.ft8cn.html.HtmlContext; import com.bg7yoz.ft8cn.icom.IcomAudioUdp; import com.bg7yoz.ft8cn.log.QSLRecord; import com.bg7yoz.ft8cn.rigs.BaseRigOperation; +import com.bg7yoz.ft8cn.serialport.UsbSerialPort; import com.bg7yoz.ft8cn.timer.UtcTimer; import java.util.ArrayList; @@ -171,6 +173,9 @@ public class GeneralVariables { public static int civAddress = 0xa4;//civ地址 public static int baudRate = 19200;//波特率 public static long band = 14074000;//载波频段 + public static int serialDataBits=8;//默认是8 + public static int serialParity = 0;//UsbSerialPort.PARITY_NONE默认是0,即:无 + public static int serialStopBits=1;//停止位的对应关系:1=1,2=3,3=1.5 public static int instructionSet = 0;//指令集,0:icom,1:yaesu 2 代,2:yaesu 3代。 public static int bandListIndex = -1;//电台波段的索引值 public static MutableLiveData mutableBandChange = new MutableLiveData<>();//波段索引值变化 @@ -254,6 +259,37 @@ public class GeneralVariables { return QSL_Callsign_list_other_band.contains(callsign); } + /** + * 检查呼号中是不是含有我的呼号 + * @param callsign 呼号 + * @return boolean + */ + static public boolean checkIsMyCallsign(String callsign){ + if (GeneralVariables.myCallsign.length() == 0) return false; + String temp = getShortCallsign(GeneralVariables.myCallsign); + return callsign.contains(temp); + } + + /** + * 对于复合呼号,获取去掉前缀或后缀的呼号 + * @return 呼号 + */ + static public String getShortCallsign(String callsign){ + if (callsign.contains("/")){ + String[] temp = callsign.split("/"); + int max =0; + int max_index =0; + for (int i = 0; i < temp.length; i++) { + if (temp[i].length()>max){ + max = temp[i].length(); + max_index=i; + } + } + return temp[max_index]; + }else{ + return callsign; + } + } /** * 查该呼号是不是在关注的呼号列表中 diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java index 5a0d7f8..50aa7c5 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java @@ -16,7 +16,6 @@ import android.Manifest; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.annotation.SuppressLint; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -61,12 +60,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; import java.util.ArrayList; -import java.util.Date; import java.util.List; @@ -232,30 +226,16 @@ public class MainActivity extends AppCompatActivity { //观察是不是flex radio - mainViewModel.mutableIsFlexRadio.observe(this, new Observer() { @Override public void onChanged(Boolean aBoolean) { - //if (floatView==null) return; if (aBoolean) { //添加flex配置按钮 floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon , new View.OnClickListener() { @Override public void onClick(View view) { - navController.navigate(R.id.flexRadioInfoFragment); - -// if (mainViewModel.baseRig != null) { -// if (mainViewModel.baseRig.isConnected()) { -// ToastMessage.show("flex connected"); -// }else { -// ToastMessage.show("flex disconnected"); -// } -// }else { -// ToastMessage.show("rig is null"); -// } - } }); } else {//删除flex配置按钮 @@ -264,6 +244,24 @@ public class MainActivity extends AppCompatActivity { } }); + //观察是不是xiegu radio + mainViewModel.mutableIsXieguRadio.observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + //添加xiegu配置按钮 + floatView.addButton(R.id.xiegu_radio, "xiegu_radio", R.drawable.xiegulogo32 + , new View.OnClickListener() { + @Override + public void onClick(View view) { + navController.navigate(R.id.xieguInfoFragment); + } + }); + } else {//删除xiegu配置按钮 + floatView.deleteButtonByName("xiegu_radio"); + } + } + }); //关闭串口设备列表按钮 binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() { diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java index 5f73bd8..05e9527 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java @@ -46,11 +46,13 @@ import com.bg7yoz.ft8cn.connector.CableSerialPort; import com.bg7yoz.ft8cn.connector.ConnectMode; import com.bg7yoz.ft8cn.connector.FlexConnector; import com.bg7yoz.ft8cn.connector.IComWifiConnector; +import com.bg7yoz.ft8cn.connector.X6100Connector; import com.bg7yoz.ft8cn.database.ControlMode; import com.bg7yoz.ft8cn.database.DatabaseOpr; import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns; import com.bg7yoz.ft8cn.database.OperationBand; import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.RadioTcpClient; import com.bg7yoz.ft8cn.ft8listener.FT8SignalListener; import com.bg7yoz.ft8cn.ft8listener.OnFt8Listen; import com.bg7yoz.ft8cn.ft8transmit.FT8TransmitSignal; @@ -76,9 +78,11 @@ import com.bg7yoz.ft8cn.rigs.KenwoodTS590Rig; import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; import com.bg7yoz.ft8cn.rigs.TrUSDXRig; import com.bg7yoz.ft8cn.rigs.Wolf_sdr_450Rig; +import com.bg7yoz.ft8cn.rigs.XieGu6100NetRig; import com.bg7yoz.ft8cn.rigs.XieGu6100Rig; import com.bg7yoz.ft8cn.rigs.XieGuRig; import com.bg7yoz.ft8cn.rigs.Yaesu2Rig; +import com.bg7yoz.ft8cn.rigs.Yaesu2_847Rig; import com.bg7yoz.ft8cn.rigs.Yaesu38Rig; import com.bg7yoz.ft8cn.rigs.Yaesu38_450Rig; import com.bg7yoz.ft8cn.rigs.Yaesu39Rig; @@ -89,6 +93,7 @@ import com.bg7yoz.ft8cn.timer.UtcTimer; import com.bg7yoz.ft8cn.ui.ToastMessage; import com.bg7yoz.ft8cn.wave.HamRecorder; import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; +import com.bg7yoz.ft8cn.x6100.X6100Radio; import java.io.File; import java.io.IOException; @@ -126,6 +131,7 @@ public class MainViewModel extends ViewModel { public ArrayList currentMessages = null;//本周期解码的消息(用于画到频谱上) public MutableLiveData mutableIsFlexRadio = new MutableLiveData<>();//是不是flex电台 + public MutableLiveData mutableIsXieguRadio = new MutableLiveData<>();//是不是flex电台 private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool(); private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool(); @@ -251,6 +257,7 @@ public class MainViewModel extends ViewModel { hamRecorder.startRecord(); mutableIsFlexRadio.setValue(false); + mutableIsXieguRadio.setValue(false); //创建用于显示时间的计时器 utcTimer = new UtcTimer(10, false, new OnUtcTimer() { @@ -494,8 +501,10 @@ public class MainViewModel extends ViewModel { int count = 0; for (Ft8Message msg : messages) { //与我的呼号有关,与关注的呼号有关 - if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign) - || msg.getCallsignTo().equals(GeneralVariables.myCallsign) + //if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign) + if (GeneralVariables.checkIsMyCallsign(msg.getCallsignFrom()) + //|| msg.getCallsignTo().equals(GeneralVariables.myCallsign) + || GeneralVariables.checkIsMyCallsign(msg.getCallsignTo()) || GeneralVariables.callsignInFollow(msg.getCallsignFrom()) || (GeneralVariables.callsignInFollow(msg.getCallsignTo()) && (msg.getCallsignTo() != null)) || (GeneralVariables.autoFollowCQ && msg.checkIsCQ())) {//是CQ,并且允许关注CQ @@ -768,6 +777,54 @@ public class MainViewModel extends ViewModel { }, 3000); } + /** + * 连接到协谷Radio + * + * @param context context + * @param xieguRadio X6100Radio对象 + */ + public void connectXieguRadioRig(Context context, X6100Radio xieguRadio) { + if (GeneralVariables.connectMode == ConnectMode.NETWORK) { + if (baseRig != null) { + if (baseRig.getConnector() != null) { + baseRig.getConnector().disconnect(); + } + } + } + GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式 + X6100Connector xieguConnector = new X6100Connector(context, xieguRadio, GeneralVariables.controlMode); + xieguConnector.setOnWaveDataReceived(new X6100Connector.OnWaveDataReceived() { + @Override + public void OnDataReceived(int bufferLen, float[] buffer) { + //Log.e(TAG,String.format("data len:%d",bufferLen)); + hamRecorder.doOnWaveDataReceived(bufferLen, buffer); + } + }); + + + xieguConnector.connect(); + connectRig(); + xieguConnector.setBaseRig(baseRig); + //接收电台发回的数据 + xieguRadio.setOnReceiveDataListener(new X6100Radio.OnReceiveDataListener() { + @Override + public void onDataReceive(byte[] data) { + baseRig.onReceiveData(data); + } + }); + + + baseRig.setOnRigStateChanged(onRigStateChanged); + baseRig.setConnector(xieguConnector); + + new Handler().postDelayed(new Runnable() {//连接是需要时间的,等2秒再设置频率 + @Override + public void run() { + setOperationBand();//设置载波频率 + } + }, 3000); + } + /** * 根据指令集创建不同型号的电台 @@ -778,11 +835,17 @@ public class MainViewModel extends ViewModel { //此处判断是用什么类型的电台,ICOM,YAESU 2,YAESU 3 switch (GeneralVariables.instructionSet) { case InstructionSet.ICOM: - baseRig = new IcomRig(GeneralVariables.civAddress); + baseRig = new IcomRig(GeneralVariables.civAddress,true); + break; + case InstructionSet.ICOM_756: + baseRig = new IcomRig(GeneralVariables.civAddress,false); break; case InstructionSet.YAESU_2: baseRig = new Yaesu2Rig(); break; + case InstructionSet.YAESU_847: + baseRig = new Yaesu2_847Rig(); + break; case InstructionSet.YAESU_3_9: baseRig = new Yaesu39Rig(false);//yaesu3代指令,9位频率,usb模式 break; @@ -819,6 +882,13 @@ public class MainViewModel extends ViewModel { case InstructionSet.FLEX_NETWORK: baseRig = new FlexNetworkRig(); break; + case InstructionSet.XIEGU_6100_FT8CNS: + if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//只在网络模式下工作 + baseRig = new XieGu6100NetRig(GeneralVariables.civAddress);//协谷6100ft8cns模式 + }else{//否则使用传统的模式 + baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100 + } + break; case InstructionSet.XIEGU_6100: baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100 break; @@ -841,11 +911,11 @@ public class MainViewModel extends ViewModel { if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK) || ((GeneralVariables.instructionSet == InstructionSet.ICOM - ||GeneralVariables.instructionSet==InstructionSet.XIEGU_6100) + || GeneralVariables.instructionSet==InstructionSet.XIEGU_6100 + || GeneralVariables.instructionSet==InstructionSet.XIEGU_6100_FT8CNS) && GeneralVariables.connectMode == ConnectMode.NETWORK)) { hamRecorder.setDataFromLan(); } else { - //hamRecorder.setDataFromMic(); if (GeneralVariables.controlMode != ControlMode.CAT || baseRig == null || !baseRig.supportWaveOverCAT()) { hamRecorder.setDataFromMic(); @@ -855,6 +925,7 @@ public class MainViewModel extends ViewModel { } mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK); + mutableIsXieguRadio.postValue(GeneralVariables.instructionSet == InstructionSet.XIEGU_6100_FT8CNS); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java index 4ce291d..7249fea 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/BaseRigConnector.java @@ -91,6 +91,13 @@ public class BaseRigConnector { public void sendWaveData(float[] data){ //留给网络方式发送音频流 } + public void sendFt8A91(byte[] a91,float baseFreq){ + //用于给x6100的ft8cns模式 + } + + public void setRFVolume(int volume){ + //用于给x6100的ft8cns模式 + } //2023-08-16 由DS1UFX提交修改(基于0.9版),用于(tr)uSDX audio over cat的支持。 public void receiveWaveData(byte[] data){ diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java index fa708fa..c0ac18e 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableConnector.java @@ -14,11 +14,11 @@ import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager; * @date 2023-03-20 */ public class CableConnector extends BaseRigConnector { - private static final String TAG="CableConnector"; + private static final String TAG = "CableConnector"; //2023-08-16 由DS1UFX提交修改(基于0.9版),用于(tr)uSDX audio over cat的支持。 public interface OnCableDataReceived { - void OnWaveReceived(int bufferLen,float[] buffer); + void OnWaveReceived(int bufferLen, float[] buffer); } private final CableSerialPort cableSerialPort; @@ -26,26 +26,26 @@ public class CableConnector extends BaseRigConnector { private final BaseRig cableConnectedRig; private OnCableDataReceived onCableDataReceived; - public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate - //, int controlMode) { + public CableConnector(Context context, CableSerialPort.SerialPort serialPort, int baudRate + //, int controlMode) { , int controlMode, BaseRig cableConnectedRig) { super(controlMode); this.cableConnectedRig = cableConnectedRig; - cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged()); - cableSerialPort.ioListener=new SerialInputOutputManager.Listener() { + cableSerialPort = new CableSerialPort(context, serialPort, baudRate, getOnConnectorStateChanged()); + cableSerialPort.ioListener = new SerialInputOutputManager.Listener() { @Override public void onNewData(byte[] data) { - if (getOnConnectReceiveData()!=null){ + if (getOnConnectReceiveData() != null) { getOnConnectReceiveData().onData(data); } } @Override public void onRunError(Exception e) { - Log.e(TAG, "CableConnector error: "+e.getMessage() ); - getOnConnectorStateChanged().onRunError("与串口失去连接:"+e.getMessage()); + Log.e(TAG, "CableConnector error: " + e.getMessage()); + getOnConnectorStateChanged().onRunError("与串口失去连接:" + e.getMessage()); } - } ; + }; //connect(); } @@ -58,10 +58,12 @@ public class CableConnector extends BaseRigConnector { @Override public void setPttOn(boolean on) { //只处理RTS和DTR - switch (getControlMode()){ - case ControlMode.DTR: cableSerialPort.setDTR_On(on);//打开和关闭DTR + switch (getControlMode()) { + case ControlMode.DTR: + cableSerialPort.setDTR_On(on);//打开和关闭DTR break; - case ControlMode.RTS:cableSerialPort.setRTS_On(on);//打开和关闭RTS + case ControlMode.RTS: + cableSerialPort.setRTS_On(on);//打开和关闭RTS break; } } @@ -87,12 +89,12 @@ public class CableConnector extends BaseRigConnector { // } @Override - public void receiveWaveData(float[] data){ + public void receiveWaveData(float[] data) { Log.i(TAG, "received wave data"); - if (onCableDataReceived!=null){ + if (onCableDataReceived != null) { Log.i(TAG, "call onCableDataReceived.OnWaveReceived"); - onCableDataReceived.OnWaveReceived(data.length,data); + onCableDataReceived.OnWaveReceived(data.length, data); } } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java index 805111b..595d757 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/CableSerialPort.java @@ -20,6 +20,8 @@ import android.os.Build; import android.util.Log; import com.bg7yoz.ft8cn.BuildConfig; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; import com.bg7yoz.ft8cn.serialport.CdcAcmSerialDriver; import com.bg7yoz.ft8cn.serialport.UsbSerialDriver; import com.bg7yoz.ft8cn.serialport.UsbSerialPort; @@ -134,7 +136,7 @@ public class CableSerialPort { } if (driver == null) { if (onStateChanged!=null){ - onStateChanged.onRunError("无法连接串口,没有驱动或串口不存在!"); + onStateChanged.onRunError(GeneralVariables.getStringFromResource(R.string.serial_no_driver)); } return false; } @@ -164,7 +166,7 @@ public class CableSerialPort { } if (usbConnection == null) { if (onStateChanged!=null){ - onStateChanged.onRunError("无法连接串口,可能没有访问USB设备的权限!"); + onStateChanged.onRunError(GeneralVariables.getStringFromResource(R.string.serial_connect_no_access)); } return false; @@ -172,7 +174,13 @@ public class CableSerialPort { try { usbSerialPort.open(usbConnection); //波特率、停止位 - usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + //usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE); + Log.d(TAG,String.format("serial:baud rate:%d,data bits:%d,stop bits:%d,parity bit:%d" + ,baudRate,GeneralVariables.serialDataBits + ,GeneralVariables.serialStopBits + ,GeneralVariables.serialParity)); + usbSerialPort.setParameters(baudRate, GeneralVariables.serialDataBits + , GeneralVariables.serialStopBits, GeneralVariables.serialParity); usbIoManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() { @Override public void onNewData(byte[] data) { @@ -201,7 +209,8 @@ public class CableSerialPort { } catch (Exception e) { Log.e(TAG, "串口打开失败: " + e.getMessage()); if (onStateChanged!=null){ - onStateChanged.onRunError("串口打开失败: " + e.getMessage()); + onStateChanged.onRunError(GeneralVariables.getStringFromResource(R.string.serial_connect_failed) + + e.getMessage()); } disconnect(); return false; diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java index e1475cd..6876b81 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/FlexConnector.java @@ -21,7 +21,7 @@ import java.io.DataInputStream; import java.io.IOException; /** - * 有线连接方式的Connector,这里是指USB方式的 + * flex网络连接方式的Connector * @author BGY70Z * @date 2023-03-20 */ @@ -35,7 +35,7 @@ public class FlexConnector extends BaseRigConnector { public int maxRfPower; public int maxTunePower; - private static final String TAG = "CableConnector"; + private static final String TAG = "FlexConnector"; private FlexRadio flexRadio; @@ -108,7 +108,7 @@ public class FlexConnector extends BaseRigConnector { //Log.e(TAG, "onResponse: "+response.resultStatus()); } - Log.e(TAG, "onResponse: command:"+response.flexCommand.toString()); + Log.d(TAG, "onResponse: command:"+response.flexCommand.toString()); //Log.e(TAG, "onResponse: "+response.resultStatus()); Log.e(TAG, "onResponse: "+response.rawData ); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/X6100Connector.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/X6100Connector.java new file mode 100644 index 0000000..f4e377c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/connector/X6100Connector.java @@ -0,0 +1,283 @@ +package com.bg7yoz.ft8cn.connector; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.FlexCommand; +import com.bg7yoz.ft8cn.flex.FlexMeterInfos; +import com.bg7yoz.ft8cn.flex.FlexMeterList; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.RadioTcpClient; +import com.bg7yoz.ft8cn.flex.VITA; +import com.bg7yoz.ft8cn.rigs.BaseRig; +import com.bg7yoz.ft8cn.ui.ToastMessage; +import com.bg7yoz.ft8cn.x6100.X6100Meters; +import com.bg7yoz.ft8cn.x6100.X6100Radio; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Objects; + +/** + * 网络连接方式连接xiegu ft8cns + * @author BGY70Z + * @date 2023-12-01 + */ +public class X6100Connector extends BaseRigConnector { + + public interface OnWaveDataReceived{ + void OnDataReceived(int bufferLen,float[] buffer); + } + //public int maxRfPower; + //public int maxTunePower; + + private static final String TAG = "X6100Connector"; + + private X6100Radio xieguRadio; + + private OnWaveDataReceived onWaveDataReceived; + + private BaseRig baseRig; + private boolean streamIsOn =false; + + public float maxTXPower=10.0f; + public MutableLiveData mutableMaxTxPower = new MutableLiveData<>(); + + + public X6100Connector(Context context, X6100Radio xiegRadio, int controlMode) { + super(controlMode); + this.xieguRadio = xiegRadio; + setXieguRadioInterface(); + + } + + public static short[] byteDataTo16BitData(byte[] buffer){ + short[] data=new short[buffer.length /2]; + for (int i = 0; i < buffer.length/2; i++) { + short res = (short) ((buffer[i*2+1] & 0x00FF) | (((short) buffer[i*2]) << 8)); + data[i]=res; + } + return data; + } + + /** + * 把原始的声音数据转换成16位的数组数据。 + * @param buffer 原始的声音数据(8位) + * @return 返回16位的int格式数组 + */ + private float[] byteDataToFloatData(byte[] buffer){ + float[] data=new float[buffer.length /2]; + for (int i = 0; i < buffer.length/2; i++) { + int res = (buffer[i*2] & 0x000000FF) | (((int) buffer[i*2+1]) << 8); + data[i]=res/32768.0f; + } + return data; + } + private void setXieguRadioInterface() { + xieguRadio.setOnReceiveStreamData(new X6100Radio.OnReceiveStreamData() { + @Override + public void onReceiveAudio(byte[] data) { + if (onWaveDataReceived!=null){ + float[] waveFloat = byteDataToFloatData(data); + onWaveDataReceived.OnDataReceived(waveFloat.length,waveFloat); + } + } + + @Override + public void onReceiveIQ(byte[] data) { + + } + + @Override + public void onReceiveFFT(VITA vita) { + + } + + @Override + public void onReceiveMeter(X6100Meters meters) { + maxTXPower= meters.max_power; + mutableMaxTxPower.postValue(maxTXPower); + } + + @Override + public void onReceiveUnKnow(byte[] data) { + + } + }); + + + //当有命令返回值时的事件 + xieguRadio.setOnCommandListener(new X6100Radio.OnCommandListener() { + @Override + public void onResponse(X6100Radio.XieguResponse response) { + Log.d(TAG, String.format("onResponse(%s): %s" + ,response.xieguCommand.toString(),response.rawData )); + if (response.xieguCommand == X6100Radio.XieguCommand.STREAM){ + if (response.resultContent.toUpperCase().contains("PORT=")){//说明流端口打开了 + streamIsOn =true; + } + } + + if (response.resultCode!=0) {//只显示失败的命令 + ToastMessage.show(response.resultContent); + Log.e(TAG, "onResponse: "+response.resultContent); + } + + } + }); + + //当有状态信息接收到时 + xieguRadio.setOnStatusListener(new X6100Radio.OnStatusListener() { + @Override + public void onStatus(X6100Radio.XieguResponse response) { + //显示状态消息 + if (response.resultCode == 0){//说明是电台状态变化了 + String status[] = response.resultContent.split(" "); + for (int i = 0; i < status.length; i++) { + if (status[i].startsWith("active_freq")){//找出频率状态,设置频率 + String temp[]=status[i].split("="); + if (baseRig != null) { + baseRig.setFreq(Long.parseLong(temp[1].trim())); + } + } + } + } + Log.d(TAG, "onStatus: "+response.rawData ); + } + }); + + + + xieguRadio.setOnTcpConnectStatus(new X6100Radio.OnTcpConnectStatus() { + @SuppressLint("DefaultLocale") + @Override + public void onConnectSuccess(RadioTcpClient tcpClient) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.init_flex_operation) + ,xieguRadio.getModelName())); + new Thread(new Runnable() {//此处使用线程方式,是防止tcp对象阻塞 + @Override + public void run() { + while (!streamIsOn) {//等待电台打开流端口 + xieguRadio.commandOpenStream();//设置UDP端口 + try { + Thread.sleep(300); + } catch (InterruptedException e) { + Log.e(TAG, Objects.requireNonNull(e.getMessage())); + } + //todo 此处经常丢命令 + xieguRadio.commandGetAudioInfo();//读取6100播放的参数 + //xieguRadio.commandSubGetMeter();//查阅仪表索引编号 + xieguRadio.commandSubAllMeter();//订阅仪表流数据 + //xieguRadio.commandSetTxPower(1);//订阅仪表流数据 + } + } + }).start(); + } + + @Override + public void onConnectFail(RadioTcpClient tcpClient) { + ToastMessage.show(String.format(GeneralVariables.getStringFromResource + (R.string.xiegu_connect_failed),xieguRadio.getModelName())); + } + + @Override + public void onConnectionClosed(RadioTcpClient tcpClient) { + if (baseRig.getOnRigStateChanged()!=null) { + baseRig.getOnRigStateChanged().onDisconnected(); + } + } + }); + + } + + + @Override + public void sendData(byte[] data) { + xieguRadio.sendData(data); + } + + + @Override + public void setPttOn(boolean on) { + xieguRadio.isPttOn=on; + xieguRadio.commandPTTOnOff(on); + } + + @Override + public void setPttOn(byte[] command) { + } + + @Override + public void sendWaveData(float[] data) { + xieguRadio.sendWaveData(data); + } + + + public void setMaxTXPower(int power){ + maxTXPower=power; + mutableMaxTxPower.postValue(maxTXPower); + GeneralVariables.flexMaxRfPower=power; + xieguRadio.commandSetTxPower(power);//设置发射功率 + + } + + //传送a91数据包的方式 + @Override + public void sendFt8A91(byte[] a91,float baseFreq){ + Log.d(TAG,String.format("A91:%s", BaseRig.byteToStr(a91))); + //xieguRadio.commandSendA91(a91,GeneralVariables.volumePercent,baseFreq); + xieguRadio.commandSendA91(a91,0.95f,baseFreq); + } + + @Override + public void setRFVolume(int volume) { + xieguRadio.commandSetTxVol(volume); + } + + @Override + public void connect() { + super.connect(); + xieguRadio.openAudio(); + xieguRadio.connect(); + xieguRadio.openStreamPort(); + } + + @Override + public void disconnect() { + super.disconnect(); + xieguRadio.closeAudio(); + xieguRadio.closeStreamPort(); + xieguRadio.disConnect(); + } + + public OnWaveDataReceived getOnWaveDataReceived() { + return onWaveDataReceived; + } + + public void setOnWaveDataReceived(OnWaveDataReceived onWaveDataReceived) { + this.onWaveDataReceived = onWaveDataReceived; + } + + public BaseRig getBaseRig() { + return baseRig; + } + + public void setBaseRig(BaseRig baseRig) { + this.baseRig = baseRig; + } + + @Override + public boolean isConnected() { + return xieguRadio.isConnect(); + } + + public X6100Radio getXieguRadio() { + return xieguRadio; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java index 3dc2173..be926f0 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java @@ -51,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, 14); + instance = new DatabaseOpr(context, databaseName, null, 15); } return instance; } @@ -364,6 +364,7 @@ public class DatabaseOpr extends SQLiteOpenHelper { } private void createSWLTables(SQLiteDatabase sqLiteDatabase) { + //Log.e(TAG,"upgrade database."); if (!checkTableExists(sqLiteDatabase, "SWLMessages")) { sqLiteDatabase.execSQL("CREATE TABLE SWLMessages (\n" + "\tID INTEGER PRIMARY KEY AUTOINCREMENT,\n" + @@ -384,6 +385,7 @@ public class DatabaseOpr extends SQLiteOpenHelper { "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" + @@ -400,7 +402,11 @@ public class DatabaseOpr extends SQLiteOpenHelper { "\tfreq TEXT,\n" + "\tstation_callsign TEXT,\n" + "\tmy_gridsquare TEXT,\n" + + "\toperator TEXT,\n" + "\tcomment TEXT)"); + }else { + alterTable(sqLiteDatabase, "SWLQSOTable", "operator" + , "operator TEXT"); } } @@ -1201,8 +1207,8 @@ public class DatabaseOpr extends SQLiteOpenHelper { }); //添加记录 querySQL = "INSERT INTO SWLQSOTable([call], gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + - "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare,comment)\n" + - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare,operator,comment)\n" + + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; databaseOpr.db.execSQL(querySQL, new String[]{qslRecord.getToCallsign() , qslRecord.getToMaidenGrid() @@ -1218,6 +1224,7 @@ public class DatabaseOpr extends SQLiteOpenHelper { , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) , qslRecord.getMyCallsign() , qslRecord.getMyMaidenGrid() + , GeneralVariables.myCallsign//我的呼号,不是双方的呼号 , qslRecord.getComment()}); @@ -1794,6 +1801,12 @@ public class DatabaseOpr extends SQLiteOpenHelper { Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); + if (callsign.contains("/")) { + String shortCallsign = GeneralVariables.getShortCallsign(callsign); + Ft8Message.hashList.addHash(FT8Package.getHash22(shortCallsign), shortCallsign); + Ft8Message.hashList.addHash(FT8Package.getHash12(shortCallsign), shortCallsign); + Ft8Message.hashList.addHash(FT8Package.getHash10(shortCallsign), shortCallsign); + } } } if (name.equalsIgnoreCase("toModifier")) { @@ -1899,6 +1912,16 @@ public class DatabaseOpr extends SQLiteOpenHelper { if (name.equalsIgnoreCase("deepMode")) {//是不是深度解码模式 GeneralVariables.deepDecodeMode =result.equals("1"); } + if (name.equalsIgnoreCase("dataBits")) {//串口数据位 + GeneralVariables.serialDataBits =Integer.parseInt(result); + } + if (name.equalsIgnoreCase("stopBits")) {//串口停止位 + GeneralVariables.serialStopBits =Integer.parseInt(result); + } + if (name.equalsIgnoreCase("parityBits")) {//串口校验位 + GeneralVariables.serialParity =Integer.parseInt(result); + } + } cursor.close(); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java index 8e7cf3c..ef465ba 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterList.java @@ -5,6 +5,8 @@ package com.bg7yoz.ft8cn.flex; * @date 2023-03-20 */ +import static com.bg7yoz.ft8cn.flex.VITA.readShortData; + import android.annotation.SuppressLint; import java.util.HashMap; @@ -73,25 +75,8 @@ public class FlexMeterList extends HashMap { - /** - * 把字节转换成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 { diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java index a76efab..a68cfc2 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import com.bg7yoz.ft8cn.GeneralVariables; import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.ui.ToastMessage; import com.bg7yoz.ft8cn.wave.FT8Resample; import java.io.ByteArrayInputStream; @@ -95,7 +96,7 @@ public class FlexRadio { private boolean allFlexRadioStatusEvent = false; private String clientID = ""; private long daxAudioStreamId = 0; - private long daxTxAudioStreamId = 0; + private int daxTxAudioStreamId = 0; private long panadapterStreamId = 0; private final HashSet streamIdSet = new HashSet<>(); @@ -251,6 +252,12 @@ public class FlexRadio { } onReceiveData(buffer); } + + @Override + public void onConnectionClosed() { + tcpClient.disconnect(); + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tcp_connect_closed)); + } }); clearBufferData();//清除一下缓存的指令数据 tcpClient.connect(ip, port);//连接TCP @@ -483,9 +490,13 @@ public class FlexRadio { } //byte[] send = vita.audioDataToVita(packetCount, streamTxId,0x534c2d, voice); - //daxTxAudioStreamId=0x0084000001&0x00000000ffffffff; - //Log.e(TAG, String.format("run: daxTxAudioStreamId:0x%X",daxTxAudioStreamId) ); - byte[] send = vita.audioDataToVita(packetCount, daxTxAudioStreamId,0x534c0123, voice); + //daxTxAudioStreamId=0x0084000001&0x00000000ffffffff; + //Log.e(TAG, String.format("run: daxTxAudioStreamId:0x%X",daxTxAudioStreamId) ); + //daxTxAudioStreamId,0x534c0123, + vita.streamId=daxTxAudioStreamId; + vita.classId = 0x534c0123; + vita.classId64 = 0x00001c2d534c0123L; + byte[] send = vita.audioFloatDataToVita(packetCount, voice); packetCount++; try { //Log.e(TAG, String.format("run: send ip:%s, port:%d",ip,4993) ); @@ -581,7 +592,7 @@ public class FlexRadio { commandStr = String.format("C%d%03d|%s\n", commandSeq, command.ordinal() , cmdContent); tcpClient.sendByte(commandStr.getBytes()); - Log.e(TAG, "sendCommand: " + commandStr); + Log.d(TAG, "sendCommand: " + commandStr); } } @@ -652,7 +663,7 @@ public class FlexRadio { } if (response.daxTxStreamId != 0) { this.daxTxAudioStreamId = response.daxTxStreamId; - Log.e(TAG, String.format("doReceiveLineEvent: txStreamID:0x%x", daxTxAudioStreamId)); + Log.d(TAG, String.format("doReceiveLineEvent: txStreamID:0x%x", daxTxAudioStreamId)); } break; @@ -1033,7 +1044,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 int daxTxStreamId = 0; public long panadapterStreamId = 0; public FlexCommand flexCommand = FlexCommand.UNKNOW; public long resultValue = 0; @@ -1135,12 +1146,12 @@ public class FlexRadio { } } - private long getStreamId(String line) { + private int getStreamId(String line) { String[] lines = line.split("\\|"); if (lines.length > 2) { if (lines[1].equals("0")) { try { - return Long.parseLong(lines[2], 16);//stream id,16进制 + return Integer.parseInt(lines[2], 16);//stream id,16进制 } catch (NumberFormatException e) { e.printStackTrace(); Log.e(TAG, "getDaxStreamId exception: " + e.getMessage()); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java index c89d532..833e2ec 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadioFactory.java @@ -10,18 +10,18 @@ import java.util.Timer; import java.util.TimerTask; // VITA 形成的发现消息解析器的枚举定义 -enum VitaTokens { - nullToken , - ipToken, - portToken, - modelToken, - serialToken, - callsignToken, - nameToken, - dpVersionToken, - versionToken, - statusToken, -}; +//enum VitaTokens { +// nullToken , +// ipToken, +// portToken, +// modelToken, +// serialToken, +// callsignToken, +// nameToken, +// dpVersionToken, +// versionToken, +// statusToken, +//}; /** * RadioFactory 当前发现的所有收音机。 * RadioFactory: 实例化这个类来创建一个 Radio Factory,它将为网络上发现的无线电维护FlexRadio列表flexRadios。 diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java index 83e88a3..af22488 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioTcpClient.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; +import java.net.SocketException; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -21,7 +22,7 @@ public class RadioTcpClient { private static RadioTcpClient radioTcpClient = null; private String ip; private int port; - public static final int MAX_BUFFER_SIZE=1024 * 64; + public static final int MAX_BUFFER_SIZE=1024 * 32; private final ExecutorService sendByteThreadPool = Executors.newCachedThreadPool(); private final SendByteRunnable sendByteRunnable=new SendByteRunnable(this); @@ -115,27 +116,40 @@ public class RadioTcpClient { return; } - } catch (IOException e) { - + }catch (SocketException e){ + Log.e(TAG,"TCP Connection exception:"+e.getMessage()); + } + catch (IOException e) { connectFail(); Log.e(TAG, "SocketThread connect io exception = " + e.getMessage()); e.printStackTrace(); return; } - + int errorCount=0; //read ... while (isConnect() && !isStop && !isInterrupted()) { int size; try { byte[] buffer = new byte[MAX_BUFFER_SIZE]; if (mInputStream == null) return; - size = mInputStream.read(buffer);//null data -1 , zrd serial rule size default 10 + size = mInputStream.read(buffer);//null data -1 , if (size > 0) { if (onDataReceiveListener != null) { byte[] temp = Arrays.copyOf(buffer, size); onDataReceiveListener.onDataReceive(temp); } + errorCount =0; + }else { + errorCount ++; + if (errorCount > 10){ + if (onDataReceiveListener!=null){ + onDataReceiveListener.onConnectionClosed(); + } + } } + + } catch (SocketException e){ + Log.e(TAG,"Tcp Connection exception:"+e.getMessage()); } catch (IOException e) { //uiHandler.sendEmptyMessage(-1); Log.e(TAG, "SocketThread read io exception = " + e.getMessage()); @@ -207,6 +221,7 @@ public class RadioTcpClient { void onConnectFail(); void onDataReceive(byte[] buffer); + void onConnectionClosed(); } public void setOnDataReceiveListener( diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java index 0b4c833..b90c5cd 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/RadioUdpClient.java @@ -20,7 +20,7 @@ import java.util.concurrent.Executors; public class RadioUdpClient { private static final String TAG = "RadioUdpSocket"; - private final int MAX_BUFFER_SIZE = 1024*2; + private final int MAX_BUFFER_SIZE = 1024*4; private DatagramSocket sendSocket; private int port; private boolean activated = false; @@ -76,7 +76,6 @@ public class RadioUdpClient { if (activated) {//通过activated判断是否结束接收线程,并清空sendSocket指针 sendSocket = new DatagramSocket(null);//绑定的端口号随机 sendSocket.bind(new InetSocketAddress(port)); - // Log.e(TAG, "openUdpPort: "+sendSocket.getLocalPort()); receiveData(); }else { if (sendSocket!=null){ @@ -92,31 +91,6 @@ 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; @@ -128,15 +102,18 @@ public class RadioUdpClient { @Override public void run() { while (client.activated) { + byte[] data = new byte[client.MAX_BUFFER_SIZE]; DatagramPacket packet = new DatagramPacket(data, data.length); try { client.sendSocket.receive(packet); if (client.onUdpEvents != null) { byte[] temp = Arrays.copyOf(packet.getData(), packet.getLength()); + client.onUdpEvents.OnReceiveData(client.sendSocket, packet, temp); + + } - //Log.d(TAG, "receiveData:host port: " + packet.getPort()); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "receiveData: error:" + e.getMessage()); @@ -152,7 +129,7 @@ public class RadioUdpClient { this.onUdpEvents = onUdpEvents; } - interface OnUdpEvents { + public interface OnUdpEvents { void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java index c9faa77..397dc7c 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VITA.java @@ -1,6 +1,6 @@ package com.bg7yoz.ft8cn.flex; /** - * VITA49协议的简单解包和封包操作。 + * VITA49协议的简单解包和封包操作。此库停止维护,将使用VITA49.java替换此库。 * * @author BGY70Z * @date 2023-03-20 @@ -18,48 +18,10 @@ public static intVH_PKT_SIZE(x) (x & 0x0000ffff) // Enumerates for field values import android.annotation.SuppressLint; -import android.util.Log; import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; -enum VitaPacketType { - IF_DATA,//IF Data packet without Stream Identifier - IF_DATA_WITH_STREAM,//IF Data packet with Stream Identifier - EXT_DATA,//Extension Data packet without Stream Identifier - EXT_DATA_WITH_STREAM,//Extension Data packet with Stream Identifier - IF_CONTEXT,//IF Context packet(see Section 7) - EXT_CONTEXT//Extension Context packet(see Section 7); -}; -//时间戳的类型 -//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, -//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 -//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, -//所有的时间带来都是在以一个采样数据为该reference-point 时间 -enum VitaTSI { - TSI_NONE,//No Integer-seconds Timestamp field included - TSI_UTC,//Coordinated Universal Time(UTC) - TSI_GPS,//GPS time - TSI_OTHER//Other -}; - -//时间戳小数部分类型 -//小数部分主要有三种: -// 一种是sample-count ,以采样周期为最小分辨率, -// 一种是real-time以ps为最小单位, -// 第三种是以任意选择的时间进行累加得出的, -// 前面两种时间戳可以直接与整数部分叠加, -// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 -// 小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, -// 所有的时间带来都是在以一个采样数据为该参考点(reference-point)的时间。 -enum VitaTSF { - TSF_NONE,//No Fractional-seconds Timestamp field included. 不包括分数秒时间戳字段 - TSF_SAMPLE_COUNT,//Sample Count Timestamp. 样本计数时间戳 - TSF_REALTIME,//Real Time(Picoseconds) Timestamp. 实时(皮秒)时间戳 - TSF_FREERUN,//Free Running Count Timestamp. 自由运行计数时间戳 -}; public class VITA { private static final String TAG = "VITA"; @@ -89,8 +51,19 @@ public class VITA { public static final int VS_DAX_Audio = 0x03e3; public static final int VS_Discovery = 0xffff; + //与x6100有关的id信息 + public static final long XIEGU_Discovery_Class_Id = 0x005849454755FFFFL; + public static final int XIEGU_Discovery_Stream_Id = 0x00000800; + public static final long XIEGU_AUDIO_CLASS_ID = 0x00584945475500A1L; + public static final int XIEGU_PING_Stream_Id= 0x00000600; + public static final long XIEGU_PING_CLASS_ID = 0x58494547550006L; + public static final long XIEGU_METER_CLASS_ID = 0x58494547550007L; + public static final int XIEGU_METER_Stream_Id= 0x00000700; + + private byte[] buffer; + public boolean streamIdPresent;//是否有流字符 public VitaPacketType packetType; public boolean classIdPresent;//指示数据包中是否包含类标识符(类ID)字段 public boolean trailerPresent;//指示数据包是否包含尾部。 @@ -98,145 +71,226 @@ public class VITA { public VitaTSF tsf;//时间戳小数部分类型 public int packetCount;//包计数器,可以对连续的IF data packet进行计数,这些packet具有相同的Stream Identifier 和packet type。 public int packetSize;//表示有多少32bit数在IF Data packet 里面 - + public int streamId;//流ID,32位 //时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位,小数部分64位。 - public long integerTimestamp;//u_int32,long是64位的 + public int integerTimestamp;//u_int32,long是64位的 public long fracTimeStamp; public long oui; public int informationClassCode;//无用了,用classId代替 public int packetClassCode;//无用了,用classId代替 public int classId;//FLEX应该是0x534CFFF,是informationClassCode与packetClassCode合并的 + public long classId64; + public byte[] payload = null; public long trailer; public boolean isAvailable = false;//电台对象是否有效。 - public boolean streamIdPresent;//是否有流字符 - - //用来区分不同的 packet stream 。 - //stream ID 不是必须的,如果仅有一个数据包在单一数据链路传递的话就可以不用要, - //如果 packet stream想用同一 stream ID 的话那每一个packet都得有, - //在系统内部,不同的packet stream 之间的 Stream ID是不同的。 - //如果要用到 data-context 配对,那么IF data packet需要 Stream ID - public long streamId;//流ID,32位,FLEX应当是0x0800 - - /* - VITA前28个字节是VITA头 + /** + * 设置vita数据包的头部 + * @param packet 数据包 + * @return 数据包 */ + public byte[] setVitaPacketHeader(byte[] packet){ + if (packet.length < 28) return packet; + packet[0] = (byte) (packetType.ordinal() << 4);//packetType + if (classIdPresent) packet[0] |=0x08; + if (trailerPresent) packet[0] |= 0x04; + + + packet[1] = (byte) (packetCount & 0xf);//packet count + packet[1] |= (byte) (tsi.ordinal() << 6);//TSI + packet[1] |= (byte) (tsf.ordinal() << 4);//TSF + + packet[2] = (byte) ((packetSize & 0xff00 ) >> 8 & 0xff);//packetSize 1(高8位) + packet[3] = (byte) (packetSize & 0xff);//packetSize 2(低8位) + + //-----Stream Identifier--No.2 word---- + packet[4] = (byte) (((streamId & 0x00ff000000) >> 24) & 0xff); + packet[5] = (byte) (((streamId & 0x0000ff0000) >> 16) & 0xff); + packet[6] = (byte) (((streamId & 0x000000ff00) >> 8) & 0xff); + packet[7] = (byte) (streamId & 0x00000000ff); + //----Class ID--No.3 words---- + + packet[8] = 0x00; + packet[9] = (byte)(((classId64 & 0x00ff000000000000L) >> 48)& 0xff); + packet[10] =(byte)(((classId64 & 0x0000ff0000000000L) >> 40)& 0xff); + packet[11] =(byte)(((classId64 & 0x000000ff00000000L) >> 32)& 0xff); + + //---Class ID--No.4 word---- + packet[12] =(byte)(((classId64 & 0x00000000ff000000L) >> 24)& 0xff); + packet[13] =(byte)(((classId64 & 0x0000000000ff0000L) >> 16)& 0xff); + packet[14] =(byte)(((classId64 & 0x000000000000ff00L) >> 8)& 0xff); + packet[15] =(byte)((classId64 & 0x00000000000000ffL )); + + //---Timestamp Int--No.5 word---- + packet[16] =(byte)(((integerTimestamp & 0xff000000) >> 24)& 0xff); + packet[17] =(byte)(((integerTimestamp & 0x00ff0000) >> 16)& 0xff); + packet[18] =(byte)(((integerTimestamp & 0x0000ff00) >> 8)& 0xff); + packet[19] =(byte)(integerTimestamp & 0x000000ff); + + //---Timestamp franc Int--No.6 No.7 word---- + packet[20] = (byte)(((fracTimeStamp & 0xff00000000000000L) >> 56)& 0xff); + packet[21] = (byte)(((fracTimeStamp & 0x00ff000000000000L) >> 48)& 0xff); + packet[22] = (byte)(((fracTimeStamp & 0x0000ff0000000000L) >> 40)& 0xff); + packet[23] = (byte)(((fracTimeStamp & 0x000000ff00000000L) >> 32)& 0xff); + //---Class ID--No.4 word---- + + packet[24] =(byte)(((fracTimeStamp & 0x00000000ff000000L) >> 24)& 0xff); + packet[25] =(byte)(((fracTimeStamp & 0x0000000000ff0000L) >> 16)& 0xff); + packet[26] =(byte)(((fracTimeStamp & 0x000000000000ff00L) >> 8)& 0xff); + packet[27] =(byte)(fracTimeStamp & 0x00000000000000ffL ) ; + return packet; + } + + public byte[] pingDataToVita(){ + byte[] result=new byte[28]; + return setVitaPacketHeader(result); + } + + /** + * 生成音频流的VITA数据包 + * + * @param count 包计数器 + * @param payload 音频流数据 + * @return vita数据包 + */ + public byte[] audioShortDataToVita(int count, short[] payload){ + packetType = VitaPacketType.EXT_DATA_WITH_STREAM; + classIdPresent = true; + trailerPresent = false;//没有尾巴 + tsi = VitaTSI.TSI_OTHER;// + tsf = VitaTSF.TSF_SAMPLE_COUNT; + this.packetCount = count; + this.packetSize = (payload.length / 2) + 7; + + byte[] result=new byte[payload.length*2 + 28]; + setVitaPacketHeader(result); + + //----HEADER--No.1 word------ + +// result[0] = (byte) (packetType.ordinal() << 4);//packetType +// if (classIdPresent) result[0] |=0x08; +// if (trailerPresent) result[0] |= 0x04; +// +// +// result[1] = (byte) (packetCount & 0xf);//packet count +// result[1] |= (byte) (tsi.ordinal() << 6);//TSI +// result[1] |= (byte) (tsf.ordinal() << 4);//TSF +// +// result[2] = (byte) ((packetSize & 0xff00 ) >> 8 & 0xff);//packetSize 1(高8位) +// result[3] = (byte) (packetSize & 0xff);//packetSize 2(低8位) +// +// //-----Stream Identifier--No.2 word---- +// result[4] = (byte) (((streamId & 0x00ff000000) >> 24) & 0xff); +// result[5] = (byte) (((streamId & 0x0000ff0000) >> 16) & 0xff); +// result[6] = (byte) (((streamId & 0x000000ff00) >> 8) & 0xff); +// result[7] = (byte) (streamId & 0x00000000ff); +// //----Class ID--No.3 words---- +// +// result[8] = 0x00; +// result[9] = (byte)(((classId64 & 0x00ff000000000000L) >> 48)& 0xff); +// result[10] =(byte)(((classId64 & 0x0000ff0000000000L) >> 40)& 0xff); +// result[11] =(byte)(((classId64 & 0x000000ff00000000L) >> 32)& 0xff); +// +// //---Class ID--No.4 word---- +// result[12] =(byte)(((classId64 & 0x00000000ff000000L) >> 24)& 0xff); +// result[13] =(byte)(((classId64 & 0x0000000000ff0000L) >> 16)& 0xff); +// result[14] =(byte)(((classId64 & 0x000000000000ff00L) >> 8)& 0xff); +// result[15] =(byte)((classId64 & 0x00000000000000ffL )); +// +// //---Timestamp Int--No.5 word---- +// result[16] =(byte)(((integerTimestamp & 0xff000000) >> 24)& 0xff); +// result[17] =(byte)(((integerTimestamp & 0x00ff0000) >> 16)& 0xff); +// result[18] =(byte)(((integerTimestamp & 0x0000ff00) >> 8)& 0xff); +// result[19] =(byte)(integerTimestamp & 0x000000ff); +// +// //---Timestamp franc Int--No.6 No.7 word---- +// result[20] = (byte)(((fracTimeStamp & 0xff00000000000000L) >> 56)& 0xff); +// result[21] = (byte)(((fracTimeStamp & 0x00ff000000000000L) >> 48)& 0xff); +// result[22] = (byte)(((fracTimeStamp & 0x0000ff0000000000L) >> 40)& 0xff); +// result[23] = (byte)(((fracTimeStamp & 0x000000ff00000000L) >> 32)& 0xff); +// //---Class ID--No.4 word---- +// +// result[24] =(byte)(((fracTimeStamp & 0x00000000ff000000L) >> 24)& 0xff); +// result[25] =(byte)(((fracTimeStamp & 0x0000000000ff0000L) >> 16)& 0xff); +// result[26] =(byte)(((fracTimeStamp & 0x000000000000ff00L) >> 8)& 0xff); +// result[27] =(byte)(fracTimeStamp & 0x00000000000000ffL ) ; + + for (int i = 0; i < payload.length ; i++) { + result[28+i*2]=(byte)(payload[i]&0xff) ; + result[28+i*2+1]= (byte)((payload[i]>>8)&0xff) ; + } + return result; + } /** * 生成音频流的VITA数据包,id应当是电台create stream是赋给的 - * - * @param stream_id streamId * @param data 音频流数据 * @return vita数据包 */ - public byte[] audioDataToVita(int count, long stream_id, int class_id, float[] data) { + public byte[] audioFloatDataToVita(int count, 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=?应该是这个全部音频流的总包数 - packetCount=count % 16;//模16 + tsf = VitaTSF.TSF_SAMPLE_COUNT; + this.packetCount = count; + this.packetSize = (data.length / 2) + 7; - //packetSize是以word(32位,4字节)为单位, - //packetSize值为263居多估计以音频,还有其它的长度,263是包含7个word(28字节)的头长度。 - packetSize = (data.length) + 7;//7个word是VITA的包头 - //----以上是Header,32位,第一个word------- - streamId = stream_id;//第二个word,此id是电台赋给的。经常是0x40000xx。 - - oui = 0x00001c2d;//第三个word,FlexRadio Systems OUI - //classId = 0x534c0123;//第四个word,64位 - classId = class_id; - //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 + if (classIdPresent) result[0] |=0x08; + if (trailerPresent) result[0] |= 0x04; - result[1] = (byte) 0x0000; - result[1] |= (byte) (packetCount & 0xf);//packet count - result[1] = (byte) (tsi.ordinal() << 6);//TSI + + result[1] = (byte) (packetCount & 0xf);//packet count + result[1] |= (byte) (tsi.ordinal() << 6);//TSI result[1] |= (byte) (tsf.ordinal() << 4);//TSF - //result[1] |= (byte) (packetCount & 0xff);//packetCount - //packetSize默认263(words) - result[2] = (byte) ((packetSize >> 8) & 0xff);//packetSize 1(高8位) + result[2] = (byte) ((packetSize & 0xff00 ) >> 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); + result[4] = (byte) (((streamId & 0x00ff000000) >> 24) & 0xff); + result[5] = (byte) (((streamId & 0x0000ff0000) >> 16) & 0xff); + result[6] = (byte) (((streamId & 0x000000ff00) >> 8) & 0xff); + result[7] = (byte) (streamId & 0x00000000ff); + //----Class ID--No.3 words---- - //----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] = (byte) ((classId & 0x00ff000000 >> 24) & 0xff); - result[13] = (byte) (((classId & 0x00ff0000) >> 16) & 0xff); - result[14] = (byte) (((classId & 0x0000ff00) >> 8) & 0xff); - result[15] = (byte) (classId & 0x000000ff); + result[9] = (byte)(((classId64 & 0x00ff000000000000L) >> 48)& 0xff); + result[10] =(byte)(((classId64 & 0x0000ff0000000000L) >> 40)& 0xff); + result[11] =(byte)(((classId64 & 0x000000ff00000000L) >> 32)& 0xff); -// result[12] = 0x53; -// result[13] = 0x4c; -// result[14] = (byte) 0x01; -// result[15] = (byte) 0x23; + //---Class ID--No.4 word---- + result[12] =(byte)(((classId64 & 0x00000000ff000000L) >> 24)& 0xff); + result[13] =(byte)(((classId64 & 0x0000000000ff0000L) >> 16)& 0xff); + result[14] =(byte)(((classId64 & 0x000000000000ff00L) >> 8)& 0xff); + result[15] =(byte)((classId64 & 0x00000000000000ffL )); + + //---Timestamp Int--No.5 word---- + result[16] =(byte)(((integerTimestamp & 0xff000000) >> 24)& 0xff); + result[17] =(byte)(((integerTimestamp & 0x00ff0000) >> 16)& 0xff); + result[18] =(byte)(((integerTimestamp & 0x0000ff00) >> 8)& 0xff); + result[19] =(byte)(integerTimestamp & 0x000000ff); + + //---Timestamp franc Int--No.6 No.7 word---- + result[20] = (byte)(((fracTimeStamp & 0xff00000000000000L) >> 56)& 0xff); + result[21] = (byte)(((fracTimeStamp & 0x00ff000000000000L) >> 48)& 0xff); + result[22] = (byte)(((fracTimeStamp & 0x0000ff0000000000L) >> 40)& 0xff); + result[23] = (byte)(((fracTimeStamp & 0x000000ff00000000L) >> 32)& 0xff); + //---Class ID--No.4 word---- + + result[24] =(byte)(((fracTimeStamp & 0x00000000ff000000L) >> 24)& 0xff); + result[25] =(byte)(((fracTimeStamp & 0x0000000000ff0000L) >> 16)& 0xff); + result[26] =(byte)(((fracTimeStamp & 0x000000000000ff00L) >> 8)& 0xff); + result[27] =(byte)(fracTimeStamp & 0x00000000000000ffL ) ; - //---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]; @@ -245,24 +299,32 @@ public class VITA { 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( VitaPacketType packetType + , VitaTSI tsi, VitaTSF tsf + , int packetCount + , int streamId, long classId64) { + this.streamIdPresent = true; + this.packetType = packetType; + this.classIdPresent = true; + this.trailerPresent = false; + this.tsi = tsi; + this.tsf = tsf; + this.packetCount = packetCount; + this.streamId = streamId; + this.classId64 = classId64; + this.isAvailable = true; + } + public VITA(byte[] data) { this.buffer = data; //如果包的长度太小,或包为空,就退出计算 @@ -284,7 +346,7 @@ public class VITA { || packetType == VitaPacketType.EXT_DATA_WITH_STREAM; if (streamIdPresent) {//是否有流ID,获取流ID,32位 - streamId = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) + streamId = ((((int) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) | ((((int) data[offset + 2]) & 0x00ff) << 8) | ((int) data[offset + 3]) & 0x00ff; offset += 4; } @@ -299,6 +361,15 @@ public class VITA { classId = ((((int) data[offset + 4]) & 0x00ff) << 24) | ((((int) data[offset + 5]) & 0x00ff) << 16) | ((((int) data[offset + 6]) & 0x00ff) << 8) | ((int) data[offset + 7]) & 0x00ff; + + classId64 = ((((long) data[offset + 1]) & 0x00ff) << 48) + | ((((long) data[offset + 2]) & 0x00ff) << 40) + | ((((long) data[offset + 3]) & 0x00ff)<<32) + | ((((long) data[offset + 4]) & 0x00ff) << 24) + | ((((long) data[offset + 5]) & 0x00ff) << 16) + | ((((long) data[offset + 6]) & 0x00ff) << 8) + | ((long) data[offset + 7]) & 0x00ff; + //Log.d(TAG,String.format("classId64:%X",classId64)); offset += 8; } //Log.e(TAG, "VITA: "+String.format("id: 0x%x, classIdPresent:0x%x,packetSize:%d",streamId,classId,packetSize) ); @@ -309,14 +380,14 @@ public class VITA { //小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, //所有的时间带来都是在以一个采样数据为该reference-point 时间 if (tsi != VitaTSI.TSI_NONE) {//32位, - integerTimestamp = ((((long) data[offset]) & 0x00ff) << 24) | ((((int) data[offset + 1]) & 0x00ff) << 16) + integerTimestamp = ((((int) 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 + 2]) & 0x00ff) << 40) | ((((long) data[offset + 3]) & 0x00ff) << 32) | ((((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; @@ -353,7 +424,7 @@ public class VITA { */ public String showPayload() { if (payload != null) { - return new String(payload).replace(" ", "\n"); + return new String(payload);//.replace(" ", "\n"); } else { return ""; } @@ -385,9 +456,9 @@ public class VITA { "类低位(packetClassCode): 0x%X\n" + "公司标识码(oui): 0x%X\n" + "时间戳类型(tsi): %s\n" + - "时间戳整数部分(integerTimestamp):%s\n" + + "时间戳整数部分(integerTimestamp):0x%X\n" + "时间戳小数部分类型(tsf): %s\n" + - "时间戳小数部分值(fracTimeStamp): %d\n" + + "时间戳小数部分值(fracTimeStamp): 0x%X\n" + "负载长度(payloadLength): %d\n" + "是否有尾部(trailerPresent): %s\n" @@ -402,7 +473,7 @@ public class VITA { , packetClassCode , oui , tsi.toString() - , timestampToDateStr(integerTimestamp * 1000) + , integerTimestamp , tsf.toString() , fracTimeStamp , (payload == null ? 0 : payload.length) @@ -426,15 +497,7 @@ public class VITA { } - public static String timestampToDateStr(Long timestamp) { - //final String DATETIME_CONVENTIONAL_CN = "yyyy-MM-dd HH:mm:ss"; - //SimpleDateFormat sdf = new SimpleDateFormat(DATETIME_CONVENTIONAL_CN); - @SuppressLint("SimpleDateFormat") - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String sd = sdf.format(new Date(timestamp)); // 时间戳转换日期 - //System.out.println(sd); - return sd; - } + public static String byteToStr(byte[] data) { StringBuilder s = new StringBuilder(); @@ -443,4 +506,40 @@ public class VITA { } return s.toString(); } + + + /** + * 把字节转换成short,做小端模式转换 + * + * @param data 字节数据 + * @param start 起始位置 + * @return short + */ + public static short readShortDataBigEnd(byte[] data, int start) { + if (data.length - start < 2) return 0; + return (short) ((short) data[start] & 0xff + | ((short) data[start +1] & 0xff) << 8); + } + + /** + * 把字节转换成short,不做小端转换!! + * + * @param data 字节数据 + * @param start 起始位置 + * @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); + } + } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaPacketType.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaPacketType.java new file mode 100644 index 0000000..10b3d0c --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaPacketType.java @@ -0,0 +1,10 @@ +package com.bg7yoz.ft8cn.flex; + +public enum VitaPacketType { + IF_DATA,//IF Data packet without Stream Identifier + IF_DATA_WITH_STREAM,//IF Data packet with Stream Identifier + EXT_DATA,//Extension Data packet without Stream Identifier + EXT_DATA_WITH_STREAM,//Extension Data packet with Stream Identifier + IF_CONTEXT,//IF Context packet(see Section 7) + EXT_CONTEXT//Extension Context packet(see Section 7); +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSF.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSF.java new file mode 100644 index 0000000..117b0e1 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSF.java @@ -0,0 +1,17 @@ +package com.bg7yoz.ft8cn.flex; + +//时间戳小数部分类型 +//小数部分主要有三种: +// 一种是sample-count ,以采样周期为最小分辨率, +// 一种是real-time以ps为最小单位, +// 第三种是以任意选择的时间进行累加得出的, +// 前面两种时间戳可以直接与整数部分叠加, +// 第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 +// 小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, +// 所有的时间带来都是在以一个采样数据为该参考点(reference-point)的时间。 +public enum VitaTSF { + TSF_NONE,//No Fractional-seconds Timestamp field included. 不包括分数秒时间戳字段 + TSF_SAMPLE_COUNT,//Sample Count Timestamp. 样本计数时间戳 + TSF_REALTIME,//Real Time(Picoseconds) Timestamp. 实时(皮秒)时间戳 + TSF_FREERUN,//Free Running Count Timestamp. 自由运行计数时间戳 +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSI.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSI.java new file mode 100644 index 0000000..35c3e64 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/flex/VitaTSI.java @@ -0,0 +1,13 @@ +package com.bg7yoz.ft8cn.flex; + +//时间戳的类型 +//时间戳共有两部分,小数部分和整数部分,整数部分以秒为分辨率,32位, 主要传递UTC时间或者 GPS 时间, +//小数部分主要有三种,一种是sample-count ,以采样周期为最小分辨率,一种是real-time以ps为最小单位,第三种是以任意选择的时间进行累加得出的,前面两种时间戳可以直接与整数部分叠加,第三种则不能保证与整数部分保持恒定关系,前两种与整数部分叠加来操作的可以在覆盖的时间范围为年 +//小数部分的时间戳共有64位,小数部分可以在没有整数部分的情况下使用, +//所有的时间带来都是在以一个采样数据为该reference-point 时间 +public enum VitaTSI { + TSI_NONE,//No Integer-seconds Timestamp field included + TSI_UTC,//Coordinated Universal Time(UTC) + TSI_GPS,//GPS time + TSI_OTHER//Other +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java index b311851..034a169 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java @@ -141,7 +141,8 @@ public class FT8Package { } if (fromCall.endsWith("/P") || fromCall.endsWith("/R")) { - fromCall = message.callsignFrom.substring(0, message.callsignFrom.length() - 2); + fromCall = fromCall.substring(0, fromCall.length() - 2); +// fromCall = message.callsignFrom.substring(0, message.callsignFrom.length() - 2); } //当双方都是复合呼号或非标准呼号时(带/的呼号),我的呼号变成标准呼号 @@ -149,6 +150,16 @@ public class FT8Package { fromCall = getStdCall(fromCall);//从复合呼号中提取标准呼号 // fromCall = fromCall.substring(0, fromCall.indexOf("/")); } + byte r1_p1=pack_r1_p1(message.callsignTo); + + byte r2_p2; + //如果双方都有后缀,但不是相同的类型后缀,则取消r1或p1标志,以发送方后缀为准 + if ((message.callsignFrom.endsWith("/R")&&message.callsignTo.endsWith("/P")) + ||(message.callsignFrom.endsWith("/P")&&message.callsignTo.endsWith("/R"))){ + r2_p2=0; + }else { + r2_p2 = pack_r1_p1(message.getCallsignFrom()); + } byte[] data = new byte[12]; @@ -156,7 +167,8 @@ public class FT8Package { data[1] = (byte) ((pack_c28(toCall) & 0x00ffffff) >> 12); data[2] = (byte) ((pack_c28(toCall) & 0x0000ffff) >> 4); data[3] = (byte) ((pack_c28(toCall) & 0x0000000f) << 4); - data[3] = (byte) (data[3] | (pack_r1_p1(message.callsignTo) << 3)); + //data[3] = (byte) (data[3] | (pack_r1_p1(message.callsignTo) << 3)); + data[3] = (byte) (data[3] | (r1_p1 << 3)); data[3] = (byte) (data[3] | (pack_c28(fromCall) & 0x00fffffff) >> 25); @@ -166,7 +178,8 @@ public class FT8Package { data[7] = (byte) ((pack_c28(fromCall) & 0x0000000ff) << 7); - data[7] = (byte) (data[7] | (pack_r1_p1(message.getCallsignFrom())) << 6); + data[7] = (byte) (data[7] | (r2_p2) << 6); + //data[7] = (byte) (data[7] | (pack_r1_p1(message.getCallsignFrom())) << 6); data[7] = (byte) (data[7] | (pack_R1_g15(message.extraInfo) & 0x0ffff) >> 10); data[8] = (byte) ((pack_R1_g15(message.extraInfo) & 0x0003fff) >> 2); data[9] = (byte) ((pack_R1_g15(message.extraInfo) & 0x00000ff) << 6); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java index 87ba29b..cf2d757 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java @@ -341,7 +341,7 @@ public class FT8TransmitSignal { try { Thread.sleep(1); long current = System.currentTimeMillis() - now; - if (current > 13000) {//实际发射的时长 + if (current > 13100) {//实际发射的时长 isTransmitting = false; break; } @@ -491,7 +491,8 @@ public class FT8TransmitSignal { if ((GeneralVariables.checkFun3(message.extraInfo) || GeneralVariables.checkFun2(message.extraInfo)) && (message.callsignFrom.equals(toCallsign.callsign) - && message.callsignTo.equals(GeneralVariables.myCallsign))) { + && GeneralVariables.checkIsMyCallsign(message.callsignTo))) { + //&& message.callsignTo.equals(GeneralVariables.myCallsign))) { receiveTargetReport = getReportFromExtraInfo(message.extraInfo); break; } @@ -502,7 +503,8 @@ public class FT8TransmitSignal { if ((GeneralVariables.checkFun3(message.extraInfo) || GeneralVariables.checkFun2(message.extraInfo)) && (message.callsignTo.equals(toCallsign.callsign) - && message.callsignFrom.equals(GeneralVariables.myCallsign))) { + && GeneralVariables.checkIsMyCallsign(message.callsignFrom))) { + //&& message.callsignFrom.equals(GeneralVariables.myCallsign))) { sentTargetReport = getReportFromExtraInfo(message.extraInfo); break; } @@ -581,7 +583,8 @@ public class FT8TransmitSignal { if (toCallsign == null) { continue; } - if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + //if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + if (GeneralVariables.checkIsMyCallsign(ft8Message.getCallsignTo()) && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { return 0; } @@ -606,7 +609,8 @@ public class FT8TransmitSignal { continue; } //是双方的呼叫信息 - if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + //if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + if (GeneralVariables.checkIsMyCallsign(ft8Message.getCallsignTo()) && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { //--TODO ----检查起始时间是不是0,如果是0,补充起始时间。因为有的呼叫会越过第一步 @@ -676,7 +680,8 @@ public class FT8TransmitSignal { if (isExcludeMessage(msg)) continue;//检查是不是属于排除的消息: if (toCallsign == null) break; - if (msg.getCallsignTo().equals(GeneralVariables.myCallsign) + //if (msg.getCallsignTo().equals(GeneralVariables.myCallsign) + if (GeneralVariables.checkIsMyCallsign(msg.getCallsignTo()) && msg.getCallsignFrom().equals(toCallsign.callsign)//todo 注意测试复合呼号的情况 && !GeneralVariables.checkFun5(msg.extraInfo)) {//cq我、不是73、发送方是我关注的目标 //设置发射之前,确定消息的序号,避免从头开始 @@ -692,7 +697,8 @@ public class FT8TransmitSignal { for (int i = messages.size() - 1; i >= 0; i--) {//此处是检查有没有CQ我。(TO:ME,且不能是73) Ft8Message msg = messages.get(i); if (isExcludeMessage(msg)) continue;//检查是不是属于排除的消息: - if ((msg.getCallsignTo().equals(GeneralVariables.myCallsign) + //if ((msg.getCallsignTo().equals(GeneralVariables.myCallsign) + if ((GeneralVariables.checkIsMyCallsign(msg.getCallsignTo()) && !GeneralVariables.checkFun5(msg.extraInfo))) {//cq我、不是73、 //设置发射之前,确定消息的序号,避免从头开始 setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz @@ -728,7 +734,8 @@ public class FT8TransmitSignal { && ((GeneralVariables.autoCallFollow && GeneralVariables.autoFollowCQ)//自动呼叫CQ || GeneralVariables.callsignInFollow(msg.getCallsignFrom()))//是我关注的 && !GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())//之前没有联通成功过 - && !msg.callsignFrom.equals(GeneralVariables.myCallsign))) {//不是我自己 + && !GeneralVariables.checkIsMyCallsign(msg.callsignFrom))) {//不是我自己 + //&& !msg.callsignFrom.equals(GeneralVariables.myCallsign))) {//不是我自己 resetTargetReport(); setTransmit(new TransmitCallsign(msg.i3, msg.n3, msg.getCallsignFrom(), msg.freq_hz diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java index d7791f6..71dfa7f 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java @@ -122,15 +122,7 @@ public class GenerateFT8 { return generateFt8(msg,frequency,sample_rate,true); } - /** - * 生成FT8信号 - * @param msg 消息 - * @param frequency 频率 - * @param sample_rate 采样率 - * @param hasModifier 是否有修饰符 - * @return - */ - public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate,boolean hasModifier) { + public static byte[] generateA91(Ft8Message msg,boolean hasModifier){ if (msg.callsignFrom.length()<3){ ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); return null; @@ -146,6 +138,7 @@ public class GenerateFT8 { msg.modifier=""; } + //判定用非标准呼号i3=4的条件: //1.FROMCALL为非标准呼号 ,且 符合2或3 //2.扩展消息时 网格、RR73,RRR,73 @@ -157,7 +150,9 @@ public class GenerateFT8 { if (!checkIsStandardCallsign(msg.callsignFrom) && (!checkIsReport(msg.extraInfo) || msg.checkIsCQ())) { msg.i3 = 4; - } else if (msg.callsignFrom.endsWith("/P")||(msg.callsignTo.endsWith("/P"))) { + //} else if (msg.callsignFrom.endsWith("/P")||(msg.callsignTo.endsWith("/P"))) { + } else if (msg.callsignFrom.endsWith("/P")//如果目标有/P后缀,则以目标呼号为准。如果目标没有/P后缀,则以发送方是否有/P后缀为准 + ||(msg.callsignTo.endsWith("/P")&&(!msg.callsignFrom.endsWith("/P")))) { msg.i3 = 2; } else { msg.i3 = 1; @@ -172,39 +167,62 @@ public class GenerateFT8 { packFreeTextTo77(msg.getMessageText(), packed); } - return generateFt8ByA91(packed,frequency,sample_rate); + return packed; + } - /* - // 其次,将二进制消息编码为FSK音调序列,79个字节 - byte[] tones = new byte[num_tones]; // 79音调(符号)数组 - //此处是88个字节(91+7)/8,可以使用a91生成音频 - ft8_encode(packed, tones); + /** + * 生成FT8信号 + * @param msg 消息 + * @param frequency 频率 + * @param sample_rate 采样率 + * @param hasModifier 是否有修饰符 + * @return + */ + public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate,boolean hasModifier) { +// if (msg.callsignFrom.length()<3){ +// ToastMessage.show(GeneralVariables.getStringFromResource(R.string.callsign_error)); +// return null; +// } +// // 首先,将文本数据打包为二进制消息,共12个字节 +// byte[] packed = new byte[FTX_LDPC_K_BYTES]; +// //把"<>"去掉 +// msg.callsignTo = msg.callsignTo.replace("<", "").replace(">", ""); +// msg.callsignFrom = msg.callsignFrom.replace("<", "").replace(">", ""); +// if (hasModifier) { +// msg.modifier = GeneralVariables.toModifier;//修饰符 +// }else { +// msg.modifier=""; +// } - // 第三,将FSK音调转换为音频信号b + //判定用非标准呼号i3=4的条件: + //1.FROMCALL为非标准呼号 ,且 符合2或3 + //2.扩展消息时 网格、RR73,RRR,73 + //3.CQ,QRZ,DE - int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 - //float[] signal = new float[Ft8num_samples]; - float[] signal = new float[num_samples]; +// if (msg.i3 != 0) {//目前只支持i3=1,i3=2,i3=4,i3=0 && n3=0 +// if (!checkIsStandardCallsign(msg.callsignFrom) +// && (!checkIsReport(msg.extraInfo) || msg.checkIsCQ())) { +// msg.i3 = 4; +// } else if (msg.callsignFrom.endsWith("/P")||(msg.callsignTo.endsWith("/P"))) { +// msg.i3 = 2; +// } else { +// msg.i3 = 1; +// } +// } +// +// if (msg.i3 == 1 || msg.i3 == 2) { +// packed = FT8Package.generatePack77_i1(msg); +// } else if (msg.i3 == 4) {//说明是非标准呼号 +// packed = FT8Package.generatePack77_i4(msg); +// } else { +// packFreeTextTo77(msg.getMessageText(), packed); +// } - //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 - //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - signal[i] = 0; - } + return generateFt8ByA91(generateA91(msg,hasModifier),frequency,sample_rate); + //return generateFt8ByA91(packed,frequency,sample_rate); - // 用79个字节符号,生成FT8音频 - synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - if (signal[i]>1.0||signal[i]<-1.0){ - Log.e(TAG, "generateFt8: "+signal[i] ); - } - } - return signal; - */ } public static float[] generateFt8ByA91(byte[] a91, float frequency,int sample_rate){ @@ -218,11 +236,6 @@ 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]; //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java index 1b352be..c11f19a 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridMarkerInfoWindow.java @@ -91,7 +91,8 @@ public class GridMarkerInfoWindow extends InfoWindow { ImageButton imageButton=(ImageButton) this.mView.findViewById(R.id.callThisImageButton); - if (GeneralVariables.myCallsign.equals(msg.getCallsignFrom())){ + //if (GeneralVariables.myCallsign.equals(msg.getCallsignFrom())){ + if (GeneralVariables.checkIsMyCallsign(msg.getCallsignFrom())){ imageButton.setVisibility(View.GONE); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java index 49c0ee6..b2506dc 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridTrackerMainActivity.java @@ -222,6 +222,7 @@ public class GridTrackerMainActivity extends AppCompatActivity { getString(R.string.decoding_takes_milliseconds) , mainViewModel.ft8SignalListener.decodeTimeSec.getValue()))); + //画电台之间的连线 //对CQ的电台打点 for (Ft8Message msg : tempMsg) { @@ -765,7 +766,8 @@ public class GridTrackerMainActivity extends AppCompatActivity { if (message != null) { //呼叫的目标不能是自己 if (!message.getCallsignFrom().equals("<...>") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + //&& !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !GeneralVariables.checkIsMyCallsign(message.getCallsignFrom()) && !(message.i3 == 0 && message.n3 == 0)) { doCallNow(message); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java index 860dbb2..6842993 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java @@ -1297,6 +1297,7 @@ public class LogHttpServer extends NanoHTTPD { , GeneralVariables.getStringFromResource(R.string.html_qsl_freq)//"freq" , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" + , "Operator"//"my_gridsquare" , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") .append("\n"); HtmlContext.tableRowEnd(result).append("\n"); @@ -1317,6 +1318,7 @@ public class LogHttpServer extends NanoHTTPD { String freq = cursor.getString(cursor.getColumnIndex("freq")); String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); + String operator = cursor.getString(cursor.getColumnIndex("operator")); String comment = cursor.getString(cursor.getColumnIndex("comment")); @@ -1336,9 +1338,9 @@ public class LogHttpServer extends NanoHTTPD { .replace(">", "") , station_callsign.replace("<", "<") .replace(">", ">"))); - HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare - , comment).append("\n"); + HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare); + HtmlContext.tableCell(result, operator == null ? "" : operator, comment).append("\n"); HtmlContext.tableRowEnd(result).append("\n"); order++; } @@ -1888,6 +1890,7 @@ public class LogHttpServer extends NanoHTTPD { } else { logStr.append("Y "); } + if (cursor.getString(cursor.getColumnIndex("gridsquare")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("gridsquare")).length() @@ -1960,6 +1963,14 @@ public class LogHttpServer extends NanoHTTPD { , cursor.getString(cursor.getColumnIndex("my_gridsquare")))); } + if (cursor.getColumnIndex("operator") != -1) { + if (cursor.getString(cursor.getColumnIndex("operator")) != null) { + logStr.append(String.format("%s " + , cursor.getString(cursor.getColumnIndex("operator")).length() + , cursor.getString(cursor.getColumnIndex("operator")))); + } + } + String comment = cursor.getString(cursor.getColumnIndex("comment")); //Distance: 99 km diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java index 6604a1a..8af31d0 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/log/SWLQsoList.java @@ -79,7 +79,8 @@ public class SWLQsoList { 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()) + //if (msg.callsignFrom.equals(record.getMyCallsign()) + if (GeneralVariables.checkIsMyCallsign(msg.callsignFrom) && msg.callsignTo.equals(record.getToCallsign()) && !foundFromReport) {//callsignFrom发出的信号报告 int report = GeneralVariables.checkFun2_3(msg.extraInfo); @@ -92,7 +93,8 @@ public class SWLQsoList { } if (msg.callsignFrom.equals(record.getToCallsign()) - && msg.callsignTo.equals(record.getMyCallsign()) + //&& msg.callsignTo.equals(record.getMyCallsign()) + && GeneralVariables.checkIsMyCallsign(msg.callsignTo) && !foundToReport) {//callsignTo发出的信号报告 int report = GeneralVariables.checkFun2_3(msg.extraInfo); if (time_on > msg.utcTime) time_on = msg.utcTime;//取最早的时间 @@ -123,7 +125,8 @@ public class SWLQsoList { for (int i = allMessages.size() - 1; i >= 0; i--) { Ft8Message msg = allMessages.get(i); if (!foundFromGrid - && msg.callsignFrom.equals(record.getMyCallsign()) + //&& msg.callsignFrom.equals(record.getMyCallsign()) + && GeneralVariables.checkIsMyCallsign(msg.callsignFrom) && (msg.callsignTo.equals(record.getToCallsign()) || msg.checkIsCQ())) {//callsignFrom的网格报告 if (GeneralVariables.checkFun1_6(msg.extraInfo)) { @@ -135,7 +138,9 @@ public class SWLQsoList { if (!foundToGrid && msg.callsignFrom.equals(record.getToCallsign()) - && (msg.callsignTo.equals(record.getMyCallsign())|| msg.checkIsCQ())) {//callsignTo发出的信号报告 + //&& (msg.callsignTo.equals(record.getMyCallsign()) + && (GeneralVariables.checkIsMyCallsign(msg.callsignTo) + || msg.checkIsCQ())) {//callsignTo发出的信号报告 if (GeneralVariables.checkFun1_6(msg.extraInfo)) { record.setToMaidenGrid(msg.extraInfo.trim()); foundToGrid = true; diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java index 003d598..2a832c7 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Flex6000Rig.java @@ -139,6 +139,7 @@ public class Flex6000Rig extends BaseRig { } } },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); } } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java index d15c0d0..a5502c7 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomCommand.java @@ -114,7 +114,7 @@ public class IcomCommand { } if (buffer[i] == (byte) 0xfe && buffer[i + 1] == (byte) 0xfe//命令头0xfe 0xfe - && (buffer[i + 2] == (byte) ctrAddr || buffer[i + 2] == (byte) 0x00)//控制者地址默认E0或00 + && (buffer[i + 2] == (byte) ctrAddr || (buffer[i + 2] == (byte) 0x00))//控制者地址默认E0或00 && buffer[i + 3] == (byte) rigAddr) {//电台地址,705的默认值是A4,协谷是70 position = i; break; diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java index 7e7ea33..8184696 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/IcomRig.java @@ -29,6 +29,8 @@ public class IcomRig extends BaseRig { private boolean alcMaxAlert = false; private boolean swrAlert = false; private Timer meterTimer;//查询meter的Timer + + private boolean oldVersion=false;//针对老电台,可能不支持SWR查询 //private boolean isPttOn = false; @Override @@ -166,9 +168,11 @@ public class IcomRig extends BaseRig { //目前只对频率和模式消息作反应 switch (icomCommand.getCommandID()) { + case IcomRigConstant.CMD_SEND_FREQUENCY_DATA://获取到的是频率数据 case IcomRigConstant.CMD_READ_OPERATING_FREQUENCY: //获取频率 + //ToastMessage.show(byteToStr(icomCommand.getData(false))); setFreq(icomCommand.getFrequency(false)); break; case IcomRigConstant.CMD_SEND_MODE_DATA://获取到的是模式数据 @@ -211,9 +215,7 @@ public class IcomRig extends BaseRig { @Override public void onReceiveData(byte[] data) { - - //ToastMessage.show("--"+byteToStr(data)); - + //ToastMessage.show(byteToStr(data)); int commandEnd = getCommandEnd(data); if (commandEnd <= -1) {//这是没有指令结尾 @@ -259,7 +261,7 @@ public class IcomRig extends BaseRig { meterTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { - if (isPttOn()) {//当Ptt被按下去的时候测量 + if (isPttOn() && !oldVersion) {//当Ptt被按下去的时候测量,并且不是老版本的电台 sendCivData(IcomRigConstant.getSWRState(ctrAddress, getCivAddress())); sendCivData(IcomRigConstant.getALCState(ctrAddress, getCivAddress())); } @@ -272,8 +274,10 @@ public class IcomRig extends BaseRig { return BaseRigOperation.getFrequencyStr(getFreq()); } - public IcomRig(int civAddress) { + + public IcomRig(int civAddress,boolean newRig) { Log.d(TAG, "IcomRig: Create."); + this.oldVersion = !newRig; setCivAddress(civAddress); startMeterTimer(); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java index 348a0be..75ff115 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/InstructionSet.java @@ -23,6 +23,10 @@ public class InstructionSet { public static final int KENWOOD_TS570=18;//KENWOOD TS-570D public static final int YAESU_3_9_U_DIG=19;//KENWOOD TS-570D + public static final int XIEGU_6100_FT8CNS=20;//6100的ft8cns版 + public static final int YAESU_847=21;//Ft-847 + public static final int ICOM_756=22;//Ft-847 + diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/TrUSDXRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/TrUSDXRig.java index aa09fa2..c1b335d 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/TrUSDXRig.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/TrUSDXRig.java @@ -248,7 +248,8 @@ public class TrUSDXRig extends BaseRig { rxStreamBuffer.write(data, 0, data.length); if (rxStreamBuffer.size() >= 256 || force) {//8位转16位,7812Hz转12000Hz //byte[] resampled = rxResample.processCopy(toWaveSamples8To16(rxStreamBuffer.toByteArray())); - float[] resampled = FT8Resample.get32Resample16(toWaveSamples8To16Int(rxStreamBuffer.toByteArray()), rxSampling, 12000); + float[] resampled = FT8Resample.get32Resample16( + toWaveSamples8To16Int(rxStreamBuffer.toByteArray()), rxSampling, 12000,1); rxStreamBuffer.reset(); getConnector().receiveWaveData(resampled); } @@ -280,7 +281,7 @@ public class TrUSDXRig extends BaseRig { // txResample.close(); // byte[] pcm8 = toWaveSamples16To8(resampled); - byte[] pcm8 = FT8Resample.get8Resample32(wave, 24000, txSampling); + byte[] pcm8 = FT8Resample.get8Resample32(wave, 24000, txSampling,1); for (int i = 0; i < pcm8.length; i++) { diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Command.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Command.java index 4b44a24..411eca6 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Command.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Command.java @@ -216,6 +216,7 @@ public class XieGu6100Command { */ public long getFrequency(boolean hasSubCommand) { byte[] data = getData(hasSubCommand); + if (data == null) return -1; if (data.length < 5) { return -1; } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100NetRig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100NetRig.java new file mode 100644 index 0000000..31d70e3 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100NetRig.java @@ -0,0 +1,89 @@ +package com.bg7yoz.ft8cn.rigs; + +import android.util.Log; + +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.connector.X6100Connector; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; + +/** + * XieGu6100的ft8cns模式,只支持网络模式,所以在设置baseRig时要做好判断 + */ +public class XieGu6100NetRig extends BaseRig { + private static final String TAG = "x6100RigNet"; + + //private final int ctrAddress = 0xE0;//接收地址,默认0xE0;电台回复命令有时也可以是0x00 + + + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + if (getConnector() != null) { + getConnector().setPttOn(on); + } + } + + @Override + public boolean isConnected() { + if (getConnector() == null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector() != null) { + X6100Connector x6100Connector =(X6100Connector)getConnector(); + x6100Connector.getXieguRadio().commandSetMode("u-dig",1); + } + } + + @Override + public void setFreqToRig() { + if (getConnector() != null) { + X6100Connector x6100Connector =(X6100Connector)getConnector(); + x6100Connector.getXieguRadio().commandTuneFreq(getFreq()); + } + } + + + @Override + public void onReceiveData(byte[] data) { + //命令解析都在X6100Radio中完成了,此处不需要动作了 + } + + @Override + public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式 + if (getConnector() != null) {//把生成的具体音频数据传递到Connector, + //判断如果是ft8cns,就传输a19数据包 + if (GeneralVariables.instructionSet == InstructionSet.XIEGU_6100_FT8CNS){ + //Log.e(TAG,"generate A91"); + getConnector().sendFt8A91(GenerateFT8.generateA91(message,true) + ,GeneralVariables.getBaseFrequency()); + }else {//否则正常传输音频数据 + float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() + , 12000);//此处电台发射音频的采样率是12000 + getConnector().sendWaveData(data); + } + } + } + + @Override + public void readFreqFromRig() {//通过X6100Radio的状态来获取频率,此处获取频率指令不需要了 + + } + + @Override + public String getName() { + return "XIEGU X6100 series"; + } + + public XieGu6100NetRig(int civAddress) { + Log.d(TAG, "x6100RigNet: Create."); + setCivAddress(civAddress); + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java index 8f40d0d..3fcc5f9 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGu6100Rig.java @@ -9,6 +9,7 @@ import com.bg7yoz.ft8cn.Ft8Message; import com.bg7yoz.ft8cn.GeneralVariables; import com.bg7yoz.ft8cn.R; import com.bg7yoz.ft8cn.connector.ConnectMode; +import com.bg7yoz.ft8cn.connector.X6100Connector; import com.bg7yoz.ft8cn.database.ControlMode; import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; import com.bg7yoz.ft8cn.ui.ToastMessage; @@ -221,13 +222,20 @@ public class XieGu6100Rig extends BaseRig { @Override public void sendWaveData(Ft8Message message) {//发送音频数据到电台,用于网络方式 if (getConnector() != null) {//把生成的具体音频数据传递到Connector, - float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() - ,12000);//此处icom电台发射音频的采样率是12000 - if (data==null){ - setPTT(false); - return; + //判断如果是ft8cns,就传输a19数据包 + if (GeneralVariables.instructionSet == InstructionSet.XIEGU_6100_FT8CNS){ + //Log.e(TAG,"generate A91"); + getConnector().sendFt8A91(GenerateFT8.generateA91(message,true) + ,GeneralVariables.getBaseFrequency()); + }else {//否则正常传输音频数据 + float[] data = GenerateFT8.generateFt8(message, GeneralVariables.getBaseFrequency() + , 12000);//此处icom电台发射音频的采样率是12000 + if (data == null) { + setPTT(false); + return; + } + getConnector().sendWaveData(data); } - getConnector().sendWaveData(data); } } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java index 15de904..fe400fc 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2RigConstant.java @@ -19,6 +19,8 @@ public class Yaesu2RigConstant { private static final byte[] PTT_ON = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}; private static final byte[] PTT_OFF = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x88}; private static final byte[] GET_METER = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xBD}; + private static final byte[] GET_CONNECT = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + private static final byte[] GET_DISCONNECT = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80}; //USB模式 private static final byte[] USB_MODE = {(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x07}; //DIG模式 @@ -61,11 +63,19 @@ public class Yaesu2RigConstant { public static byte[] setOperationUSBMode() { return DIG_MODE; } - + public static byte[] setOperationUSB847Mode() { + return USB_MODE; + } public static byte[] readMeter() { return GET_METER; } + public static byte[] sendConnectData() { + return GET_CONNECT; + } + public static byte[] sendDisconnectData() { + return GET_DISCONNECT; + } public static byte[] setOperationFreq(long freq) { byte[] data = new byte[]{ (byte) (((byte) (freq % 1000000000 / 100000000) << 4) + (byte) (freq % 100000000 / 10000000)) diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2_847Rig.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2_847Rig.java new file mode 100644 index 0000000..1c7a922 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2_847Rig.java @@ -0,0 +1,178 @@ +package com.bg7yoz.ft8cn.rigs; + +import static com.bg7yoz.ft8cn.GeneralVariables.QUERY_FREQ_TIMEOUT; +import static com.bg7yoz.ft8cn.GeneralVariables.START_QUERY_FREQ_DELAY; + +import android.util.Log; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.database.ControlMode; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * YAESU的部分电台,回送的数据不是连续的,所以,要做一个缓冲区,接受5字节长度。满了就复位。或发送指令时,就复位。 + * ft848在连接成功后,必须发送5个0,结束后发送4个0加80 + */ +public class Yaesu2_847Rig extends BaseRig{ + private static final String TAG="Yaesu2_847Rig"; + private Timer readFreqTimer = new Timer(); + + private int swr = 0; + private int alc = 0; + private boolean alcMaxAlert = false; + private boolean swrAlert = false; + private boolean sentConnect =false; + + private TimerTask readTask(){ + return new TimerTask() { + @Override + public void run() { + try { + if (!isConnected()){ + readFreqTimer.cancel(); + readFreqTimer.purge(); + readFreqTimer=null; + return; + } + if (!sentConnect) {//发送连接头数据,5个0,只发送1次 + sendConnectData(); + sentConnect =!sentConnect; + } + + if (isPttOn()) { + readMeters(); + } else { + readFreqFromRig(); + } + }catch (Exception e) + { + Log.e(TAG, "readFreq error:"+e.getMessage() ); + } + } + }; + } + + + @Override + public void setPTT(boolean on) { + super.setPTT(on); + + if (getConnector()!=null){ + switch (getControlMode()){ + case ControlMode.CAT://以CIV指令 + getConnector().setPttOn(Yaesu2RigConstant.setPTTState(on)); + break; + case ControlMode.RTS: + case ControlMode.DTR: + getConnector().setPttOn(on); + break; + } + } + } + + + + + @Override + public boolean isConnected() { + if (getConnector()==null) { + return false; + } + return getConnector().isConnected(); + } + + @Override + public void setUsbModeToRig() { + if (getConnector()!=null){ + getConnector().sendData(Yaesu2RigConstant.setOperationUSB847Mode()); + } + } + + @Override + public void setFreqToRig() { + if (getConnector()!=null){ + getConnector().sendData(Yaesu2RigConstant.setOperationFreq(getFreq())); + } + } + + @Override + public void onReceiveData(byte[] data) { + //YAESU 817的指令,返回频率是5字节的,METER是2字节的。 + //Meter是2字节的,第一字节高位功率,0-A,低位ALC 0-9,第二字节高位驻波比,0-C,0为高驻波,低位音频输入0-8 + if (data.length == 5) {//频率 + long freq = Yaesu2Command.getFrequency(data); + if (freq > -1) { + setFreq(freq); + } + } else if (data.length == 2) {//METERS + alc = (data[0] & 0x0f); + swr = (data[1] & 0x0f0) >> 4; + showAlert(); + } + + } + + /** + * 读取Meter RM; + */ + private void readMeters() { + if (getConnector() != null) { + getConnector().sendData(Yaesu2RigConstant.readMeter()); + } + } + + private void sendConnectData() {//连接电台后,要发送5个0 + if (getConnector() != null) { + getConnector().sendData(Yaesu2RigConstant.sendConnectData()); + } + } + + @Override + public void onDisconnecting() {//断开电台前,要发送4个0加80 + if (getConnector() != null) { + getConnector().sendData(Yaesu2RigConstant.sendDisconnectData()); + } + super.onDisconnecting(); + } + + private void showAlert() { + if (swr > Yaesu2RigConstant.swr_817_alert_min) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc >= Yaesu2RigConstant.alc_817_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + @Override + public void readFreqFromRig(){ + if (getConnector()!=null){ + //clearBuffer();//清除一下缓冲区 + getConnector().sendData(Yaesu2RigConstant.setReadOperationFreq()); + } + } + + @Override + public String getName() { + return "YAESU 847 series"; + } + + public Yaesu2_847Rig() { + readFreqTimer.schedule(readTask(),START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java index 59390de..3aa700f 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3RigConstant.java @@ -1,132 +1,132 @@ -package com.bg7yoz.ft8cn.rigs; - -import android.annotation.SuppressLint; - -public class Yaesu3RigConstant { - private static final String TAG = "Yaesu3RigConstant"; - //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 - public static final int LSB = 0x01; - public static final int USB = 0x02; - public static final int CW = 0x03; - public static final int FM = 0x04; - public static final int AM = 0x05; - public static final int RTTY = 0x06; - public static final int CW_R = 0x07; - public static final int DATA = 0x08; - public static final int RTTY_R = 0x09; - public static final int NONE = 0x0A; - public static final int FM_N = 0x0B; - public static final int DATA_R = 0x0C; - public static final int AM_N = 0x0D; - - - public static final int swr_39_alert_max=125;//相当于3.0 - public static final int alc_39_alert_max=125;//超过,在表上显示红色 - //PTT状态 - - //指令集 - private static final String PTT_ON = "MX1;"; - private static final String PTT_OFF = "MX0;"; - private static final String USB_MODE = "MD02;"; - private static final String USB_MODE_DATA = "MD09;"; - private static final String DATA_U_MODE = "MD0C;"; - private static final String READ_FREQ = "FA;"; - private static final String READ_39METER_ALC = "RM4;";//38,39的指令都是一样的 - private static final String READ_39METER_SWR = "RM6;";//38,39的指令都是一样的 - - private static final String TX_ON = "TX1;";//用于FT450 ptt - private static final String TX_OFF = "TX0;";//用于FT450 ptt - - - - - - - public static String getModeStr(int mode) { - switch (mode) { - case LSB: - return "LSB"; - case USB: - return "USB"; - case CW: - return "CW"; - case FM: - return "FM"; - case AM: - return "AM"; - case RTTY: - return "RTTY"; - case CW_R: - return "CW_R"; - case DATA: - return "DATA"; - case RTTY_R: - return "RTTY_R"; - case NONE: - return "NONE"; - case FM_N: - return "FM_N"; - case DATA_R: - return "DATA_R"; - case AM_N: - return "AM_N"; - default: - return "UNKNOWN"; - } - } - - - public static byte[] setPTTState(boolean on) { - if (on) { - return PTT_ON.getBytes(); - } else { - return PTT_OFF.getBytes(); - } - - } - //针对YAESU 450的发射指令 - public static byte[] setPTT_TX_On(boolean on) {//用于FT450 - if (on) { - return TX_ON.getBytes(); - } else { - return TX_OFF.getBytes(); - } - - } - public static byte[] setOperationUSBMode() { - return USB_MODE.getBytes(); - } - public static byte[] setOperationUSB_Data_Mode() { - return DATA_U_MODE.getBytes(); - } - - public static byte[] setOperationDATA_U_Mode() { - return DATA_U_MODE.getBytes(); - } - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 - return String.format("FA%011d;",freq).getBytes(); - } - - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq9Byte(long freq) { - return String.format("FA%09d;",freq).getBytes(); - } - @SuppressLint("DefaultLocale") - public static byte[] setOperationFreq8Byte(long freq) { - return String.format("FA%08d;",freq).getBytes(); - } - public static byte[] setReadOperationFreq(){ - return READ_FREQ.getBytes(); - } - - public static byte[] setRead39Meters_ALC(){ - return READ_39METER_ALC.getBytes(); - } - public static byte[] setRead39Meters_SWR(){ - return READ_39METER_SWR.getBytes(); - } - - -} +package com.bg7yoz.ft8cn.rigs; + +import android.annotation.SuppressLint; + +public class Yaesu3RigConstant { + private static final String TAG = "Yaesu3RigConstant"; + //LSB:0,USB:1,AM:2,CW:3,RTTY:4,FM:5,WFM:6,CW_R:7,RTTY_R:8,DV:17 + public static final int LSB = 0x01; + public static final int USB = 0x02; + public static final int CW = 0x03; + public static final int FM = 0x04; + public static final int AM = 0x05; + public static final int RTTY = 0x06; + public static final int CW_R = 0x07; + public static final int DATA = 0x08; + public static final int RTTY_R = 0x09; + public static final int NONE = 0x0A; + public static final int FM_N = 0x0B; + public static final int DATA_R = 0x0C; + public static final int AM_N = 0x0D; + + + public static final int swr_39_alert_max=125;//相当于3.0 + public static final int alc_39_alert_max=125;//超过,在表上显示红色 + //PTT状态 + + //指令集 + private static final String PTT_ON = "MX1;"; + private static final String PTT_OFF = "MX0;"; + private static final String USB_MODE = "MD02;"; + private static final String USB_MODE_DATA = "MD09;"; + private static final String DATA_U_MODE = "MD0C;"; + private static final String READ_FREQ = "FA;"; + private static final String READ_39METER_ALC = "RM4;";//38,39的指令都是一样的 + private static final String READ_39METER_SWR = "RM6;";//38,39的指令都是一样的 + + private static final String TX_ON = "TX1;";//用于FT450 ptt + private static final String TX_OFF = "TX0;";//用于FT450 ptt + + + + + + + public static String getModeStr(int mode) { + switch (mode) { + case LSB: + return "LSB"; + case USB: + return "USB"; + case CW: + return "CW"; + case FM: + return "FM"; + case AM: + return "AM"; + case RTTY: + return "RTTY"; + case CW_R: + return "CW_R"; + case DATA: + return "DATA"; + case RTTY_R: + return "RTTY_R"; + case NONE: + return "NONE"; + case FM_N: + return "FM_N"; + case DATA_R: + return "DATA_R"; + case AM_N: + return "AM_N"; + default: + return "UNKNOWN"; + } + } + + + public static byte[] setPTTState(boolean on) { + if (on) { + return PTT_ON.getBytes(); + } else { + return PTT_OFF.getBytes(); + } + + } + //针对YAESU 450的发射指令 + public static byte[] setPTT_TX_On(boolean on) {//用于FT450 + if (on) { + return TX_ON.getBytes(); + } else { + return TX_OFF.getBytes(); + } + + } + public static byte[] setOperationUSBMode() { + return USB_MODE.getBytes(); + } + public static byte[] setOperationUSB_Data_Mode() { + return DATA_U_MODE.getBytes(); + } + + public static byte[] setOperationDATA_U_Mode() { + return DATA_U_MODE.getBytes(); + } + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq11Byte(long freq) {//用于KENWOOD TS590 + return String.format("FA%011d;",freq).getBytes(); + } + + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq9Byte(long freq) { + return String.format("FA%09d;",freq).getBytes(); + } + @SuppressLint("DefaultLocale") + public static byte[] setOperationFreq8Byte(long freq) { + return String.format("FA%08d;",freq).getBytes(); + } + public static byte[] setReadOperationFreq(){ + return READ_FREQ.getBytes(); + } + + public static byte[] setRead39Meters_ALC(){ + return READ_39METER_ALC.getBytes(); + } + public static byte[] setRead39Meters_SWR(){ + return READ_39METER_SWR.getBytes(); + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java index f294da3..e83fd2f 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialPort.java @@ -27,6 +27,7 @@ import java.util.EnumSet; public interface UsbSerialPort extends Closeable { /** 5 data bits. */ + //数据位 int DATABITS_5 = 5; /** 6 data bits. */ int DATABITS_6 = 6; @@ -39,6 +40,7 @@ public interface UsbSerialPort extends Closeable { @Retention(RetentionPolicy.SOURCE) @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) @interface Parity {} + //奇偶校验位 /** No parity. */ int PARITY_NONE = 0; /** Odd parity. */ diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java index c0ab7d2..d7228ce 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java @@ -53,7 +53,8 @@ public class CallingListAdapter extends RecyclerView.Adapter") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) - && !(message.i3 == 0 && message.n3 == 0)) { + //&& !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !GeneralVariables.checkIsMyCallsign(message.getCallsignFrom()) + && !(message.i3 == 0 && (message.n3 == 0 || message.n3 == 5))) {//遥测和自由文本不能呼叫 doCallNow(message); } else { callingListAdapter.notifyItemChanged(viewHolder.getAdapterPosition()); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java index 3501cbc..e4c2dcf 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java @@ -48,6 +48,9 @@ public class ConfigFragment extends Fragment { private FragmentConfigBinding binding; private BandsSpinnerAdapter bandsSpinnerAdapter; private BauRateSpinnerAdapter bauRateSpinnerAdapter; + private SerialDataBitsSpinnerAdapter dataBitsSpinnerAdapter; + private SerialParityBitsSpinnerAdapter parityBitsSpinnerAdapter; + private SerialStopBitsSpinnerAdapter stopBitsSpinnerAdapter; private RigNameSpinnerAdapter rigNameSpinnerAdapter; private LaunchSupervisionSpinnerAdapter launchSupervisionSpinnerAdapter; private PttDelaySpinnerAdapter pttDelaySpinnerAdapter; @@ -257,13 +260,6 @@ public class ConfigFragment extends Fragment { mainViewModel = MainViewModel.getInstance(this); binding = FragmentConfigBinding.inflate(inflater, container, false); - //只对中国开方问题收集 -// if (GeneralVariables.isChina) { -// binding.faqButton.setVisibility(View.VISIBLE); -// } else { -// binding.faqButton.setVisibility(View.GONE); -// } - //设置时间偏移 setUtcTimeOffsetSpinner(); @@ -277,6 +273,15 @@ public class ConfigFragment extends Fragment { //设置波特率列表 setBauRateSpinner(); + //设置数据位列表 + setDataBitsSpinner(); + + //设置校验位 + setParityBitsSpinner(); + + //设置停止位 + setStopBitsSpinner(); + //设置电台名称,参数列表 setRigNameSpinner(); @@ -307,6 +312,9 @@ public class ConfigFragment extends Fragment { //设置无回应次数中断 setNoReplyLimitSpinner(); + //设置各个spinner的OnItemSelected事件 + setSpinnerOnItemSelected(); + //显示滚动箭头 new Handler().postDelayed(new Runnable() { @Override @@ -387,122 +395,24 @@ public class ConfigFragment extends Fragment { }); //设置PTT延迟 - binding.pttDelayOffsetSpinner.setOnItemSelectedListener(null); binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); - binding.pttDelayOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.pttDelay = i * 10; - writeConfig("pttDelay", String.valueOf(GeneralVariables.pttDelay)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - //获取操作的波段 - binding.operationBandSpinner.setOnItemSelectedListener(null); binding.operationBandSpinner.setSelection(GeneralVariables.bandListIndex); - binding.operationBandSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.bandListIndex = i; - GeneralVariables.band = OperationBand.getBandFreq(i);//把当前的频段保存下来 - - mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 - writeConfig("bandFreq", String.valueOf(GeneralVariables.band)); - if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 - || GeneralVariables.controlMode == ControlMode.RTS - || GeneralVariables.controlMode == ControlMode.DTR) { - //如果在CAT、RTS模式下,修改电台的频率 - mainViewModel.setOperationBand(); - } - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - - //获取电台型号 - binding.rigNameSpinner.setOnItemSelectedListener(null); binding.rigNameSpinner.setSelection(GeneralVariables.modelNo); - new Handler().postDelayed(new Runnable() {//延迟2秒修改OnItemSelectedListener - @Override - public void run() { - binding.rigNameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.modelNo = i; - writeConfig("model", String.valueOf(i)); - setAddrAndBauRate(rigNameSpinnerAdapter.getRigName(i)); - - //指令集 - GeneralVariables.instructionSet = rigNameSpinnerAdapter.getRigName(i).instructionSet; - writeConfig("instruction", String.valueOf(GeneralVariables.instructionSet)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - } - }); - } - }, 2000); - - + //串口数据位 + binding.dataBitsSpinner.setSelection(dataBitsSpinnerAdapter.getPosition(GeneralVariables.serialDataBits)); + //串口停止位 + binding.stopBitsSpinner.setSelection(stopBitsSpinnerAdapter.getPosition(GeneralVariables.serialStopBits)); + binding.parityBitsSpinner.setSelection(parityBitsSpinnerAdapter.getPosition(GeneralVariables.serialParity)); //获取波特率 - binding.baudRateSpinner.setOnItemSelectedListener(null); binding.baudRateSpinner.setSelection(bauRateSpinnerAdapter.getPosition( GeneralVariables.baudRate)); - binding.baudRateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.baudRate = bauRateSpinnerAdapter.getValue(i); - writeConfig("baudRate", String.valueOf(GeneralVariables.baudRate)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - } - }); - //设置发射监管 - binding.launchSupervisionSpinner.setOnItemSelectedListener(null); binding.launchSupervisionSpinner.setSelection(launchSupervisionSpinnerAdapter .getPosition(GeneralVariables.launchSupervision)); - binding.launchSupervisionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.launchSupervision = LaunchSupervisionSpinnerAdapter.getTimeOut(i); - writeConfig("launchSupervision", String.valueOf(GeneralVariables.launchSupervision)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); - //设置无回应中断 - binding.noResponseCountSpinner.setOnItemSelectedListener(null); binding.noResponseCountSpinner.setSelection(GeneralVariables.noReplyLimit); - binding.noResponseCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView adapterView, View view, int i, long l) { - GeneralVariables.noReplyLimit = i; - writeConfig("noReplyLimit", String.valueOf(GeneralVariables.noReplyLimit)); - } - - @Override - public void onNothingSelected(AdapterView adapterView) { - - } - }); //设置自动关注CQ @@ -587,17 +497,183 @@ public class ConfigFragment extends Fragment { } }); + //串口默认值设置复位键 + binding.serialDefaultButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + GeneralVariables.serialParity = 0; + GeneralVariables.serialDataBits = 8; + GeneralVariables.serialStopBits = 1; + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + binding.parityBitsSpinner.setSelection( + parityBitsSpinnerAdapter.getPosition(GeneralVariables.serialParity)); + binding.dataBitsSpinner.setSelection( + dataBitsSpinnerAdapter.getPosition(GeneralVariables.serialDataBits)); + binding.stopBitsSpinner.setSelection( + stopBitsSpinnerAdapter.getPosition(GeneralVariables.serialStopBits)); + } + }); + + } + }); + return binding.getRoot(); } + /** + * 设置各个spinner的OnItemSelected事件,防止在进入主界面时,重复向数据库写入配置信息 + */ + private void setSpinnerOnItemSelected(){ + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + binding.pttDelayOffsetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.pttDelay = i * 10; + writeConfig("pttDelay", String.valueOf(GeneralVariables.pttDelay)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + binding.operationBandSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.bandListIndex = i; + GeneralVariables.band = OperationBand.getBandFreq(i);//把当前的频段保存下来 + + mainViewModel.databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来 + writeConfig("bandFreq", String.valueOf(GeneralVariables.band)); + if (GeneralVariables.controlMode == ControlMode.CAT//CAT、RTS、DTR模式下控制电台 + || GeneralVariables.controlMode == ControlMode.RTS + || GeneralVariables.controlMode == ControlMode.DTR) { + //如果在CAT、RTS模式下,修改电台的频率 + mainViewModel.setOperationBand(); + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + binding.rigNameSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.modelNo = i; + writeConfig("model", String.valueOf(i)); + setAddrAndBauRate(rigNameSpinnerAdapter.getRigName(i)); + + //指令集 + GeneralVariables.instructionSet = rigNameSpinnerAdapter.getRigName(i).instructionSet; + writeConfig("instruction", String.valueOf(GeneralVariables.instructionSet)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + + + binding.baudRateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.baudRate = bauRateSpinnerAdapter.getValue(i); + writeConfig("baudRate", String.valueOf(GeneralVariables.baudRate)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + + + binding.launchSupervisionSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.launchSupervision = LaunchSupervisionSpinnerAdapter.getTimeOut(i); + writeConfig("launchSupervision", String.valueOf(GeneralVariables.launchSupervision)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + + binding.noResponseCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.noReplyLimit = i; + writeConfig("noReplyLimit", String.valueOf(GeneralVariables.noReplyLimit)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + //串口数据位 + binding.dataBitsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.serialDataBits = dataBitsSpinnerAdapter.getValue(i); + writeConfig("dataBits", String.valueOf(GeneralVariables.serialDataBits)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + //串口停止位 + binding.stopBitsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.serialStopBits = stopBitsSpinnerAdapter.getValue(i); + writeConfig("stopBits", String.valueOf(GeneralVariables.serialStopBits)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + //校验位 + binding.parityBitsSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + GeneralVariables.serialParity = parityBitsSpinnerAdapter.getValue(i); + writeConfig("parityBits", String.valueOf(GeneralVariables.serialParity)); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + } + }, 1000); + } + /** * 设置地址和波特率,指令集 * * @param rigName 电台型号 */ private void setAddrAndBauRate(RigNameList.RigName rigName) { - //mainViewModel.setCivAddress(rigName.address); GeneralVariables.civAddress = rigName.address; mainViewModel.setCivAddress(); GeneralVariables.baudRate = rigName.bauRate; @@ -715,6 +791,49 @@ public class ConfigFragment extends Fragment { }); } + /** + * 设置数据位列表 + */ + private void setDataBitsSpinner(){ + dataBitsSpinnerAdapter = new SerialDataBitsSpinnerAdapter(requireContext()); + binding.dataBitsSpinner.setAdapter(dataBitsSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + dataBitsSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + + /** + * 设置校验位列表 + */ + private void setParityBitsSpinner(){ + parityBitsSpinnerAdapter = new SerialParityBitsSpinnerAdapter(requireContext()); + binding.parityBitsSpinner.setAdapter(parityBitsSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + parityBitsSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + /** + * 设置停止位列表 + */ + private void setStopBitsSpinner(){ + stopBitsSpinnerAdapter = new SerialStopBitsSpinnerAdapter(requireContext()); + binding.stopBitsSpinner.setAdapter(stopBitsSpinnerAdapter); + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + stopBitsSpinnerAdapter.notifyDataSetChanged(); + } + }); + } + + + /** * 设置无回应次数中断 */ @@ -771,6 +890,8 @@ public class ConfigFragment extends Fragment { binding.pttDelayOffsetSpinner.setSelection(GeneralVariables.pttDelay / 10); } }); + + } @@ -943,13 +1064,15 @@ public class ConfigFragment extends Fragment { * 设置连线的方式,可以是USB,也可以是BLUE_TOOTH */ private void setConnectMode() { - if (GeneralVariables.controlMode == ControlMode.CAT + if ((GeneralVariables.controlMode == ControlMode.CAT) //&& BluetoothConstants.checkBluetoothIsOpen() ) { //此处要改成VISIBLE binding.connectModeLayout.setVisibility(View.VISIBLE); + binding.serialLayout.setVisibility(View.VISIBLE); } else { binding.connectModeLayout.setVisibility(View.GONE); + binding.serialLayout.setVisibility(View.GONE); } binding.connectModeRadioGroup.clearCheck(); switch (GeneralVariables.connectMode) { @@ -985,7 +1108,10 @@ public class ConfigFragment extends Fragment { //打开网络电台列表对话框 if (GeneralVariables.instructionSet== InstructionSet.FLEX_NETWORK) { new SelectFlexRadioDialog(requireContext(), mainViewModel).show(); - }else if(GeneralVariables.instructionSet== InstructionSet.ICOM + }else if (GeneralVariables.instructionSet== InstructionSet.XIEGU_6100_FT8CNS) { + new SelectXieguRadioDialog(requireContext(), mainViewModel).show(); + } + else if(GeneralVariables.instructionSet== InstructionSet.ICOM ||GeneralVariables.instructionSet== InstructionSet.XIEGU_6100 ||GeneralVariables.instructionSet== InstructionSet.XIEGUG90S) { new LoginIcomRadioDialog(requireContext(), mainViewModel).show(); @@ -1067,6 +1193,16 @@ public class ConfigFragment extends Fragment { , true).show(); } }); + //设置串口参数帮助 + binding.serialHelpImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.serial_setting_help) + , true).show(); + } + }); + //显示列表方式 binding.messageModeeHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java index 234e5f8..0e97529 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexRadioInfoFragment.java @@ -33,12 +33,6 @@ public class FlexRadioInfoFragment extends Fragment { } -// public static FlexRadioInfoFragment newInstance(String param1, String param2) { -// FlexRadioInfoFragment fragment = new FlexRadioInfoFragment(); -// -// return fragment; -// } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java index d3600fb..4bd92fe 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/MyCallingFragment.java @@ -467,7 +467,8 @@ public class MyCallingFragment extends Fragment { if (message != null) { //呼叫的目标不能是自己 if (!message.getCallsignFrom().equals("<...>") - && !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + //&& !message.getCallsignFrom().equals(GeneralVariables.myCallsign) + && !GeneralVariables.checkIsMyCallsign(message.getCallsignFrom()) && !(message.i3 == 0 && message.n3 == 0)) { doCallNow(message); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java index 00e6080..508f327 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/RigNameSpinnerAdapter.java @@ -50,11 +50,17 @@ public class RigNameSpinnerAdapter extends BaseAdapter { view=_LayoutInflater.inflate(R.layout.rig_name_spinner_item, null); if (view!=null){ TextView textView=view.findViewById(R.id.rigNameItemTextView); + if (rigNameList.getRigNameInfo(i).startsWith("#")) { + view.setVisibility(View.GONE); + } ImageView imageView=view.findViewById(R.id.rigLogoImageView); - if (rigNameList.getRigNameInfo(i).contains("GUOHE")){ + if (rigNameList.getRigNameInfo(i).toUpperCase().contains("GUOHE")){ imageView.setImageDrawable(mContext.getDrawable(R.drawable.guohe_logo)); imageView.setVisibility(View.VISIBLE); - }else { + }else if (rigNameList.getRigNameInfo(i).toUpperCase().contains("XIEGU")){ + imageView.setImageDrawable(mContext.getDrawable(R.drawable.xiegulogo)); + imageView.setVisibility(View.VISIBLE); + }else { imageView.setVisibility(View.GONE); imageView.setImageDrawable(null); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectXieguRadioDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectXieguRadioDialog.java new file mode 100644 index 0000000..6e1feef --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectXieguRadioDialog.java @@ -0,0 +1,246 @@ +package com.bg7yoz.ft8cn.ui; +/** + * FlexRadio选择对话框。 + * @author BGY70Z + * @date 2023-03-20 + */ + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.FlexRadioFactory; +import com.bg7yoz.ft8cn.x6100.X6100Radio; +import com.bg7yoz.ft8cn.x6100.XieguRadioFactory; + +public class SelectXieguRadioDialog extends Dialog { + private static final String TAG="SelectXieguRadioDialog"; + private final MainViewModel mainViewModel; + private RecyclerView xieguRecyclerView; + private ImageView upImage; + private ImageView downImage; + private XieguRadioFactory xieguRadioFactory; + private XieguRadioAdapter xieguRadioAdapter; + private ImageButton connectXieguImageButton; + private EditText inputXieguAddressEdit; + + + + public SelectXieguRadioDialog(@NonNull Context context, MainViewModel mainViewModel){ + super(context); + this.mainViewModel = mainViewModel; + } + + @SuppressLint("NotifyDataSetChanged") + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setContentView(R.layout.select_xiegu_dialog_layout); + xieguRecyclerView=(RecyclerView) findViewById(R.id.xieguRadioListRecyclerView); + xieguRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + upImage=(ImageView) findViewById(R.id.xieguRadioScrollUpImageView); + downImage=(ImageView)findViewById(R.id.xieguRadioScrollDownImageView); + inputXieguAddressEdit=(EditText)findViewById(R.id.inputXieguAddressEdit); + connectXieguImageButton=(ImageButton) findViewById(R.id.connectXieguImageButton); + connectXieguImageButton.setEnabled(false); + connectXieguImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.select_xiegu_device) + ,inputXieguAddressEdit.getText())); + X6100Radio xieguRadio = new X6100Radio(); + xieguRadio.setRig_ip(inputXieguAddressEdit.getText().toString()); + xieguRadio.setModelName("Xiegu Rig"); + ToastMessage.show(xieguRadio.getRig_ip()); + //此处添加连接6100电台的动作 + mainViewModel.connectXieguRadioRig(GeneralVariables.getMainContext(),xieguRadio); + + dismiss(); + } + }); + + inputXieguAddressEdit.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + connectXieguImageButton.setEnabled(!inputXieguAddressEdit.getText().toString().isEmpty()); + } + }); + + + + + xieguRadioAdapter=new XieguRadioAdapter(); + xieguRecyclerView.setAdapter(xieguRadioAdapter); + + xieguRadioFactory = XieguRadioFactory.getInstance(); + + + xieguRadioFactory.setOnXieguRadioEvents(new XieguRadioFactory.OnXieguRadioEvents() { + + @Override + public void onXieguRadioAdded(X6100Radio xieguRadio) { + xieguRecyclerView.post(new Runnable() { + @Override + public void run() { + xieguRadioAdapter.notifyDataSetChanged(); + } + }); + } + + @Override + public void onXieguRadioInvalid(X6100Radio flexRadio) { + xieguRecyclerView.post(new Runnable() { + @Override + public void run() { + xieguRadioAdapter.notifyDataSetChanged(); + } + }); + } + }); + + + + //显示滚动箭头 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + setScrollImageVisible(); + } + }, 1000); + xieguRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + setScrollImageVisible(); + } + }); + } + + + @Override + public void show() { + super.show(); + WindowManager.LayoutParams params = getWindow().getAttributes(); + //设置对话框的大小,以百分比0.6 + int height = getWindow().getWindowManager().getDefaultDisplay().getHeight(); + int width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); +// params.height = (int) (height * 0.6); + if (width > height) { + params.width = (int) (width * 0.6); + params.height = (int) (height * 0.6); + } else { + params.width = (int) (width * 0.8); + params.height = (int) (height * 0.5); + } + getWindow().setAttributes(params); + } + + /** + * 设置界面的上下滚动的图标 + */ + private void setScrollImageVisible() { + + if (xieguRecyclerView.canScrollVertically(1)) { + upImage.setVisibility(View.VISIBLE); + } else { + upImage.setVisibility(View.GONE); + } + + if (xieguRecyclerView.canScrollVertically(-1)) { + downImage.setVisibility(View.VISIBLE); + } else { + downImage.setVisibility(View.GONE); + } + } + + class XieguRadioAdapter extends RecyclerView.Adapter{ + + + @NonNull + @Override + public XieguViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View view = layoutInflater.inflate(R.layout.xiegu_device_list_item, parent, false); + final XieguViewHolder holder = new XieguViewHolder(view); + return holder; + } + + @Override + public void onBindViewHolder(@NonNull XieguViewHolder holder, int position) { + holder.xieguRadio=xieguRadioFactory.xieguRadios.get(position); + holder.xieguRadioIpTextView.setText(holder.xieguRadio.getRig_ip()); + holder.xieguInfoTextView.setText(holder.xieguRadio.getMac()); + holder.xieguRadioNameTextView.setText(holder.xieguRadio.getModelName()); + + holder.xieguRadioListConstraintLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ToastMessage.show(String.format( + GeneralVariables.getStringFromResource(R.string.select_xiegu_device) + ,holder.xieguRadio.getModelName())); + ToastMessage.show(holder.xieguRadio.getRig_ip()); + //此处添加连接6100电台的动作 + mainViewModel.connectXieguRadioRig(GeneralVariables.getMainContext(),holder.xieguRadio); + dismiss(); + } + }); + + } + + @Override + public int getItemCount() { + return xieguRadioFactory.xieguRadios.size(); + } + + + + class XieguViewHolder extends RecyclerView.ViewHolder{ + public X6100Radio xieguRadio; + TextView xieguRadioNameTextView,xieguRadioIpTextView,xieguInfoTextView; + ConstraintLayout xieguRadioListConstraintLayout; + public XieguViewHolder(@NonNull View itemView) { + super(itemView); + xieguRadioNameTextView=itemView.findViewById(R.id.xieguRadioNameTextView); + xieguRadioIpTextView=itemView.findViewById(R.id.xieguRadioIpTextView); + xieguInfoTextView=itemView.findViewById(R.id.xieguInfoTextView); + xieguRadioListConstraintLayout=itemView.findViewById(R.id.xieguRadioListConstraintLayout); + } + + } + } + + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialDataBitsSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialDataBitsSpinnerAdapter.java new file mode 100644 index 0000000..0cf86c3 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialDataBitsSpinnerAdapter.java @@ -0,0 +1,63 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 串口数据位列表界面 + * @author BGY70Z + * @date 2024-01-03 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.R; + + +public class SerialDataBitsSpinnerAdapter extends BaseAdapter { + private final Context mContext; + private final int[] dataBits= {8,7,6,5}; + public SerialDataBitsSpinnerAdapter(Context context) { + mContext=context; + } + + @Override + public int getCount() { + return dataBits.length; + } + + @Override + public Object getItem(int i) { + return dataBits[i]; + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.serial_data_bits_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.serialDataBitsItemTextView); + textView.setText(String.valueOf(dataBits[i])); + } + return view; + } + public int getPosition(int i){ + for (int j = 0; j < dataBits.length; j++) { + if (dataBits[j]==i){ + return j; + } + } + return 0; + } + public int getValue(int position){ + return dataBits[position]; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialParityBitsSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialParityBitsSpinnerAdapter.java new file mode 100644 index 0000000..441e628 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialParityBitsSpinnerAdapter.java @@ -0,0 +1,71 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 串口校验位列表界面 + * @author BGY70Z + * @date 2024-01-03 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; + + +public class SerialParityBitsSpinnerAdapter extends BaseAdapter { + private final Context mContext; + private final int[] parityBits= {0,1,2,3,4}; + private final String[] parityStr={GeneralVariables.getStringFromResource(R.string.serial_parity_none) + ,GeneralVariables.getStringFromResource(R.string.serial_parity_odd) + ,GeneralVariables.getStringFromResource(R.string.serial_parity_even) + ,GeneralVariables.getStringFromResource(R.string.serial_parity_mark) + ,GeneralVariables.getStringFromResource(R.string.serial_parity_space) + }; + public SerialParityBitsSpinnerAdapter(Context context) { + mContext=context; + } + + @Override + public int getCount() { + return parityBits.length; + } + + @Override + public Object getItem(int i) { + return parityBits[i]; + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.serial_parity_bits_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.serialParityBitsItemTextView); + //textView.setText(String.valueOf(parityBits[i])); + textView.setText(parityStr[i]); + } + return view; + } + public int getPosition(int i){ + for (int j = 0; j < parityBits.length; j++) { + if (parityBits[j]==i){ + return j; + } + } + return 2; + } + public int getValue(int position){ + return parityBits[position]; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialStopBitsSpinnerAdapter.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialStopBitsSpinnerAdapter.java new file mode 100644 index 0000000..7fa2279 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SerialStopBitsSpinnerAdapter.java @@ -0,0 +1,64 @@ +package com.bg7yoz.ft8cn.ui; +/** + * 串口停止位列表界面 + * @author BGY70Z + * @date 2024-01-03 + */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.bg7yoz.ft8cn.R; + + +public class SerialStopBitsSpinnerAdapter extends BaseAdapter { + private final Context mContext; + private final int[] stopBits= {1,3,2}; + private final String[] stopBitsStr= {"1","1.5","2"}; + public SerialStopBitsSpinnerAdapter(Context context) { + mContext=context; + } + + @Override + public int getCount() { + return stopBits.length; + } + + @Override + public Object getItem(int i) { + return stopBits[i]; + } + + @Override + public long getItemId(int i) { + return i; + } + + @SuppressLint({"ViewHolder", "InflateParams"}) + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + LayoutInflater _LayoutInflater=LayoutInflater.from(mContext); + view=_LayoutInflater.inflate(R.layout.serial_stop_bits_spinner_item, null); + if (view!=null){ + TextView textView=(TextView)view.findViewById(R.id.serialStopBitsItemTextView); + textView.setText(stopBitsStr[i]); + } + return view; + } + public int getPosition(int i){ + for (int j = 0; j < stopBits.length; j++) { + if (stopBits[j]==i){ + return j; + } + } + return 0; + } + public int getValue(int position){ + return stopBits[position]; + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java index d3c0e60..039bd1a 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/SetVolumeDialog.java @@ -9,6 +9,7 @@ import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; import android.os.Bundle; +import android.util.Log; import android.view.WindowManager; import android.widget.SeekBar; import android.widget.TextView; @@ -60,6 +61,12 @@ public class SetVolumeDialog extends Dialog { GeneralVariables.volumePercent=i/100f; GeneralVariables.mutableVolumePercent.postValue(i/100f); mainViewModel.databaseOpr.writeConfig("volumeValue",String.valueOf(i),null); + if (mainViewModel.baseRig!=null){ + if (mainViewModel.baseRig.getConnector()!=null) { + mainViewModel.baseRig.getConnector().setRFVolume(i); + Log.e(TAG,String.format("set volume:%d",i)); + } + } } @Override diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java index 543c03b..2e0d013 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/VolumeProgress.java @@ -62,6 +62,9 @@ public class VolumeProgress extends View { init(); invalidate(); } + public float getPercent(){ + return mPercent; + } public void reDraw(){ setPercent(mPercent); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java index 22244d3..5f1cfd2 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java @@ -12,9 +12,11 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; import android.util.TypedValue; @@ -24,6 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.GeneralVariables; import com.bg7yoz.ft8cn.timer.UtcTimer; import java.util.ArrayList; @@ -44,7 +47,8 @@ public class WaterfallView extends View { private Paint touchPaint = new Paint(); private final Paint fontPaint = new Paint(); private final Paint messagePaint = new Paint(); - private final Paint messagePaintBack = new Paint();//消息背景 + private final Paint textLinePaint = new Paint(); +// private final Paint messagePaintBack = new Paint();//消息背景 private final Paint utcPaint = new Paint(); Paint linearPaint = new Paint(); private final Paint utcPainBack = new Paint(); @@ -105,6 +109,14 @@ public class WaterfallView extends View { fontPaint.setDither(true); fontPaint.setTextAlign(Paint.Align.LEFT); + + textLinePaint.setColor(0xff00ffff); + textLinePaint.setAntiAlias(true); + textLinePaint.setDither(true); + textLinePaint.setStrokeWidth(2); + textLinePaint.setStyle(Paint.Style.FILL_AND_STROKE); + + // messagePaint = new Paint(); messagePaint.setTextSize(dpToPixel(11)); messagePaint.setColor(0xff00ffff); @@ -113,16 +125,17 @@ public class WaterfallView extends View { messagePaint.setStrokeWidth(0); messagePaint.setStyle(Paint.Style.FILL_AND_STROKE); messagePaint.setTextAlign(Paint.Align.CENTER); + messagePaint.setShadowLayer(10,5,5,Color.BLACK); //messagePaintBack = new Paint(); - messagePaintBack.setTextSize(dpToPixel(11)); - messagePaintBack.setColor(0xff000000);//背景不透明 - messagePaintBack.setAntiAlias(true); - messagePaintBack.setDither(true); - messagePaintBack.setStrokeWidth(dpToPixel(3)); - messagePaintBack.setFakeBoldText(true); - messagePaintBack.setStyle(Paint.Style.FILL_AND_STROKE); - messagePaintBack.setTextAlign(Paint.Align.CENTER); +// messagePaintBack.setTextSize(dpToPixel(11)); +// messagePaintBack.setColor(0xff000000);//背景不透明 +// messagePaintBack.setAntiAlias(true); +// messagePaintBack.setDither(true); +// messagePaintBack.setStrokeWidth(dpToPixel(3)); +// messagePaintBack.setFakeBoldText(true); +// messagePaintBack.setStyle(Paint.Style.FILL_AND_STROKE); +// messagePaintBack.setTextAlign(Paint.Align.CENTER); //utcPaint = new Paint(); utcPaint.setTextSize(dpToPixel(10)); @@ -133,6 +146,7 @@ public class WaterfallView extends View { utcPaint.setStyle(Paint.Style.FILL_AND_STROKE); utcPaint.setTextAlign(Paint.Align.LEFT); + //utcPainBack = new Paint(); utcPainBack.setTextSize(dpToPixel(10)); utcPainBack.setColor(0xff000000);//背景不透明 @@ -242,27 +256,39 @@ public class WaterfallView extends View { //消息有3种:普通、CQ、有我 if (drawMessage && messages != null) { drawMessage = false;//只画一遍 - fontPaint.setTextAlign(Paint.Align.LEFT); + //fontPaint.setTextAlign(Paint.Align.LEFT); + //fontPaint.setStrikeThruText(true); for (Ft8Message msg : messages) { if (msg.inMyCall()) {//与我有关 messagePaint.setColor(0xffffb2b2); + textLinePaint.setColor(0xffffb2b2); } else if (msg.checkIsCQ()) {//CQ messagePaint.setColor(0xffeeee00); + textLinePaint.setColor(0xffeeee00); } else { messagePaint.setColor(0xff00ffff); + textLinePaint.setColor(0xff00ffff); } + Path path = new Path(); path.moveTo(msg.freq_hz * freq_width, pathStart); path.lineTo(msg.freq_hz * freq_width, pathEnd); - _canvas.drawTextOnPath(msg.getMessageText(true), path - , 0, 0, messagePaintBack);//消息背景 + +// _canvas.drawTextOnPath(msg.getMessageText(true), path +// , 0, 0, messagePaintBack);//消息背景 _canvas.drawTextOnPath(msg.getMessageText(true), path , 0, 0, messagePaint);//消息 - + if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//画删除线 + float text_len = messagePaint.measureText(msg.getMessageText(true)); + float text_start = ((pathEnd- pathStart)-text_len)/2; + float text_high =dpToPixel(4);//messagePaint.getFontSpacing()/2; + _canvas.drawLine(msg.freq_hz * freq_width + text_high , text_start + , msg.freq_hz * freq_width + text_high, text_len + text_start, textLinePaint); + } } } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/XieguInfoFragment.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/XieguInfoFragment.java new file mode 100644 index 0000000..73890ba --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/ui/XieguInfoFragment.java @@ -0,0 +1,188 @@ +package com.bg7yoz.ft8cn.ui; + +import android.os.Bundle; + +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.MainViewModel; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.connector.X6100Connector; +import com.bg7yoz.ft8cn.databinding.FragmentXieguInfoBinding; +import com.bg7yoz.ft8cn.x6100.X6100Meters; +import com.bg7yoz.ft8cn.x6100.X6100Radio; + + +public class XieguInfoFragment extends Fragment { + private static final String TAG ="XieguInfoFragment"; + private MainViewModel mainViewModel; + private X6100Connector connector; + private FragmentXieguInfoBinding binding; + private X6100Radio xieguRadio; + private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener=null; + + + + public XieguInfoFragment() { + // Required empty public constructor + } + + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mainViewModel = MainViewModel.getInstance(this); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding=FragmentXieguInfoBinding.inflate(getLayoutInflater()); + if (mainViewModel.baseRig != null){ + connector = (X6100Connector) mainViewModel.baseRig.getConnector(); + xieguRadio =connector.getXieguRadio(); + binding.xieguInfoTextView.setText(xieguRadio.getModelName()); + + //ping 值 + xieguRadio.mutablePing.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Long aLong) { + binding.xieguPingValueTextView.setText( + String.format(GeneralVariables.getStringFromResource(R.string.xiegu_ping_value) + ,aLong)); + } + }); + //丢包数量 + xieguRadio.mutableLossPackets.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Integer integer) { + binding.xieguLossValueTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.x6100_packet_lost) + ,integer)); + } + }); + mainViewModel.baseRig.mutableFrequency.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Long aLong) { + binding.xieguFreqValueTextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.xiegu_band_str) + ,GeneralVariables.getBandString())); + } + }); + xieguRadio.mutableMeters.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(X6100Meters x6100Meters) { + binding.xieguMetersValueTextView.setText(x6100Meters.toString()); + binding.xieguSMeterRulerView.setValue(x6100Meters.sMeter); + binding.xieguSwrMeterRulerView.setValue(x6100Meters.swr); + binding.xieguPowerMeterRulerView.setValue(x6100Meters.power); + binding.xieguAlcMeterRulerView.setValue(x6100Meters.alc); + binding.xieguVoltMeterRulerView.setValue(x6100Meters.volt); + } + }); + + } + binding.xieguSMeterRulerView.initVal(-130f, -30f, 10f, 9, 3); + binding.xieguSMeterRulerView.initLabels("S.Po", "dBm" + , new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + , new String[]{"20", "40", ""}); + binding.xieguSwrMeterRulerView.initVal(1f, 3f, 8f, 4, 4); + binding.xieguSwrMeterRulerView.initLabels("SWR", "" + , new String[]{"1", "1.5", "2", "2.5", "3"} + , new String[]{"", "", "", "∞"}); + + binding.xieguAlcMeterRulerView.initVal(0f, 100f, 100f, 6, 4); + binding.xieguAlcMeterRulerView.initLabels("ALC", "" + , new String[]{"0", "10", "20","30","40","50","60"} + , new String[]{"70", "80", "90","100"}); + + binding.xieguPowerMeterRulerView.initVal(-0f, 5f, 10f, 5, 5); + binding.xieguPowerMeterRulerView.initLabels("PWR", "W" + , new String[]{"0", "1", "2", "3", "4", "5"} + , new String[]{"6", "7", "8", "9", "10"}); + binding.xieguVoltMeterRulerView.initVal(-0f, 14f, 16f, 8, 2); + binding.xieguVoltMeterRulerView.initLabels("Volt", "V" + , new String[]{"0", "2", "4", "6", "8", "10","12","13","14"} + , new String[]{ "15", "16"}); + + binding.xieguMaxPwrProgress.setValueColor(getContext().getColor(R.color.power_progress_value)); + binding.xieguMaxPwrProgress.setRadarColor(getContext().getColor(R.color.power_progress_radar_value)); + binding.xieguMaxPwrProgress.setAlarmValue(0.99f); + + + connector.mutableMaxTxPower.observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Float aFloat) { + binding.xieguMaxPwrProgress.setPercent( aFloat.floatValue() / 10f); + binding.xiegumaxPowerSeekBar.setOnSeekBarChangeListener(null); + binding.xiegumaxPowerSeekBar.setProgress(Math.round(aFloat.floatValue()*10)); + binding.xiegumaxPowerSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener); + binding.xieguMaxTxPowertextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), Math.round(aFloat.floatValue()))); + } + }); + + + + binding.xieguSMeterRulerView.setValue(-60f); + binding.xieguSwrMeterRulerView.setValue(1.1f); + binding.xieguAlcMeterRulerView.setValue(30f); + binding.xieguPowerMeterRulerView.setValue(8f); + binding.xieguVoltMeterRulerView.setValue(12.5f); + + + + binding.xieguAtuOnButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + xieguRadio.commandAtuOn(); + } + }); + binding.xieguAtuOffButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + xieguRadio.commandAtuOff(); + } + }); + binding.xieguStartAtuButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + xieguRadio.commandAtuStart(); + } + }); + + onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + binding.xieguMaxPwrProgress.setPercent(i * 1.0f / 100); + + connector.setMaxTXPower(i/10); + binding.xieguMaxTxPowertextView.setText(String.format( + GeneralVariables.getStringFromResource(R.string.flex_max_tx_power), i/10)); + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }; + binding.xiegumaxPowerSeekBar.setOnSeekBarChangeListener( onSeekBarChangeListener); + + + + return binding.getRoot(); + } +} \ No newline at end of file diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/FT8Resample.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/FT8Resample.java index 5764443..4af629d 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/FT8Resample.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/FT8Resample.java @@ -12,18 +12,19 @@ public class FT8Resample { } public static native short[] get16Resample16(short[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); + public static native float[] get32Resample16(short[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); public static native short[] get16Resample32(float[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); public static native float[] get32Resample32(float[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); public static native byte[] get8Resample16(short[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); public static native byte[] get8Resample32(float[] inputData, int inputRate - , int outputRate); + , int outputRate,int channels); } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java index 0755f3d..47f762a 100644 --- a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/wave/HamRecorder.java @@ -209,22 +209,24 @@ public class HamRecorder { onHamRecord = new OnHamRecord() { @Override public void OnReceiveData(float[] data, int size) { - for (int i = 0; (i < size) && (dataCount < voiceData.length); i++) { - voiceData[dataCount] = data[i];//把录音缓冲区的数据搬运到本监听器中来 - dataCount++; - } - if (dataCount >= (voiceData.length)) {//当数据量达到所需要的。发起回调。 -// new Thread(new Runnable() {//以新的线程运行,防止占用过多的录音时间。 -// @Override -// public void run() { - onGetVoiceDataDone.onGetDone(voiceData); -// } -// }).start(); + int remainingSize = size+dataCount-voiceData.length;//如果大于0,就是剩余的数据量, + for (int i = 0; (i < size) && (dataCount < voiceData.length); i++) { + voiceData[dataCount] = data[i];//把录音缓冲区的数据搬运到本监听器中来 + dataCount++; + } + + if (dataCount >= (voiceData.length)) {//当数据量达到所需要的。发起回调。 + onGetVoiceDataDone.onGetDone(voiceData); if (afterDoneRemove) {//如果是一次性的获取数据,则在录音对象中的监听列表中删除此监听回调。 hamRecorder.deleteVoiceDataMonitor(voiceDataMonitor); } else { dataCount = 0;//如果是循环录音,则复位计数器。 + if (remainingSize>0) {//把剩余的数据补发到后续事件上 + float[] remainingData = new float[remainingSize]; + System.arraycopy(data, size - remainingSize, remainingData, 0, remainingSize); + OnReceiveData(remainingData,remainingSize); + } } } } diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Meters.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Meters.java new file mode 100644 index 0000000..ed4c9fd --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Meters.java @@ -0,0 +1,91 @@ +package com.bg7yoz.ft8cn.x6100; + +import android.annotation.SuppressLint; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.VITA; + +public class X6100Meters { + private final String TAG="X6100Meters"; + public float sMeter; + public float power; + public float swr; + public float alc; + public float volt; + public float max_power; + public short tx_volume; + public short af_level;//电台的音量 + + public X6100Meters() { + + } + public synchronized void update(byte[] meterData){ + for (int i = 0; i < meterData.length/4; i++) { + short index = VITA.readShortDataBigEnd(meterData,i*4); + short value= VITA.readShortDataBigEnd(meterData,i*4+2); + setValues(index,value); + } + } + + private void setValues(short index,short value){ + switch (index){ + case 0://sMeter + sMeter = (100.0f/255.0f)*value -130; + //Log.e(TAG,String.format("s.Meter:%.1f",sMeter)); + break; + case 1://power + power = (25/255f)*value*10; + //Log.e(TAG,String.format("power:%.1f",power)); + break; + case 2://swr + swr= value *1.0f; + //Log.e(TAG,String.format("swr:%d",value)); + //ToastMessage.show(String.format("swr:%d",value)); + break; + case 3://alc + alc = (100.0f/255f)*value; + //ToastMessage.show(String.format("alc:%d",value)); + //Log.e(TAG,String.format("alc:%.1f",alc)); + break; + case 4: + volt = value *1.0f; + //Log.e(TAG,String.format("volt:%.1f",volt)); + break; + case 5: + max_power = value /25.5f; + //GeneralVariables.flexMaxRfPower=(int)max_power; + //Log.e(TAG,String.format("max_power:%.1f,val:%d",max_power,value)); + break; + case 6: + tx_volume = value; + //Log.e(TAG,String.format("tx_volume:%d",tx_volume)); + break; + case 7: + af_level = value; + //Log.e(TAG,String.format("tx_volume:%d",tx_volume)); + break; + default: + } + } + + @SuppressLint("DefaultLocale") + @NonNull + @Override + public String toString() { + //return String.format("S.Meter: %.1f dBm\nSWR: %s\nALC: %.1f\nVolt: %.1fV\nTX power: %.1f W\nMax tx power: %.1f\nTX volume:%d%%" + return String.format(GeneralVariables.getStringFromResource(R.string.xiegu_meter_info) + ,sMeter + ,swr > 8 ? "∞" : String.format("%.1f",swr) + ,alc + ,volt + ,power + ,max_power + ,tx_volume + ); + //"信号强度: %.1f dBm\n驻波: %s\nALC: %.1f\n电压: %.1fV\n发射功率: %.1f W\n最大发射功率: %.1f\n发射音量:%d%%" + } +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Radio.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Radio.java new file mode 100644 index 0000000..cea5d1d --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/X6100Radio.java @@ -0,0 +1,975 @@ +package com.bg7yoz.ft8cn.x6100; + +import static com.bg7yoz.ft8cn.flex.VITA.XIEGU_METER_CLASS_ID; +import static com.bg7yoz.ft8cn.flex.VITA.XIEGU_METER_Stream_Id; +import static com.bg7yoz.ft8cn.flex.VITA.XIEGU_PING_CLASS_ID; +import static com.bg7yoz.ft8cn.flex.VITA.XIEGU_PING_Stream_Id; +import static com.bg7yoz.ft8cn.flex.VITA.byteToStr; +import static com.bg7yoz.ft8cn.flex.VITA.readShortData; +import static com.bg7yoz.ft8cn.x6100.X6100Radio.XieguResponseStyle.RESPONSE; + +import android.annotation.SuppressLint; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioTrack; +import android.util.Log; + +import androidx.lifecycle.MutableLiveData; + +import com.bg7yoz.ft8cn.GeneralVariables; +import com.bg7yoz.ft8cn.R; +import com.bg7yoz.ft8cn.flex.FlexCommand; +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.FlexResponseStyle; +import com.bg7yoz.ft8cn.flex.RadioTcpClient; +import com.bg7yoz.ft8cn.flex.RadioUdpClient; +import com.bg7yoz.ft8cn.flex.VITA; +import com.bg7yoz.ft8cn.flex.VitaPacketType; +import com.bg7yoz.ft8cn.flex.VitaTSF; +import com.bg7yoz.ft8cn.flex.VitaTSI; +import com.bg7yoz.ft8cn.rigs.BaseRig; +import com.bg7yoz.ft8cn.ui.ToastMessage; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Timer; +import java.util.TimerTask; + +public class X6100Radio { + public enum XieguCommand { + UNKNOW,//未知指令 + AUDIO,//音频指令 + STREAM,//数据流指令 + SUB,//订阅仪表 + UNSUB,//取消订阅仪表 + A91,//ft8符号 + ATU,//自动天调 + TUNE,//设置频率 + MODE,//操作模式 + PTT,//PTT操作 + SET//设置操作 + } + + public enum XieguResponseStyle { + STATUS,//状态信息,S+HANDLE + RESPONSE,//命令的响应,R+客户端命令序列号 + HANDLE,//电台给定的句柄,H+句柄(32位的16进制表示) + VERSION,//版本号,V+版本号 + COMMAND,//发送命令,C+序列号|命令 + UNKNOW//未知的回复类型 + } + + private static final String TAG = "X6100Radio"; + private static int lossCount = 0; + private static int currentCount = -1; + + private String modelName;//电台型号 + private String version;//电台版本号 + private String rig_ip;//电台的IP + private String mac;//mac地址 + public boolean isPttOn = false; + private int control_port = 7002;//电台的控制端口 + private int stream_port = 7003;//电台端流数据的端口 + private int discovery_port = 7001;//发现协议的端口 + private long lastSeen;//最后一次消息的时间 + private boolean isAvailable = true;//电台是不是有效 + private final StringBuilder buffer = new StringBuilder();//指令的缓存 + private final RadioTcpClient tcpClient = new RadioTcpClient(); + private RadioUdpClient streamClient; + private int commandSeq = 1;//指令的序列 + private XieguCommand xieguCommand; + private int handle = 0; + private String commandStr; + private int frames = 768;//每个周期的帧数 + private int period = 64;//每个周期的时长,毫秒 + + + //************************事件处理接口******************************* + private OnReceiveDataListener onReceiveDataListener;//当前接收到的数据事件 + private OnTcpConnectStatus onTcpConnectStatus;//当TCP连接状态变化的事件 + private OnReceiveStreamData onReceiveStreamData;//当接收到流数据后的处理事件 + private OnCommandListener onCommandListener;//触发命令事件 + private OnStatusListener onStatusListener;//触发状态事件 + //***************************************************************** + private AudioTrack audioTrack = null; + + + ///******************用于仪表信息显示************* + public MutableLiveData mutablePing = new MutableLiveData<>();//ping值 + public MutableLiveData mutableLossPackets = new MutableLiveData<>();//丢失的包数量 + public MutableLiveData mutableMeters = new MutableLiveData<>(); + private X6100Meters meters = new X6100Meters(); + + private boolean swrAlert = false; + private boolean alcAlert = false; + + + private Timer pingTimer = new Timer(); + + private TimerTask pingTask() { + return new TimerTask() { + @Override + public void run() { + + try { + if (!streamClient.isActivated() || !isConnect()) { + pingTimer.cancel(); + pingTimer.purge(); + pingTimer = null; + return; + } + VITA vita = new VITA(VitaPacketType.EXT_DATA_WITH_STREAM + , VitaTSI.TSI_OTHER + , VitaTSF.TSF_REALTIME + , 0 + , XIEGU_PING_Stream_Id + , XIEGU_PING_CLASS_ID); + + vita.packetCount = 0; + vita.packetSize = 7; + vita.integerTimestamp = 0;//0是发送包,1是接收包 + vita.fracTimeStamp = System.currentTimeMillis(); + streamClient.sendData(vita.pingDataToVita(), rig_ip, stream_port); + + } catch (Exception e) { + Log.e(TAG, "ping timer error:" + e.getMessage()); + } + } + }; + } + + /** + * 更新最后看到的时间 + */ + public void updateLastSeen() { + this.lastSeen = System.currentTimeMillis(); + } + + public X6100Radio() { + updateLastSeen(); + } + + public X6100Radio(String s, String ip) { + mutableLossPackets.postValue(0); + update(s, ip); + } + + public void update(String discoverStr, String ip) { + Log.d(TAG, discoverStr); + rig_ip = ip; + + String[] paras = discoverStr.replace("\0", " ").split(" "); + version = getParameterStr(paras, "ft8cn_server_version"); + modelName = getParameterStr(paras, "model"); + mac = getParameterStr(paras, "mac"); + control_port = getParameterInt(paras, "control_port"); + stream_port = getParameterInt(paras, "stream_port"); + discovery_port = getParameterInt(paras, "discovery_port"); + + updateLastSeen(); + } + + /** + * 到参数列表中找指定的字符类型参数 + * + * @param parameters 参数列表 + * @param prefix 参数名前缀 + * @return 参数 + */ + private String getParameterStr(String[] parameters, String prefix) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { + return parameters[i].substring(prefix.length() + 1); + } + } + //如果没找到,返回空字符串 + return ""; + } + + /** + * 到参数列表中找指定的int类型参数 + * + * @param parameters 参数列表 + * @param prefix 参数名前缀 + * @return 参数 + */ + private int getParameterInt(String[] parameters, String prefix) { + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].toLowerCase().startsWith(prefix.toLowerCase() + "=")) { + try { + return Integer.parseInt(parameters[i].substring(prefix.length() + 1)); + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "getParameterInt exception: " + e.getMessage()); + return 0; + } + } + } + //如果没找到,返回0 + return 0; + + } + + /** + * 检查是不是 刚刚 离线,离线条件:5秒内没有收到电台的广播数据包 + * + * @return 是否 + */ + public boolean isInvalidNow() { + if (isAvailable) {//如果标记在线,而大于5秒的时间没有收到数据包,就视为刚刚离线。 + isAvailable = System.currentTimeMillis() - lastSeen < 1000 * 5;//小于5秒,就视为在线 + return !isAvailable; + } else {//如果已经标记不在线了,就不是刚刚离线的。 + return false; + } + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getVersion() { + return version; + } + + public String getRig_ip() { + return rig_ip; + } + + public void setRig_ip(String rig_ip) { + this.rig_ip = rig_ip; + } + + public String getMac() { + return mac; + } + + public boolean isEqual(String madAddress) { + return this.mac.equalsIgnoreCase(madAddress); + } + + /** + * 连接到控制电台 + */ + public void connect() { + this.connect(this.rig_ip, this.control_port); + } + + /** + * 连接控制到电台,TCP + * + * @param ip 地址 + * @param port 端口 + */ + public void connect(String ip, int port) { + if (tcpClient.isConnect()) { + tcpClient.disconnect(); + } + //Tcp连接触发的事件 + tcpClient.setOnDataReceiveListener(new RadioTcpClient.OnDataReceiveListener() { + @Override + public void onConnectSuccess() { + if (onTcpConnectStatus != null) { + onTcpConnectStatus.onConnectSuccess(tcpClient); + } + } + + @Override + public void onConnectFail() { + if (onTcpConnectStatus != null) { + onTcpConnectStatus.onConnectFail(tcpClient); + } + } + + @Override + public void onDataReceive(byte[] buffer) { + if (onReceiveDataListener != null) {//此处把数据传递给XieGu6100NetRig + onReceiveDataListener.onDataReceive(buffer); + } + onReceiveData(buffer); + } + + @Override + public void onConnectionClosed() { + tcpClient.disconnect(); + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tcp_connect_closed)); + if (onTcpConnectStatus != null){ + onTcpConnectStatus.onConnectionClosed(tcpClient); + } + } + }); + clearBufferData();//清除一下缓存的指令数据 + tcpClient.connect(ip, port);//连接TCP + + } + + /** + * 关闭接收数据流的端口 + */ + public synchronized void closeStreamPort() { + if (streamClient != null) { + if (streamClient.isActivated()) { + try { + streamClient.setActivated(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + streamClient = null; + } + + @SuppressLint("DefaultLocale") + public synchronized void commandAtuOn() { + sendCommand(XieguCommand.ATU, "atu on"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandAtuOff() { + sendCommand(XieguCommand.ATU, "atu off"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandAtuStart() { + sendCommand(XieguCommand.ATU, "atu start"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandOpenStream() { + sendCommand(XieguCommand.STREAM, String.format("stream on %d", stream_port)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSendA91(byte[] a91, float vol, float freq) { + sendCommand(XieguCommand.A91, String.format("a91 %.2f %.0f %s", vol, freq, BaseRig.byteToStr(a91))); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandGetAudioInfo() { + sendCommand(XieguCommand.AUDIO, "audio get all"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandGetStreamInfo() { + sendCommand(XieguCommand.STREAM, "stream get"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSubAllMeter() { + sendCommand(XieguCommand.SUB, "sub all"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSubGetMeter() { + sendCommand(XieguCommand.SUB, "sub get"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandUnubMeter() { + sendCommand(XieguCommand.UNSUB, "unsub"); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandTuneFreq(long freq) { + sendCommand(XieguCommand.TUNE, String.format("tune %d", freq)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetMode(String mode, int filter) { + sendCommand(XieguCommand.TUNE, String.format("mode %s %d", mode, filter)); + } + + @SuppressLint("DefaultLocale") + public synchronized void commandSetTxPower(int power) { + sendCommand(XieguCommand.SET, String.format("set tx %d", power)); + } + @SuppressLint("DefaultLocale") + public synchronized void commandSetTxVol(int volume) { + sendCommand(XieguCommand.SET, String.format("set tx_vol %d", volume)); + } + + private void showAlert() { + Log.e(TAG, String.format("ALC:%f", meters.alc)); + if (meters.swr >= 5) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + + + if (meters.alc > 50 || meters.alc < 20) { + if (!alcAlert) { + alcAlert = true; + if (meters.alc > 50) { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } else { + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_low_alert)); + } + } + } else { + alcAlert = false; + } + } + + /** + * 打开接收数据流的端口 + */ + public void openStreamPort() { + if (streamClient != null) { + if (streamClient.isActivated()) { + try { + streamClient.setActivated(false); + } catch (Exception e) { + ToastMessage.show(e.getMessage()); + e.printStackTrace(); + } + + } + } + + + RadioUdpClient.OnUdpEvents onUdpEvents = new RadioUdpClient.OnUdpEvents() { + @SuppressLint("DefaultLocale") + @Override + public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { + VITA vita = new VITA(data); + if (vita.classId64 == VITA.XIEGU_AUDIO_CLASS_ID) {//音频数据 + + //判断数据包丢失情况 + int temp = lossCount; + if (currentCount <= -1) { + currentCount = vita.packetCount; + } + if (currentCount > vita.packetCount) { + lossCount = lossCount + vita.packetCount + 16 - currentCount - 1; + } else if (currentCount < vita.packetCount) { + lossCount = lossCount + vita.packetCount - currentCount - 1; + } + currentCount = vita.packetCount; + if (lossCount > temp) { + Log.e(TAG, String.format("丢包数量:%d", lossCount)); + + for (int i = 0; i < (lossCount - temp); i++) { + Log.d(TAG, String.format("补发数据,%d,size:%d", i, vita.payload.length)); + sendReceivedAudio(vita.payload);//把当前的数据补发给录音对象 + } + mutableLossPackets.postValue(lossCount); + } + sendReceivedAudio(vita.payload);//把音频发给录音对象 + playReceiveAudio(vita.payload);//发送当前的音频数据 + } else if (vita.classId64 == XIEGU_PING_CLASS_ID//ping数据 + && vita.streamId == XIEGU_PING_Stream_Id + && vita.integerTimestamp == 1) {//ping的回包 + mutablePing.postValue(System.currentTimeMillis() - vita.fracTimeStamp); + } else if (vita.classId64 == XIEGU_METER_CLASS_ID//仪表数据 + && vita.streamId == XIEGU_METER_Stream_Id) { + new Thread(new Runnable() { + @Override + public void run() { + meters.update(vita.payload); + mutableMeters.postValue(meters); + if (isPttOn) { + showAlert(); + } else { + alcAlert = false; + swrAlert = false; + } + if (onReceiveStreamData != null){ + onReceiveStreamData.onReceiveMeter(meters); + } + } + }).start(); + + } + + + } + }; + + //此处要确定stream的udp端口 + streamClient = new RadioUdpClient(stream_port); + streamClient.setOnUdpEvents(onUdpEvents); + try { + streamClient.setActivated(true); + pingTimer.schedule(pingTask(), 1000, 1000);//启动ping计时器 + } catch (SocketException e) { + ToastMessage.show(e.getMessage()); + e.printStackTrace(); + Log.d(TAG, "streamClient: " + e.getMessage()); + } + + + } + + /** + * 当接收到音频数据后,发送给录音对象的操作 + * + * @param data 音频数据 + */ + private void sendReceivedAudio(byte[] data) { + if (onReceiveStreamData != null) { + onReceiveStreamData.onReceiveAudio(data); + } + } + + /** + * 当接收到音频数据时的处理 + * + * @param data 音频数据 + */ + private void playReceiveAudio(byte[] data) { + if (audioTrack != null) {//如果音频播放已经打开,就写音频流数据 + audioTrack.write(data, 0, data.length, AudioTrack.WRITE_NON_BLOCKING); + } + } + + /** + * 断开与电台的连接 + */ + public synchronized void disConnect() { + if (tcpClient.isConnect()) { + tcpClient.disconnect(); + } + if (pingTimer != null) { + pingTimer.cancel(); + pingTimer.purge(); + pingTimer = null; + } + } + + /** + * 电台是否连接 + * + * @return 是否 + */ + public boolean isConnect() { + return tcpClient.isConnect(); + } + + /** + * 关闭音频 + */ + public void closeAudio() { + if (audioTrack != null) { + audioTrack.stop(); + audioTrack = null; + } + } + + + + /** + * 当接收到数据时触发的事件,此处是TCP连接得到的数据 + * + * @param data 数据 + */ + private void onReceiveData(byte[] data) { + + if (data.length > 4) {//判断是不是老式的icom指令 + if ((data[0] == (byte) 0xfe) && (data[1] == (byte) 0xfe) + && ((data[2] == (byte) 0xe0) || data[3] == (byte) 0xe0)) { + clearBufferData(); + return; + } + } + String s = new String(data); + if (!s.contains("\n")) {//不包含换行符,说明命令行没有接受完。 + buffer.append(s); + } else {//说明已经有命令行了。可能不止一个哦。在此部分要触发OnReceiveLine + String[] commands = s.split("\n"); + if (commands.length > 0) {//把收到数据的第一行,追加到之前接收的命令数据上 + buffer.append(commands[0]); + } + + //先把缓存中的数据触发出来 + doReceiveLineEvent(buffer.toString()); + clearBufferData(); + //从第二行开始触发,最后一行不触发,最后一行要看是不是换行结尾 + for (int i = 1; i < commands.length - 1; i++) { + doReceiveLineEvent(commands[i]); + } + + if (commands.length > 1) {//当数据是多行的时候,最后一行的处理 + if (s.endsWith("\n")) {//如果是以换行结尾,或者缓冲区没满(接收完全了),就触发事件 + doReceiveLineEvent(commands[commands.length - 1]); + } else {//如果不是以换行结尾,说明指令没有接收完全 + buffer.append(commands[commands.length - 1]); + } + } + } + } + + + /** + * 当接收到数据行时,触发的事件。可以触发两种事件: + * 1.行数据事件onReceiveLineListener; + * 2.命令事件onCommandListener。 + *

+ * //* @param line 数据行 + */ + private void doReceiveLineEvent(String line) { + + XieguResponse response = new XieguResponse(line); + //更新一下句柄 + switch (response.responseStyle) { + case VERSION: + this.version = response.head.substring(1); + break; + case HANDLE: + this.handle = Integer.parseInt(response.head.substring(1), 16); + break; + case RESPONSE: + if (XieguCommand.AUDIO == response.xieguCommand) {//是音频指令回复的信息 + setAudioInfo(response.resultContent); + } + if (onCommandListener != null) { + onCommandListener.onResponse(response); + } + break; + case STATUS: + + if (response.resultCode == 0) {//说明是电台状态变化了 + String status[] = response.resultContent.split(" "); + for (int i = 0; i < status.length; i++) {//找出ptt的状体,设置ptt + if (status[i].startsWith("ptt")) {//判断PTT + String temp[] = status[i].split("="); + isPttOn = temp[1].equalsIgnoreCase("on"); + } + + if (status[i].startsWith("play_volume")) {//判断PTT + String temp[] = status[i].split("="); + float vol = Integer.parseInt(temp[1].trim())*1.0f/100f; + GeneralVariables.volumePercent = vol; + GeneralVariables.mutableVolumePercent.postValue(vol); + } + } + } + + if (onStatusListener != null) { + onStatusListener.onStatus(response); + } + break; + } + + } + + /** + * 获取电台的音频信息 + * + * @param result 返回信息 + */ + private void setAudioInfo(String result) { + String[] keys = result.split(" "); + for (int i = 0; i < keys.length; i++) { + String[] val = keys[i].split("="); + if (val[0].equalsIgnoreCase("period")) period = Integer.parseInt(val[1]) / 1000; + if (val[0].equalsIgnoreCase("frames")) frames = Integer.parseInt(val[1]); + } + Log.d(TAG, String.format("set audio para:frames=%d,period=%d", frames, period)); + } + + public synchronized void sendData(byte[] data) { + tcpClient.sendByte(data); + } + + /** + * 制作命令,命令序号规则:后3位是命令的种类,序号除1000,是命令的真正序号 + * + * @param command 命令的种类 + * @param cmdContent 命令的具体内容 + */ + @SuppressLint("DefaultLocale") + public void sendCommand(XieguCommand command, String cmdContent) { + if (tcpClient.isConnect()) { + commandSeq++; + xieguCommand = command; + commandStr = String.format("C%05d%03d|%s\n", commandSeq, command.ordinal() + , cmdContent); + tcpClient.sendByte(commandStr.getBytes()); + Log.d(TAG, "sendCommand: " + commandStr); + } + } + + public synchronized void commandPTTOnOff(boolean on) { + if (on) { + sendCommand(XieguCommand.PTT, "ptt on"); + } else { + sendCommand(XieguCommand.PTT, "ptt off"); + } + } + + /** + * 发射的采样率为12000采样率,单声道,16位 + * + * @param data 音频 + */ + public void sendWaveData(float[] data) { + Log.d(TAG, String.format("send wav data,len:%d....", data.length)); + short[] temp = new short[data.length]; + //传递过来的音频是LPCM,32 float,12000Hz + //x6100的音频格式是LPCM 16 Int,12000Hz + //要做一下浮点到16位int的转换 + for (int i = 0; i < data.length; i++) { + float x = data[i]; + if (x > 1.0) + x = 1.0f; + else if (x < -1.0) + x = -1.0f; + temp[i] = (short) (x * 32767.0); + } + short[] payload = new short[frames]; + + VITA vita = new VITA(VitaPacketType.EXT_DATA_WITH_STREAM + , VitaTSI.TSI_OTHER + , VitaTSF.TSF_SAMPLE_COUNT + , 0 + , 0x84000001 + , 0x584945475500A1L); + + vita.packetCount = 0; + vita.integerTimestamp = 0; + vita.fracTimeStamp = payload.length * 2L; + + + try { + int count = 0; + int a = 0; + while (count < temp.length) { + long now = System.currentTimeMillis();//获取当前时间 + Arrays.fill(payload, (short) 0);//数组清零 + + if (!isPttOn) break; + if (data.length - count > frames) { + System.arraycopy(temp, count, payload, 0, frames); + count = count + frames; + } else { + System.arraycopy(temp, count, payload, 0, temp.length - count); + count = temp.length; + } + streamClient.sendData(vita.audioShortDataToVita(vita.packetCount, payload), rig_ip, stream_port); + while (isPttOn) { + if (System.currentTimeMillis() - now >= period) {//64毫秒一个周期 + break; + } + } + a++; + } + + + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + /** + * 清空缓存数据 + */ + private void clearBufferData() { + buffer.setLength(0); + } + + /** + * 打开音频,流方式。当收到音频流的时候,播放数据 + */ + public void openAudio() { + AudioAttributes attributes = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build(); + AudioFormat myFormat = new AudioFormat.Builder().setSampleRate(12000) + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); + int mySession = 0; + audioTrack = new AudioTrack(attributes, myFormat + //, 12000 * 4, AudioTrack.MODE_STREAM + , 768 * 2 * 4, AudioTrack.MODE_STREAM//x6100一个声音周期是64毫秒,共计768*2个字节 + , mySession); + audioTrack.play(); + } + + public OnTcpConnectStatus getOnTcpConnectStatus() { + return onTcpConnectStatus; + } + + public void setOnTcpConnectStatus(OnTcpConnectStatus onTcpConnectStatus) { + this.onTcpConnectStatus = onTcpConnectStatus; + } + + public OnReceiveStreamData getOnReceiveStreamData() { + return onReceiveStreamData; + } + + public void setOnReceiveStreamData(OnReceiveStreamData onReceiveStreamData) { + this.onReceiveStreamData = onReceiveStreamData; + } + + public OnStatusListener getOnStatusListener() { + return onStatusListener; + } + + public void setOnStatusListener(OnStatusListener onStatusListener) { + this.onStatusListener = onStatusListener; + } + + public OnCommandListener getOnCommandListener() { + return onCommandListener; + } + + public void setOnCommandListener(OnCommandListener onCommandListener) { + this.onCommandListener = onCommandListener; + } + + public OnReceiveDataListener getOnReceiveDataListener() { + return onReceiveDataListener; + } + + public void setOnReceiveDataListener(OnReceiveDataListener onReceiveDataListener) { + this.onReceiveDataListener = onReceiveDataListener; + } + + +//**************各种接口********************** + + /** + * 当TCP接收到数据 + */ + public interface OnReceiveDataListener { + void onDataReceive(byte[] data); + } + + /** + * 当TCP连接状态变化 + */ + public interface OnTcpConnectStatus { + void onConnectSuccess(RadioTcpClient tcpClient); + + void onConnectFail(RadioTcpClient tcpClient); + void onConnectionClosed(RadioTcpClient tcpClient); + } + + /** + * 当接收到流数据时的事件 + */ + public interface OnReceiveStreamData { + void onReceiveAudio(byte[] data);//音频数据 + + void onReceiveIQ(byte[] data);//IQ数据 + + void onReceiveFFT(VITA vita);//频谱数据 + + void onReceiveMeter(X6100Meters meters);//仪表数据 + + void onReceiveUnKnow(byte[] data);//未知数据 + } + + /** + * 当接收到指令回复 + */ + public interface OnCommandListener { + void onResponse(XieguResponse response); + } + + public interface OnStatusListener { + void onStatus(XieguResponse response); + } + //******************************************* + + + /** + * 电台TCP回复数据的基础类 + */ + public static class XieguResponse { + private static final String TAG = "XieguResponse"; + public XieguResponseStyle responseStyle; + public String head;//消息头 + public int resultCode;//消息代码 + public String resultContent;//扩展消息,有的返回消息分为3段,取第3段消息 + public String rawData;//原始数据 + public int seq_number;//32位int,指令序号 + + public XieguCommand xieguCommand = XieguCommand.UNKNOW; + + + public XieguResponse(String line) { + rawData = line; + char header; + if (line.length() > 0) { + header = line.toUpperCase().charAt(0); + } else { + header = 0; + } + switch (header) { + case 'S': + responseStyle = XieguResponseStyle.STATUS; + getHeadAndContent(line, "\\|");//获取指令的头、值、内容 + + break; + case 'R': + responseStyle = RESPONSE; + getHeadAndContent(line, "\\|"); + try { + seq_number = Integer.parseInt(head.substring(1));//解析指令序号 + xieguCommand = XieguCommand.values()[seq_number % 1000]; + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "XieguResponse parseInt seq_number exception: " + e.getMessage()); + } + break; + case 'H': + responseStyle = XieguResponseStyle.HANDLE; + head = line; + resultContent = line; + Log.d(TAG, "XieguResponse: handle:" + line.substring(1)); + + break; + case 'V': + responseStyle = XieguResponseStyle.VERSION; + head = line; + resultContent = line; + break; + + case 0: + default: + responseStyle = XieguResponseStyle.UNKNOW; + break; + } + } + + + /** + * 分割消息的头和内容,并分别负值给head和content + * + * @param line 消息 + * @param split 分隔符 + */ + private void getHeadAndContent(String line, String split) { + String[] temp = line.split(split); + if (temp.length > 1) { + head = temp[0]; + + resultCode = Integer.parseInt(temp[1]); + + } else { + head = ""; + } + + if (temp.length > 2) { + resultContent = temp[2]; + } else { + resultContent = ""; + } + } + } + +} diff --git a/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/XieguRadioFactory.java b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/XieguRadioFactory.java new file mode 100644 index 0000000..fd3a950 --- /dev/null +++ b/ft8cn/app/src/main/java/com/bg7yoz/ft8cn/x6100/XieguRadioFactory.java @@ -0,0 +1,187 @@ +package com.bg7yoz.ft8cn.x6100; + +/** + * XieguRadioFactory 当前发现的所有收音机。 + * RadioFactory: 实例化这个类来创建一个 Radio Factory,通过它发现在相同局域网内地协谷电台。 + * + * 通过Upd协议,在7001端口的广播数据中获取vita协议数据,并解析电台信息。 + * + * @author BGY70Z + * @date 2023-11-29 + */ + + +import android.util.Log; + +import com.bg7yoz.ft8cn.flex.FlexRadio; +import com.bg7yoz.ft8cn.flex.RadioUdpClient; +import com.bg7yoz.ft8cn.flex.VITA; + + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; + + + + +public class XieguRadioFactory { + private static final String TAG="XieguRadioFactory"; + private static final int XIEGU_DISCOVERY_PORT =7001; + private static XieguRadioFactory instance=null; + private final RadioUdpClient broadcastClient ; + private OnXieguRadioEvents onXieguRadioEvents; + + private Timer refreshTimer=null; + private TimerTask refreshTask=null; + + public ArrayList xieguRadios=new ArrayList<>(); + + /** + * 获取电台列表实例 + * @return 电台列表实例 + */ + public static XieguRadioFactory getInstance(){ + if (instance==null){ + instance= new XieguRadioFactory(); + } + instance.xieguRadios.clear(); + return instance; + } + + + + public XieguRadioFactory() { + broadcastClient = new RadioUdpClient(XIEGU_DISCOVERY_PORT); + + + broadcastClient.setOnUdpEvents(new RadioUdpClient.OnUdpEvents() { + @Override + public void OnReceiveData(DatagramSocket socket, DatagramPacket packet, byte[] data) { + VITA vita = new VITA(data); + + if (vita.isAvailable//如果数据包有效 + &&vita.classId64 == VITA.XIEGU_Discovery_Class_Id + &&vita.streamId==VITA.XIEGU_Discovery_Stream_Id){ + InetAddress address = packet.getAddress();//获取ip地址 + updateXieguRadioList(new String(vita.payload),address.getHostAddress()); + } + } + }); + try { + broadcastClient.setActivated(true); + } catch (SocketException e) { + e.printStackTrace(); + Log.e(TAG, "XieguRadioFactory: "+e.getMessage()); + } + + } + + + public void startRefreshTimer(){ + if (refreshTimer==null) { + refreshTask=new TimerTask() { + @Override + public void run() { + Log.e(TAG, "run: 检查离线" ); + checkOffLineRadios(); + } + }; + refreshTimer=new Timer(); + refreshTimer.schedule(refreshTask, 1000, 1000);//检查电台列表中的电台是否在线(每一秒) + } + } + public void cancelRefreshTimer(){ + if (refreshTimer!=null){ + refreshTimer.cancel(); + refreshTimer=null; + refreshTask.cancel(); + refreshTask=null; + } + } + + /** + * 从数据中查找电台的MAC地址 + * @param s 数据 + * @return mac地址 + */ + private String getMacAddress(String s){ + String[] strings=s.split(" "); + for (int i = 0; i + + + + + + + + + + + \ No newline at end of file diff --git a/ft8cn/app/src/main/res/drawable/xiegulogo.png b/ft8cn/app/src/main/res/drawable/xiegulogo.png new file mode 100644 index 0000000..1ab44b7 Binary files /dev/null and b/ft8cn/app/src/main/res/drawable/xiegulogo.png differ diff --git a/ft8cn/app/src/main/res/drawable/xiegulogo32.png b/ft8cn/app/src/main/res/drawable/xiegulogo32.png new file mode 100644 index 0000000..22ff4d0 Binary files /dev/null and b/ft8cn/app/src/main/res/drawable/xiegulogo32.png differ diff --git a/ft8cn/app/src/main/res/layout-land/fragment_xiegu_info.xml b/ft8cn/app/src/main/res/layout-land/fragment_xiegu_info.xml new file mode 100644 index 0000000..31cd412 --- /dev/null +++ b/ft8cn/app/src/main/res/layout-land/fragment_xiegu_info.xml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +