diff --git a/FT8CN软件设计初衷及使用说明0.88版.pdf b/FT8CN软件设计初衷及使用说明0.88版.pdf deleted file mode 100644 index 639f0e6..0000000 Binary files a/FT8CN软件设计初衷及使用说明0.88版.pdf and /dev/null differ diff --git a/ft8CN/app/.gitignore b/ft8CN/app/.gitignore old mode 100755 new mode 100644 diff --git a/ft8CN/app/build.gradle b/ft8CN/app/build.gradle old mode 100755 new mode 100644 index 1d5b54f..94c2598 --- a/ft8CN/app/build.gradle +++ b/ft8CN/app/build.gradle @@ -5,7 +5,7 @@ plugins { id 'com.android.application' } -def currentTime = getCurrentTime(); +def currentTime = getCurrentTime() static def getCurrentTime() { DateFormat df = new SimpleDateFormat("yyyy-MM-dd") @@ -22,7 +22,7 @@ android { minSdk 23 targetSdk 33 versionCode 1 - versionName '0.87' + versionName '0.89' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dataBinding{ @@ -35,14 +35,6 @@ android { } } - ndk{ - abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' - } - sourceSets{ - main{ - jniLibs.srcDirs=['libs'] - } - } signingConfig signingConfigs.debug } diff --git a/ft8CN/app/libs/arm64-v8a/libft8cn.so b/ft8CN/app/libs/arm64-v8a/libft8cn.so old mode 100755 new mode 100644 index 2a4dd10..6304cb8 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 old mode 100755 new mode 100644 index 1c8d16a..3683d6e 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 old mode 100755 new mode 100644 index 0b053bb..c054f3b 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 old mode 100755 new mode 100644 index aec1734..75d7d58 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/proguard-rules.pro b/ft8CN/app/proguard-rules.pro old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/androidTest/java/com/bg7yoz/ft8cn/ExampleInstrumentedTest.java b/ft8CN/app/src/androidTest/java/com/bg7yoz/ft8cn/ExampleInstrumentedTest.java deleted file mode 100755 index a49eee3..0000000 --- a/ft8CN/app/src/androidTest/java/com/bg7yoz/ft8cn/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.bg7yoz.ft8cn; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.bg7yoz.ft8cn", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/ft8CN/app/src/libs/MPAndroidChartv_3.1.0.jar b/ft8CN/app/src/libs/MPAndroidChartv_3.1.0.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/nanohttpd-2.2.0-javadoc.jar b/ft8CN/app/src/libs/nanohttpd-2.2.0-javadoc.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/nanohttpd-2.2.0-sources.jar b/ft8CN/app/src/libs/nanohttpd-2.2.0-sources.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/nanohttpd-2.2.0.jar b/ft8CN/app/src/libs/nanohttpd-2.2.0.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/osmdroid-android-6.1.14-javadoc.jar b/ft8CN/app/src/libs/osmdroid-android-6.1.14-javadoc.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/osmdroid-android-6.1.14-sources.jar b/ft8CN/app/src/libs/osmdroid-android-6.1.14-sources.jar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/libs/osmdroid-android-6.1.14.aar b/ft8CN/app/src/libs/osmdroid-android-6.1.14.aar old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/AndroidManifest.xml b/ft8CN/app/src/main/AndroidManifest.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/audio_output_help.txt b/ft8CN/app/src/main/assets/audio_output_help.txt new file mode 100644 index 0000000..d893a0f --- /dev/null +++ b/ft8CN/app/src/main/assets/audio_output_help.txt @@ -0,0 +1,5 @@ +关于音频输出设置 + +采样位深:也称采样精度,FT8CN只有16位整型和32位浮点可选。采样位数是表示声音强度量化后的精细程度,它的数值越大,波动幅度的分辨率也就越高,所发出声音的能力越强。 + +采样率:也称取样频率, 指每秒钟取得声音样本的次数。采样频率越高,声音的质量也就越好,但占的资源也多。 \ No newline at end of file diff --git a/ft8CN/app/src/main/assets/audio_output_help_en.txt b/ft8CN/app/src/main/assets/audio_output_help_en.txt new file mode 100644 index 0000000..aa73dc4 --- /dev/null +++ b/ft8CN/app/src/main/assets/audio_output_help_en.txt @@ -0,0 +1,5 @@ +Audio Output Setting + +Bit depth: Choose 16-bit int or 32-bit float. Bit depth dictates the number of possible amplitude values of audio sample. A higher bit depth will produce a higher resolution audio. + +Sample rate: Sample rate refers to the number of samples that are present within one second of digital audio. Higher sample rate provides more accurate audio waveform, but consume more system resources. \ No newline at end of file diff --git a/ft8CN/app/src/main/assets/auto_follow_help.txt b/ft8CN/app/src/main/assets/auto_follow_help.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/auto_follow_help_en.txt b/ft8CN/app/src/main/assets/auto_follow_help_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/bands.txt b/ft8CN/app/src/main/assets/bands.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/callsign.txt b/ft8CN/app/src/main/assets/callsign.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/callsign_en.txt b/ft8CN/app/src/main/assets/callsign_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/civ_help.txt b/ft8CN/app/src/main/assets/civ_help.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/civ_help_en.txt b/ft8CN/app/src/main/assets/civ_help_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/clear_cache_data.txt b/ft8CN/app/src/main/assets/clear_cache_data.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/clear_cache_data_en.txt b/ft8CN/app/src/main/assets/clear_cache_data_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/connectMode.txt b/ft8CN/app/src/main/assets/connectMode.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/connectMode_en.txt b/ft8CN/app/src/main/assets/connectMode_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/controlMode.txt b/ft8CN/app/src/main/assets/controlMode.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/controlMode_en.txt b/ft8CN/app/src/main/assets/controlMode_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/country_en2cn.dat b/ft8CN/app/src/main/assets/country_en2cn.dat old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/country_en2cn.old b/ft8CN/app/src/main/assets/country_en2cn.old old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/country_en2hk.dat b/ft8CN/app/src/main/assets/country_en2hk.dat old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/cqzone.json b/ft8CN/app/src/main/assets/cqzone.json old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/cty.dat b/ft8CN/app/src/main/assets/cty.dat old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/decode_help.txt b/ft8CN/app/src/main/assets/decode_help.txt new file mode 100644 index 0000000..566715e --- /dev/null +++ b/ft8CN/app/src/main/assets/decode_help.txt @@ -0,0 +1,4 @@ +关于解码模式 +快速解码:沿用原来FT8CN的解码,对接收到的音频做一次性的解码。 +多次解码:在快速解码的基础上,再做多次的解码,增加迭代的次数,并尝试解码频率有叠加的信号。 +注:多次解码增加的运算量,总的解码时间会变长,会缩短设备的续航时间。 \ No newline at end of file diff --git a/ft8CN/app/src/main/assets/decode_help_en.txt b/ft8CN/app/src/main/assets/decode_help_en.txt new file mode 100644 index 0000000..dbb179f --- /dev/null +++ b/ft8CN/app/src/main/assets/decode_help_en.txt @@ -0,0 +1,4 @@ +Decode mode: +Fast decode: Default mode, one decoding pass on received audio. +Deep decode: Recursive decoding that process received audio in multiple passes . +* Note: Deep decoding requires longer decoding time and more battery consumption. \ No newline at end of file diff --git a/ft8CN/app/src/main/assets/dxcc_list.json b/ft8CN/app/src/main/assets/dxcc_list.json old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/excludeCallsign.txt b/ft8CN/app/src/main/assets/excludeCallsign.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/excludeCallsign_en.txt b/ft8CN/app/src/main/assets/excludeCallsign_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/frequency.txt b/ft8CN/app/src/main/assets/frequency.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/frequency_en.txt b/ft8CN/app/src/main/assets/frequency_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/ituzone.json b/ft8CN/app/src/main/assets/ituzone.json old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/launch_supervision_en.txt b/ft8CN/app/src/main/assets/launch_supervision_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/launch_supervision_help.txt b/ft8CN/app/src/main/assets/launch_supervision_help.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/maidenhead.txt b/ft8CN/app/src/main/assets/maidenhead.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/maidenhead_en.txt b/ft8CN/app/src/main/assets/maidenhead_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/nightUSGS4Layer.sqlite b/ft8CN/app/src/main/assets/nightUSGS4Layer.sqlite old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/no_response_help.txt b/ft8CN/app/src/main/assets/no_response_help.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/no_response_help_en.txt b/ft8CN/app/src/main/assets/no_response_help_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/operationBand.txt b/ft8CN/app/src/main/assets/operationBand.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/operationBand_en.txt b/ft8CN/app/src/main/assets/operationBand_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/pttdelay.txt b/ft8CN/app/src/main/assets/pttdelay.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/pttdelay_en.txt b/ft8CN/app/src/main/assets/pttdelay_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/readme.txt b/ft8CN/app/src/main/assets/readme.txt index 4cad789..453c5b1 100644 --- a/ft8CN/app/src/main/assets/readme.txt +++ b/ft8CN/app/src/main/assets/readme.txt @@ -14,6 +14,17 @@ Please click "FAQ" if you have good suggestions or questions . BG7YOZ 2022-07-01 + + 2023-07-08(0.89) + 1.增加多重解码功能,在多重解码模式下,提高解码深度,尝试解码叠加的信号。 + 2.解决导入ADI后通联过的分区没有及时更新的问题。 + 3.解决iCom电台在网络模式下,发射音频会出现破音的情况。 + 4.解决在某些情况下RR73卡死的问题。 + 5.提高解码稳定性。 + 6.改正解码消息的comment字段有时不正确显示的问题。 + 7.修改导出日志的提示。 + + 2023-05-02(0.88 Patch 2) 1.增加音频输出设置(位深、采样率)。 2.增加日志可以按条件查询并导出。 diff --git a/ft8CN/app/src/main/assets/rig_model_help.txt b/ft8CN/app/src/main/assets/rig_model_help.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/rig_model_help_en.txt b/ft8CN/app/src/main/assets/rig_model_help_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/rigaddress.txt b/ft8CN/app/src/main/assets/rigaddress.txt old mode 100755 new mode 100644 index ca6387f..ab56380 --- a/ft8CN/app/src/main/assets/rigaddress.txt +++ b/ft8CN/app/src/main/assets/rigaddress.txt @@ -41,7 +41,7 @@ YAESU FT-DX Other series,00,4800,3 KENWOOD(建伍) TK-90,00,9600,5 KENWOOD(建伍) TS-480,00,9600,7 KENWOOD(建伍) TS-590,00,9600,7 -KENWOOD(建伍) TS-2000,00,9600,7 +KENWOOD(建伍) TS-2000,00,9600,14 KN990,00,38400,1 Elecraft K3S\K3\KX3\KX2,00,38400,10 mcHF-QRP sdr,00,4800,1 diff --git a/ft8CN/app/src/main/assets/swlMode.txt b/ft8CN/app/src/main/assets/swlMode.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/swlMode_en.txt b/ft8CN/app/src/main/assets/swlMode_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/timeoffset.txt b/ft8CN/app/src/main/assets/timeoffset.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/timeoffset_en.txt b/ft8CN/app/src/main/assets/timeoffset_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/transDelay.txt b/ft8CN/app/src/main/assets/transDelay.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/assets/transDelay_en.txt b/ft8CN/app/src/main/assets/transDelay_en.txt old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/cpp/CMakeLists.txt b/ft8CN/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..fae89e1 --- /dev/null +++ b/ft8CN/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,69 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.18.1) + +# Declares and names the project. + +project("ft8cn") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + ft8cn + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + + generate_ft8.cpp + ft8Listener.cpp + ft8Spectrum.cpp + fft/kiss_fftr.c + fft/kiss_fft.c + + ft8/decode.c + ft8/crc.c + ft8/constants.c + ft8/ldpc.c + ft8/unpack.c + ft8/text.c + ft8/hash22.c + ft8/encode.c + ft8/pack.c + monitor_opr.c + ft8Decoder.c + ft8Encoder.c + spectrum_data.c + ) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log ) + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + ft8cn + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) diff --git a/ft8CN/app/src/main/cpp/common/comm_str.h b/ft8CN/app/src/main/cpp/common/comm_str.h new file mode 100644 index 0000000..185aca8 --- /dev/null +++ b/ft8CN/app/src/main/cpp/common/comm_str.h @@ -0,0 +1,11 @@ +// +// Created by jmsmf on 2022/4/22. +// + +#ifndef NATIVEC_COMM_STR_H +#define NATIVEC_COMM_STR_H + +#endif //NATIVEC_COMM_STR_H +const char ERROR_FILE_NAME_IS_NULL[] = "error:wav_path is null!!!"; +const char ERROR_OPEN_FILE_FAILED[] ="Error : the content of the file does not meet the requirements."; +const char INFO_DECODE_OK[] ="decode OK!!!"; \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/common/common.h b/ft8CN/app/src/main/cpp/common/common.h new file mode 100644 index 0000000..fc2a2f9 --- /dev/null +++ b/ft8CN/app/src/main/cpp/common/common.h @@ -0,0 +1,3 @@ +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif diff --git a/ft8CN/app/src/main/cpp/common/debug.h b/ft8CN/app/src/main/cpp/common/debug.h new file mode 100644 index 0000000..4dec693 --- /dev/null +++ b/ft8CN/app/src/main/cpp/common/debug.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include + +#define LOG_DEBUG 0 +#define LOG_INFO 1 +#define LOG_WARN 2 +#define LOG_ERROR 3 +#define LOG_FATAL 4 +#define LOG_LEVEL LOG_DEBUG +//#define LOG_LEVEL LOG_INFO + +//#define LOG(level, ...) if (level >= LOG_LEVEL) fprintf(stderr, __VA_ARGS__) +#define TAG "FT8_DECODER" // 这个是自定义的LOG的标识 +#define LOG(level, ...) if (level >= LOG_LEVEL) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) +#define LOG_PRINTF(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) + +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/common/wave.c b/ft8CN/app/src/main/cpp/common/wave.c new file mode 100644 index 0000000..6f08ac6 --- /dev/null +++ b/ft8CN/app/src/main/cpp/common/wave.c @@ -0,0 +1,131 @@ +#include "wave.h" + +#include +#include +#include + +#include + +// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +void save_wav(const float* signal, int num_samples, int sample_rate, const char* path) +{ + char subChunk1ID[4] = { 'f', 'm', 't', ' ' }; + uint32_t subChunk1Size = 16; // 16 for PCM + uint16_t audioFormat = 1; // PCM = 1 + uint16_t numChannels = 1; + uint16_t bitsPerSample = 16; + uint32_t sampleRate = sample_rate; + uint16_t blockAlign = numChannels * bitsPerSample / 8; + uint32_t byteRate = sampleRate * blockAlign; + + char subChunk2ID[4] = { 'd', 'a', 't', 'a' }; + uint32_t subChunk2Size = num_samples * blockAlign; + + char chunkID[4] = { 'R', 'I', 'F', 'F' }; + uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size); + char format[4] = { 'W', 'A', 'V', 'E' }; + + int16_t* raw_data = (int16_t*)malloc(num_samples * blockAlign); + for (int i = 0; i < num_samples; i++) + { + float x = signal[i]; + if (x > 1.0) + x = 1.0; + else if (x < -1.0) + x = -1.0; + raw_data[i] = (int)(x * 32767.0); + } + + FILE* f = fopen(path, "wb"); + + // NOTE: works only on little-endian architecture + fwrite(chunkID, sizeof(chunkID), 1, f); + fwrite(&chunkSize, sizeof(chunkSize), 1, f); + fwrite(format, sizeof(format), 1, f); + + fwrite(subChunk1ID, sizeof(subChunk1ID), 1, f); + fwrite(&subChunk1Size, sizeof(subChunk1Size), 1, f); + fwrite(&audioFormat, sizeof(audioFormat), 1, f); + fwrite(&numChannels, sizeof(numChannels), 1, f); + fwrite(&sampleRate, sizeof(sampleRate), 1, f); + fwrite(&byteRate, sizeof(byteRate), 1, f); + fwrite(&blockAlign, sizeof(blockAlign), 1, f); + fwrite(&bitsPerSample, sizeof(bitsPerSample), 1, f); + + fwrite(subChunk2ID, sizeof(subChunk2ID), 1, f); + fwrite(&subChunk2Size, sizeof(subChunk2Size), 1, f); + + fwrite(raw_data, blockAlign, num_samples, f); + + fclose(f); + + free(raw_data); +} + +// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path) +{ + char subChunk1ID[4]; // = {'f', 'm', 't', ' '}; + uint32_t subChunk1Size; // = 16; // 16 for PCM + uint16_t audioFormat; // = 1; // PCM = 1 + uint16_t numChannels; // = 1; + uint16_t bitsPerSample; // = 16; + uint32_t sampleRate; + uint16_t blockAlign; // = numChannels * bitsPerSample / 8; + uint32_t byteRate; // = sampleRate * blockAlign; + + char subChunk2ID[4]; // = {'d', 'a', 't', 'a'}; + uint32_t subChunk2Size; // = num_samples * blockAlign; + + char chunkID[4]; // = {'R', 'I', 'F', 'F'}; + uint32_t chunkSize; // = 4 + (8 + subChunk1Size) + (8 + subChunk2Size); + char format[4]; // = {'W', 'A', 'V', 'E'}; + + FILE* f = fopen(path, "rb"); + if (f==NULL){ + return -1; + } + + // NOTE: works only on little-endian architecture + fread((void*)chunkID, sizeof(chunkID), 1, f); + fread((void*)&chunkSize, sizeof(chunkSize), 1, f); + fread((void*)format, sizeof(format), 1, f); + + fread((void*)subChunk1ID, sizeof(subChunk1ID), 1, f); + fread((void*)&subChunk1Size, sizeof(subChunk1Size), 1, f); + if (subChunk1Size != 16) + return -1; + + fread((void*)&audioFormat, sizeof(audioFormat), 1, f); + fread((void*)&numChannels, sizeof(numChannels), 1, f); + fread((void*)&sampleRate, sizeof(sampleRate), 1, f); + fread((void*)&byteRate, sizeof(byteRate), 1, f); + fread((void*)&blockAlign, sizeof(blockAlign), 1, f); + fread((void*)&bitsPerSample, sizeof(bitsPerSample), 1, f); + + if (audioFormat != 1 || numChannels != 1 || bitsPerSample != 16) + return -1; + + fread((void*)subChunk2ID, sizeof(subChunk2ID), 1, f); + fread((void*)&subChunk2Size, sizeof(subChunk2Size), 1, f); + + if (subChunk2Size / blockAlign > *num_samples) + return -2; + + *num_samples = subChunk2Size / blockAlign; + *sample_rate = sampleRate; + + int16_t* raw_data = (int16_t*)malloc(*num_samples * blockAlign); + + fread((void*)raw_data, blockAlign, *num_samples, f); + for (int i = 0; i < *num_samples; i++) + { + signal[i] = raw_data[i] / 32768.0f; + } + + free(raw_data); + + fclose(f); + + return 0; +} diff --git a/ft8CN/app/src/main/cpp/common/wave.h b/ft8CN/app/src/main/cpp/common/wave.h new file mode 100644 index 0000000..def1296 --- /dev/null +++ b/ft8CN/app/src/main/cpp/common/wave.h @@ -0,0 +1,10 @@ +#ifndef _INCLUDE_WAVE_H_ +#define _INCLUDE_WAVE_H_ + +// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +void save_wav(const float* signal, int num_samples, int sample_rate, const char* path); + +// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path); + +#endif // _INCLUDE_WAVE_H_ diff --git a/ft8CN/app/src/main/cpp/fft/_kiss_fft_guts.h b/ft8CN/app/src/main/cpp/fft/_kiss_fft_guts.h new file mode 100644 index 0000000..bf5d53c --- /dev/null +++ b/ft8CN/app/src/main/cpp/fft/_kiss_fft_guts.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" +#include + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#if (FIXED_POINT==32) +# define FRACBITS 31 +# define SAMPPROD int64_t +#define SAMP_MAX 2147483647 +#else +# define FRACBITS 15 +# define SAMPPROD int32_t +#define SAMP_MAX 32767 +#endif + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) + + +#ifdef KISS_FFT_USE_ALLOCA +// define this to allow use of alloca instead of malloc for temporary buffers +// Temporary buffers are used in two case: +// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 +// 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. +#include +#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) +#define KISS_FFT_TMP_FREE(ptr) +#else +#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) +#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) +#endif diff --git a/ft8CN/app/src/main/cpp/fft/kiss_fft.c b/ft8CN/app/src/main/cpp/fft/kiss_fft.c new file mode 100644 index 0000000..af2f695 --- /dev/null +++ b/ft8CN/app/src/main/cpp/fft/kiss_fft.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + + +#include "_kiss_fft_guts.h" +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1 = st->twiddles; + kiss_fft_cpx t; + Fout2 = Fout + m; + do{ + C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2); + + C_MUL (t, *Fout2 , *tw1); + tw1 += fstride; + C_SUB( *Fout2 , *Fout , t ); + C_ADDTO( *Fout , t ); + ++Fout2; + ++Fout; + }while (--m); +} + +static void kf_bfly4( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + const size_t m + ) +{ + kiss_fft_cpx *tw1,*tw2,*tw3; + kiss_fft_cpx scratch[6]; + size_t k=m; + const size_t m2=2*m; + const size_t m3=3*m; + + + tw3 = tw2 = tw1 = st->twiddles; + + do { + C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4); + + C_MUL(scratch[0],Fout[m] , *tw1 ); + C_MUL(scratch[1],Fout[m2] , *tw2 ); + C_MUL(scratch[2],Fout[m3] , *tw3 ); + + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + if(st->inverse) { + Fout[m].r = scratch[5].r - scratch[4].i; + Fout[m].i = scratch[5].i + scratch[4].r; + Fout[m3].r = scratch[5].r + scratch[4].i; + Fout[m3].i = scratch[5].i - scratch[4].r; + }else{ + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + } + ++Fout; + }while(--k); +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; ur += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + int Norig = st->nfft; + + kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p); + + for ( u=0; u=Norig) twidx-=Norig; + C_MUL(t,scratch[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + KISS_FFT_TMP_FREE(scratch); +} + +static +void kf_work( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + kiss_fft_cpx * Fout_beg=Fout; + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + const kiss_fft_cpx * Fout_end = Fout + p*m; + +#ifdef _OPENMP + // use openmp extensions at the + // top-level (not recursive) + if (fstride==1 && p<=5) + { + int k; + + // execute the p different work units in different threads +# pragma omp parallel for + for (k=0;k floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} + +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; + + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } + + kf_factor(nfft,st->factors); + } + return st; +} + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft); + KISS_FFT_TMP_FREE(tmpbuf); + }else{ + kf_work( fout, fin, 1,in_stride, st->factors,st ); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + + +void kiss_fft_cleanup(void) +{ + // nothing needed any more +} + +int kiss_fft_next_fast_size(int n) +{ + while(1) { + int m=n; + while ( (m%2) == 0 ) m/=2; + while ( (m%3) == 0 ) m/=3; + while ( (m%5) == 0 ) m/=5; + if (m<=1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +} diff --git a/ft8CN/app/src/main/cpp/fft/kiss_fft.h b/ft8CN/app/src/main/cpp/fft/kiss_fft.h new file mode 100644 index 0000000..45c3a6a --- /dev/null +++ b/ft8CN/app/src/main/cpp/fft/kiss_fft.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) +#define KISS_FFT_FREE _mm_free +#else +#define KISS_FFT_MALLOC malloc +#define KISS_FFT_FREE free +#endif + + +#ifdef FIXED_POINT +#include +# if (FIXED_POINT == 32) +# define kiss_fft_scalar int32_t +# else +# define kiss_fft_scalar int16_t +# endif +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free KISS_FFT_FREE + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +/* + * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) + */ +int kiss_fft_next_fast_size(int n); + +/* for real ffts, we need an even size */ +#define kiss_fftr_next_fast_size_real(n) \ + (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ft8CN/app/src/main/cpp/fft/kiss_fftr.c b/ft8CN/app/src/main/cpp/fft/kiss_fftr.c new file mode 100644 index 0000000..8102132 --- /dev/null +++ b/ft8CN/app/src/main/cpp/fft/kiss_fftr.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + void * pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize = 0, memneeded; + + if (nfft & 1) { + fprintf(stderr,"Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + + for (i = 0; i < nfft/2; ++i) { + double phase = + -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); + if (inverse_fft) + phase *= -1; + kf_cexp (st->super_twiddles+i,phase); + } + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + fprintf(stderr,"kiss fft usage error: improper alloc\n"); + exit(1); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k-1]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + fprintf (stderr, "kiss fft usage error: improper alloc\n"); + exit (1); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + C_FIXDIV(st->tmpbuf[0],2); + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 ); + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k-1]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} diff --git a/ft8CN/app/src/main/cpp/fft/kiss_fftr.h b/ft8CN/app/src/main/cpp/fft/kiss_fftr.h new file mode 100644 index 0000000..588948d --- /dev/null +++ b/ft8CN/app/src/main/cpp/fft/kiss_fftr.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free KISS_FFT_FREE + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ft8CN/app/src/main/cpp/ft8/constants.c b/ft8CN/app/src/main/cpp/ft8/constants.c new file mode 100644 index 0000000..f98fc50 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/constants.c @@ -0,0 +1,394 @@ +#include "constants.h" + + +//科斯塔阵列 +const uint8_t kFT8CostasPattern[7] = { 3, 1, 4, 0, 6, 5, 2 }; +const uint8_t kFT4CostasPattern[4][4] = { + { 0, 1, 3, 2 }, + { 1, 0, 2, 3 }, + { 2, 3, 1, 0 }, + { 3, 2, 0, 1 } +}; + +// Gray code map (FTx bits -> channel symbols) +//格雷码 +const uint8_t kFT8GrayMap[8] = { 0, 1, 3, 2, 5, 6, 4, 7 }; +const uint8_t kFT4GrayMap[4] = { 0, 1, 3, 2 }; + +const uint8_t kFT4XORSequence[10] = { + 0x4Au, // 01001010 + 0x5Eu, // 01011110 + 0x89u, // 10001001 + 0xB4u, // 10110100 + 0xB0u, // 10110000 + 0x8Au, // 10001010 + 0x79u, // 01111001 + 0x55u, // 01010101 + 0xBEu, // 10111110 + 0x28u, // 00101 [000] +}; + +// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +const uint8_t kFTXLDPCGenerator[FTX_LDPC_M][FTX_LDPC_K_BYTES] = { + { 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 }, + { 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 }, + { 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 }, + { 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 }, + { 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 }, + { 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 }, + { 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 }, + { 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 }, + { 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 }, + { 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 }, + { 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 }, + { 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 }, + { 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 }, + { 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 }, + { 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 }, + { 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 }, + { 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 }, + { 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 }, + { 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 }, + { 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 }, + { 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 }, + { 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 }, + { 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 }, + { 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 }, + { 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 }, + { 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 }, + { 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 }, + { 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 }, + { 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 }, + { 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 }, + { 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 }, + { 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 }, + { 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 }, + { 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 }, + { 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 }, + { 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 }, + { 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 }, + { 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 }, + { 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 }, + { 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 }, + { 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 }, + { 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 }, + { 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 }, + { 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 }, + { 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 }, + { 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 }, + { 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 }, + { 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 }, + { 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 }, + { 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 }, + { 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 }, + { 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 }, + { 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 }, + { 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 }, + { 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 }, + { 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 }, + { 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 }, + { 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 }, + { 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 }, + { 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 }, + { 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 }, + { 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 }, + { 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 }, + { 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 }, + { 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 }, + { 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 }, + { 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 }, + { 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 }, + { 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 }, + { 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 }, + { 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 }, + { 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 }, + { 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 }, + { 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 }, + { 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 }, + { 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 }, + { 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 }, + { 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 }, + { 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 }, + { 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 }, + { 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 }, + { 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 }, + { 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 } +}; + +// Each row describes one LDPC parity check. +// Each number is an index into the codeword (1-origin). +// The codeword bits mentioned in each row must XOR to zero. +const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7] = { + { 4, 31, 59, 91, 92, 96, 153 }, + { 5, 32, 60, 93, 115, 146, 0 }, + { 6, 24, 61, 94, 122, 151, 0 }, + { 7, 33, 62, 95, 96, 143, 0 }, + { 8, 25, 63, 83, 93, 96, 148 }, + { 6, 32, 64, 97, 126, 138, 0 }, + { 5, 34, 65, 78, 98, 107, 154 }, + { 9, 35, 66, 99, 139, 146, 0 }, + { 10, 36, 67, 100, 107, 126, 0 }, + { 11, 37, 67, 87, 101, 139, 158 }, + { 12, 38, 68, 102, 105, 155, 0 }, + { 13, 39, 69, 103, 149, 162, 0 }, + { 8, 40, 70, 82, 104, 114, 145 }, + { 14, 41, 71, 88, 102, 123, 156 }, + { 15, 42, 59, 106, 123, 159, 0 }, + { 1, 33, 72, 106, 107, 157, 0 }, + { 16, 43, 73, 108, 141, 160, 0 }, + { 17, 37, 74, 81, 109, 131, 154 }, + { 11, 44, 75, 110, 121, 166, 0 }, + { 45, 55, 64, 111, 130, 161, 173 }, + { 8, 46, 71, 112, 119, 166, 0 }, + { 18, 36, 76, 89, 113, 114, 143 }, + { 19, 38, 77, 104, 116, 163, 0 }, + { 20, 47, 70, 92, 138, 165, 0 }, + { 2, 48, 74, 113, 128, 160, 0 }, + { 21, 45, 78, 83, 117, 121, 151 }, + { 22, 47, 58, 118, 127, 164, 0 }, + { 16, 39, 62, 112, 134, 158, 0 }, + { 23, 43, 79, 120, 131, 145, 0 }, + { 19, 35, 59, 73, 110, 125, 161 }, + { 20, 36, 63, 94, 136, 161, 0 }, + { 14, 31, 79, 98, 132, 164, 0 }, + { 3, 44, 80, 124, 127, 169, 0 }, + { 19, 46, 81, 117, 135, 167, 0 }, + { 7, 49, 58, 90, 100, 105, 168 }, + { 12, 50, 61, 118, 119, 144, 0 }, + { 13, 51, 64, 114, 118, 157, 0 }, + { 24, 52, 76, 129, 148, 149, 0 }, + { 25, 53, 69, 90, 101, 130, 156 }, + { 20, 46, 65, 80, 120, 140, 170 }, + { 21, 54, 77, 100, 140, 171, 0 }, + { 35, 82, 133, 142, 171, 174, 0 }, + { 14, 30, 83, 113, 125, 170, 0 }, + { 4, 29, 68, 120, 134, 173, 0 }, + { 1, 4, 52, 57, 86, 136, 152 }, + { 26, 51, 56, 91, 122, 137, 168 }, + { 52, 84, 110, 115, 145, 168, 0 }, + { 7, 50, 81, 99, 132, 173, 0 }, + { 23, 55, 67, 95, 172, 174, 0 }, + { 26, 41, 77, 109, 141, 148, 0 }, + { 2, 27, 41, 61, 62, 115, 133 }, + { 27, 40, 56, 124, 125, 126, 0 }, + { 18, 49, 55, 124, 141, 167, 0 }, + { 6, 33, 85, 108, 116, 156, 0 }, + { 28, 48, 70, 85, 105, 129, 158 }, + { 9, 54, 63, 131, 147, 155, 0 }, + { 22, 53, 68, 109, 121, 174, 0 }, + { 3, 13, 48, 78, 95, 123, 0 }, + { 31, 69, 133, 150, 155, 169, 0 }, + { 12, 43, 66, 89, 97, 135, 159 }, + { 5, 39, 75, 102, 136, 167, 0 }, + { 2, 54, 86, 101, 135, 164, 0 }, + { 15, 56, 87, 108, 119, 171, 0 }, + { 10, 44, 82, 91, 111, 144, 149 }, + { 23, 34, 71, 94, 127, 153, 0 }, + { 11, 49, 88, 92, 142, 157, 0 }, + { 29, 34, 87, 97, 147, 162, 0 }, + { 30, 50, 60, 86, 137, 142, 162 }, + { 10, 53, 66, 84, 112, 128, 165 }, + { 22, 57, 85, 93, 140, 159, 0 }, + { 28, 32, 72, 103, 132, 166, 0 }, + { 28, 29, 84, 88, 117, 143, 150 }, + { 1, 26, 45, 80, 128, 147, 0 }, + { 17, 27, 89, 103, 116, 153, 0 }, + { 51, 57, 98, 163, 165, 172, 0 }, + { 21, 37, 73, 138, 152, 169, 0 }, + { 16, 47, 76, 130, 137, 154, 0 }, + { 3, 24, 30, 72, 104, 139, 0 }, + { 9, 40, 90, 106, 134, 151, 0 }, + { 15, 58, 60, 74, 111, 150, 163 }, + { 18, 42, 79, 144, 146, 152, 0 }, + { 25, 38, 65, 99, 122, 160, 0 }, + { 17, 42, 75, 129, 170, 172, 0 } +}; + +// Each row corresponds to a codeword bit. +// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit. +// 1-origin. +const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3] = { + { 16, 45, 73 }, + { 25, 51, 62 }, + { 33, 58, 78 }, + { 1, 44, 45 }, + { 2, 7, 61 }, + { 3, 6, 54 }, + { 4, 35, 48 }, + { 5, 13, 21 }, + { 8, 56, 79 }, + { 9, 64, 69 }, + { 10, 19, 66 }, + { 11, 36, 60 }, + { 12, 37, 58 }, + { 14, 32, 43 }, + { 15, 63, 80 }, + { 17, 28, 77 }, + { 18, 74, 83 }, + { 22, 53, 81 }, + { 23, 30, 34 }, + { 24, 31, 40 }, + { 26, 41, 76 }, + { 27, 57, 70 }, + { 29, 49, 65 }, + { 3, 38, 78 }, + { 5, 39, 82 }, + { 46, 50, 73 }, + { 51, 52, 74 }, + { 55, 71, 72 }, + { 44, 67, 72 }, + { 43, 68, 78 }, + { 1, 32, 59 }, + { 2, 6, 71 }, + { 4, 16, 54 }, + { 7, 65, 67 }, + { 8, 30, 42 }, + { 9, 22, 31 }, + { 10, 18, 76 }, + { 11, 23, 82 }, + { 12, 28, 61 }, + { 13, 52, 79 }, + { 14, 50, 51 }, + { 15, 81, 83 }, + { 17, 29, 60 }, + { 19, 33, 64 }, + { 20, 26, 73 }, + { 21, 34, 40 }, + { 24, 27, 77 }, + { 25, 55, 58 }, + { 35, 53, 66 }, + { 36, 48, 68 }, + { 37, 46, 75 }, + { 38, 45, 47 }, + { 39, 57, 69 }, + { 41, 56, 62 }, + { 20, 49, 53 }, + { 46, 52, 63 }, + { 45, 70, 75 }, + { 27, 35, 80 }, + { 1, 15, 30 }, + { 2, 68, 80 }, + { 3, 36, 51 }, + { 4, 28, 51 }, + { 5, 31, 56 }, + { 6, 20, 37 }, + { 7, 40, 82 }, + { 8, 60, 69 }, + { 9, 10, 49 }, + { 11, 44, 57 }, + { 12, 39, 59 }, + { 13, 24, 55 }, + { 14, 21, 65 }, + { 16, 71, 78 }, + { 17, 30, 76 }, + { 18, 25, 80 }, + { 19, 61, 83 }, + { 22, 38, 77 }, + { 23, 41, 50 }, + { 7, 26, 58 }, + { 29, 32, 81 }, + { 33, 40, 73 }, + { 18, 34, 48 }, + { 13, 42, 64 }, + { 5, 26, 43 }, + { 47, 69, 72 }, + { 54, 55, 70 }, + { 45, 62, 68 }, + { 10, 63, 67 }, + { 14, 66, 72 }, + { 22, 60, 74 }, + { 35, 39, 79 }, + { 1, 46, 64 }, + { 1, 24, 66 }, + { 2, 5, 70 }, + { 3, 31, 65 }, + { 4, 49, 58 }, + { 1, 4, 5 }, + { 6, 60, 67 }, + { 7, 32, 75 }, + { 8, 48, 82 }, + { 9, 35, 41 }, + { 10, 39, 62 }, + { 11, 14, 61 }, + { 12, 71, 74 }, + { 13, 23, 78 }, + { 11, 35, 55 }, + { 15, 16, 79 }, + { 7, 9, 16 }, + { 17, 54, 63 }, + { 18, 50, 57 }, + { 19, 30, 47 }, + { 20, 64, 80 }, + { 21, 28, 69 }, + { 22, 25, 43 }, + { 13, 22, 37 }, + { 2, 47, 51 }, + { 23, 54, 74 }, + { 26, 34, 72 }, + { 27, 36, 37 }, + { 21, 36, 63 }, + { 29, 40, 44 }, + { 19, 26, 57 }, + { 3, 46, 82 }, + { 14, 15, 58 }, + { 33, 52, 53 }, + { 30, 43, 52 }, + { 6, 9, 52 }, + { 27, 33, 65 }, + { 25, 69, 73 }, + { 38, 55, 83 }, + { 20, 39, 77 }, + { 18, 29, 56 }, + { 32, 48, 71 }, + { 42, 51, 59 }, + { 28, 44, 79 }, + { 34, 60, 62 }, + { 31, 45, 61 }, + { 46, 68, 77 }, + { 6, 24, 76 }, + { 8, 10, 78 }, + { 40, 41, 70 }, + { 17, 50, 53 }, + { 42, 66, 68 }, + { 4, 22, 72 }, + { 36, 64, 81 }, + { 13, 29, 47 }, + { 2, 8, 81 }, + { 56, 67, 73 }, + { 5, 38, 50 }, + { 12, 38, 64 }, + { 59, 72, 80 }, + { 3, 26, 79 }, + { 45, 76, 81 }, + { 1, 65, 74 }, + { 7, 18, 77 }, + { 11, 56, 59 }, + { 14, 39, 54 }, + { 16, 37, 66 }, + { 10, 28, 55 }, + { 15, 60, 70 }, + { 17, 25, 82 }, + { 20, 30, 31 }, + { 12, 67, 68 }, + { 23, 75, 80 }, + { 27, 32, 62 }, + { 24, 69, 75 }, + { 19, 21, 71 }, + { 34, 53, 61 }, + { 35, 46, 47 }, + { 33, 59, 76 }, + { 40, 43, 83 }, + { 41, 42, 63 }, + { 49, 75, 83 }, + { 20, 44, 48 }, + { 42, 49, 57 } +}; + +const uint8_t kFTX_LDPCNumRows[FTX_LDPC_M] = { + 7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, + 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, + 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, + 6, 6, 6 +}; \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/constants.h b/ft8CN/app/src/main/cpp/ft8/constants.h new file mode 100644 index 0000000..a97dd50 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/constants.h @@ -0,0 +1,89 @@ +#ifndef _INCLUDE_CONSTANTS_H_ +#define _INCLUDE_CONSTANTS_H_ + +#include +#include + +typedef enum +{ + PROTO_FT4, + PROTO_FT8 +} ftx_protocol_t; +#define kMin_score (10) /// 候选人的最低同步分数阈值。Minimum sync score threshold for candidates +#define FT8_SAMPLE_RATE (12000)//采样率 +#define FT8_SYMBOL_PERIOD (0.160f) ///< FT8 symbol duration, defines tone deviation in Hz and symbol rate +#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period + +#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate +#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period + +// Define FT8 symbol counts +// FT8 message structure: +// S D1 S D2 S +// S - sync block (7 symbols of Costas pattern) +// D1 - first data block (29 symbols each encoding 3 bits) +#define FT8_ND (58) ///< Data symbols +#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND) +#define FT8_LENGTH_SYNC (7) ///< Length of each sync group +#define FT8_NUM_SYNC (3) ///< Number of sync groups +#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups +#define FT8_SYMBOL_BT (2.0f) + +// Define FT4 symbol counts +// FT4 message structure: +// R Sa D1 Sb D2 Sc D3 Sd R +// R - ramping symbol (no payload information conveyed) +// Sx - one of four _different_ sync blocks (4 symbols of Costas pattern) +// Dy - data block (29 symbols each encoding 2 bits) +#define FT4_ND (87) ///< Data symbols +#define FT4_NR (2) ///< Ramp symbols (beginning + end) +#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR) +#define FT4_LENGTH_SYNC (4) ///< Length of each sync group +#define FT4_NUM_SYNC (4) ///< Number of sync groups +#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups + +// Define LDPC parameters +#define FTX_LDPC_N (174) ///编码消息中的位数(LDPC校验和位的有效负载)< Number of bits in the encoded message (payload with LDPC checksum bits) +#define FTX_LDPC_K (91) ///< 有效负载位数(包括CRC)Number of payload bits (including CRC) +#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K) +#define FTX_LDPC_N_BYTES ((FTX_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message) +#define FTX_LDPC_K_BYTES ((FTX_LDPC_K + 7) / 8) ///< 存储91位所需的整字节数(仅限有效负载+CRC)Number of whole bytes needed to store 91 bits (payload + CRC only) + +// Define CRC parameters +#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1 +#define FT8_CRC_WIDTH (14) + +#define DECODE_TIME_OUT (1) ///解码迭代时间超时(秒) + +/// Costas 7x7 tone pattern for synchronization +extern const uint8_t kFT8CostasPattern[7]; +extern const uint8_t kFT4CostasPattern[4][4]; + +/// Gray code map to encode 8 symbols (tones) +extern const uint8_t kFT8GrayMap[8]; +extern const uint8_t kFT4GrayMap[4]; + +extern const uint8_t kFT4XORSequence[10]; + +/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +extern const uint8_t kFTXLDPCGenerator[FTX_LDPC_M][FTX_LDPC_K_BYTES]; + +/// LDPC(174,91) parity check matrix, containing 83 rows, +/// each row describes one parity check, +/// each number is an index into the codeword (1-origin). +/// The codeword bits mentioned in each row must xor to zero. +/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. +extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7]; + +/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit. +/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit. +/// The numbers use 1 as the origin (first entry). +extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3]; + +/// Number of rows (columns in C/C++) in the array Nm. +extern const uint8_t kFTX_LDPCNumRows[FTX_LDPC_M]; + +#endif // _INCLUDE_CONSTANTS_H_ + + + diff --git a/ft8CN/app/src/main/cpp/ft8/crc.c b/ft8CN/app/src/main/cpp/ft8/crc.c new file mode 100644 index 0000000..72a4c89 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/crc.c @@ -0,0 +1,63 @@ +#include "crc.h" +#include "constants.h" + +#define TOPBIT (1u << (FT8_CRC_WIDTH - 1)) + +// Compute 14-bit CRC for a sequence of given number of bits +// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits) +{ + uint16_t remainder = 0; + int idx_byte = 0; + + // Perform modulo-2 division, a bit at a time. + for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) + { + if (idx_bit % 8 == 0) + { + // Bring the next byte into the remainder. + remainder ^= (message[idx_byte] << (FT8_CRC_WIDTH - 8)); + ++idx_byte; + } + + // Try to divide the current data bit. + if (remainder & TOPBIT) + { + remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL; + } + else + { + remainder = (remainder << 1); + } + } + + return remainder & ((TOPBIT << 1) - 1u); +} + +uint16_t ftx_extract_crc(const uint8_t a91[]) +{ + uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5); + return chksum; +} + +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]) +{ + // Copy 77 bits of payload data + for (int i = 0; i < 10; i++) + a91[i] = payload[i]; + + // Clear 3 bits after the payload to make 82 bits + a91[9] &= 0xF8u; + a91[10] = 0; + + // Calculate CRC of 82 bits (77 + 5 zeros) + // 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits' + uint16_t checksum = ftx_compute_crc(a91, 96 - 14); + + // Store the CRC at the end of 77 bit message + a91[9] |= (uint8_t)(checksum >> 11); + a91[10] = (uint8_t)(checksum >> 3); + a91[11] = (uint8_t)(checksum << 5); +} diff --git a/ft8CN/app/src/main/cpp/ft8/crc.h b/ft8CN/app/src/main/cpp/ft8/crc.h new file mode 100644 index 0000000..99db708 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/crc.h @@ -0,0 +1,23 @@ +#ifndef _INCLUDE_CRC_H_ +#define _INCLUDE_CRC_H_ + +#include +#include + +// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits); + +/// Extract the FT8/FT4 CRC of a packed message (during decoding) +/// 提取压缩消息的FT8/FT4 CRC(解码期间) +/// @param[in] a91 77 bits of payload data + CRC +/// @return Extracted CRC +uint16_t ftx_extract_crc(const uint8_t a91[]); + +/// Add FT8/FT4 CRC to a packed message (during encoding) +/// @param[in] payload 77 bits of payload data +/// @param[out] a91 91 bits of payload data + CRC +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]); + +#endif // _INCLUDE_CRC_H_ \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/decode.c b/ft8CN/app/src/main/cpp/ft8/decode.c new file mode 100644 index 0000000..a5e69bb --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/decode.c @@ -0,0 +1,628 @@ +#include "decode.h" +#include "constants.h" +#include "crc.h" +#include "ldpc.h" +#include "unpack.h" + +#include +#include +#include "../common/debug.h" +#include "hash22.h" + +/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding +/// @param[in] wf Waterfall data collected during message slot +/// @param[in] cand Candidate to extract the message from +/// @param[in] code_map Symbol encoding map +/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits +static void ft4_extract_likelihood(const waterfall_t *wf, const candidate_t *cand, float *log174); + +static void ft8_extract_likelihood(const waterfall_t *wf, candidate_t *cand, float *log174); + +/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[], +/// as a string of packed bits starting from the MSB of the first byte of packed[] +/// @param[in] plain Array of bits (0 and nonzero values) with num_bits entires +/// @param[in] num_bits Number of bits (entries) passed in bit_array +/// @param[out] packed Byte-packed bits representing the data in bit_array +static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]); + +static float max2(float a, float b); + +static float max4(float a, float b, float c, float d); + +static void heapify_down(candidate_t heap[], int heap_size); + +static void heapify_up(candidate_t heap[], int heap_size); + +static void ftx_normalize_logl(float *log174); + +static void ft4_extract_symbol(const uint8_t *wf, float *logl); + +static void ft8_extract_symbol(const uint8_t *wf, float *logl); + +static void +ft8_decode_multi_symbols(const uint8_t *wf, int num_bins, int n_syms, int bit_idx, float *log174); + +static int get_index(const waterfall_t *wf, const candidate_t *candidate) { + int offset = candidate->time_offset;//time_offset:-12 ~ 23,(costas阵列7个符号+29个数据符号=36) + offset = (offset * wf->time_osr) + candidate->time_sub;//time_sub:0~1 + offset = (offset * wf->freq_osr) + candidate->freq_sub;//freq_sub:0~1 + offset = (offset * wf->num_bins) + + candidate->freq_offset;//num_bins:960,freq_offset:0~ 960(-1) -7 + return offset; +} + + +static int ft8_sync_score(const waterfall_t *wf, candidate_t *candidate) { + /* + * ft8本应有58个符号,但在开始(0-7)、中间(36-43)、结尾(72-79)加了科斯塔阵列,所以共有79个符号, + *此函数在4层循环中执行。时间采样率2*频率采样率2*时间偏移(-12~24=36)*频率偏移(num_bins:960-7) + */ + int score = 0; + int num_average = 0; + + float signal = 0; + float noise = 0; + + // Get the pointer to symbol 0 of the candidate + //获取指向候选符号0的指针,在mag数组中取candidate对应的mag数据。 + const uint8_t *mag_cand = wf->mag + get_index(wf, candidate); + //用于信号量和噪音的计算,暂时注释掉 + // const float *mag_signal = wf->mag2 + get_index(wf, candidate); + + + // Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79) + //计算同步符号的平均分数(m+k=0-7、36-43、72-79) + //m=0~2(3组),k=0~6(7个符号) + for (int m = 0; m < FT8_NUM_SYNC; ++m) { + for (int k = 0; k < FT8_LENGTH_SYNC; ++k) { + //FT8_SYNC_OFFSET=36,block=0..6,36~43,72~79,这是costas阵列在符号序列中的索引 + int block = (FT8_SYNC_OFFSET * m) + k; // 相对于消息relative to the message + int block_abs = + //time_offset=-12.。23(36个) + candidate->time_offset + block; // 相对于捕获的信号relative to the captured signal + // Check for time boundaries + //检查时间界限 + if (block_abs < 0) + continue; + if (block_abs >= wf->num_blocks) + break; + + // Get the pointer to symbol 'block' of the candidate + //获取指向候选人符号“block”的指针 + const uint8_t *p8 = mag_cand + (block * wf->block_stride); + + // Weighted difference between the expected and all other symbols + //预期符号和所有其他符号之间的加权差 + // Does not work as well as the alternative score below + //效果不如下面的备选分数 + // score += 8 * p8[kFT8CostasPattern[k]] - + // p8[0] - p8[1] - p8[2] - p8[3] - + // p8[4] - p8[5] - p8[6] - p8[7]; + // ++num_average; + + // Check only the neighbors of the expected symbol frequency- and time-wise + //仅检查预期符号频率和时间的相邻项,k=0..6 + int sm = kFT8CostasPattern[k]; //预期数据的索引 Index of the expected bin + + + //此处计算信号量和噪音,可能不正确,暂时注释掉 + // const float *p8Signal = mag_signal + (block * wf->block_stride); + + + + //通过sm判断相邻频率的信号量是否小于本位置的信号量,小于就加分 + if (sm > 0) { + // look at one frequency bin lower + //信号量的差值。 + score += p8[sm] - p8[sm - 1]; + ++num_average; + } + if (sm < 7) { + // look at one frequency bin higher + score += p8[sm] - p8[sm + 1]; + ++num_average; + } + //判断前后符号时间频率信号量是否小于本位置的信号量,小于就加分 + if ((k > 0) && (block_abs > 0)) { + // look one symbol back in time + score += p8[sm] - p8[sm - wf->block_stride]; + + ++num_average; + } + if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) { + // look one symbol forward in time + score += p8[sm] - p8[sm + wf->block_stride]; + ++num_average; + } + } + } + + if (num_average > 0) { + score /= num_average; + } + + return score; +} + +static int ft4_sync_score(const waterfall_t *wf, const candidate_t *candidate) { + int score = 0; + int num_average = 0; + + // Get the pointer to symbol 0 of the candidate + const uint8_t *mag_cand = wf->mag + get_index(wf, candidate); + + // Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103) + for (int m = 0; m < FT4_NUM_SYNC; ++m) { + for (int k = 0; k < FT4_LENGTH_SYNC; ++k) { + int block = 1 + (FT4_SYNC_OFFSET * m) + k; + int block_abs = candidate->time_offset + block; + // Check for time boundaries + if (block_abs < 0) + continue; + if (block_abs >= wf->num_blocks) + break; + + // Get the pointer to symbol 'block' of the candidate + const uint8_t *p4 = mag_cand + (block * wf->block_stride); + + int sm = kFT4CostasPattern[m][k]; // Index of the expected bin + + // score += (4 * p4[sm]) - p4[0] - p4[1] - p4[2] - p4[3]; + // num_average += 4; + + // Check only the neighbors of the expected symbol frequency- and time-wise + if (sm > 0) { + // look at one frequency bin lower + score += p4[sm] - p4[sm - 1]; + ++num_average; + } + if (sm < 3) { + // look at one frequency bin higher + score += p4[sm] - p4[sm + 1]; + ++num_average; + } + if ((k > 0) && (block_abs > 0)) { + // look one symbol back in time + score += p4[sm] - p4[sm - wf->block_stride]; + ++num_average; + } + if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) { + // look one symbol forward in time + score += p4[sm] - p4[sm + wf->block_stride]; + ++num_average; + } + } + } + + if (num_average > 0) + score /= num_average; + + return score; +} + +//检测ft8信号,num_candidates最大候选人数量=120,heap[]候选人列表(size=120),kMin_score候选人的最低同步分数阈值=10 +int ft8_find_sync(const waterfall_t *wf, int num_candidates, candidate_t heap[], int min_score) { + int heap_size = 0; + candidate_t candidate;//候选人 + // Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits. + // I.e. we can afford to skip the first 7 or the last 7 Costas symbols, as long as we track how many + // sync symbols we included in the score, so the score is averaged. + //在这里,我们允许超过信号边界的时间偏移,只要我们仍然拥有所有数据位。 + //也就是说,我们可以跳过前7个或最后7个Costas符号,只要我们跟踪有多少个 + //我们在分数中包含了同步符号,所以分数是平均值。 + //循环:时间过采样*频率过采样*前36个符号(7同步+29信息)*fft频率偏移=2*2*36*960=3840*36=138240 + for (candidate.time_sub = 0; candidate.time_sub < wf->time_osr; ++candidate.time_sub) { + for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub) { + for (candidate.time_offset = -12; candidate.time_offset < 24; ++candidate.time_offset) { + for (candidate.freq_offset = 0; + (candidate.freq_offset + 7) < wf->num_bins; ++candidate.freq_offset) { + if (wf->protocol == PROTO_FT4) { + candidate.score = ft4_sync_score(wf, &candidate); + } else { + candidate.score = ft8_sync_score(wf, &candidate); + } + + if (candidate.score < min_score) + continue; + + // If the heap is full AND the current candidate is better than + // the worst in the heap, we remove the worst and make space + //如果堆已满且当前候选堆优于在堆中最坏的,我们移除最坏的,并创造空间 + if (heap_size == num_candidates && candidate.score > heap[0].score) { + heap[0] = heap[heap_size - 1]; + --heap_size; + //降序? + heapify_down(heap, heap_size); + } + + // If there's free space in the heap, we add the current candidate + //如果堆中有可用空间,我们将添加当前候选堆 + if (heap_size < num_candidates) { + heap[heap_size] = candidate; + ++heap_size; + //升序? + heapify_up(heap, heap_size); + } + } + } + } + } + + + + // Sort the candidates by sync strength - here we benefit from the heap structure + int len_unsorted = heap_size; + while (len_unsorted > 1) { + candidate_t tmp = heap[len_unsorted - 1]; + heap[len_unsorted - 1] = heap[0]; + heap[0] = tmp; + len_unsorted--; + heapify_down(heap, len_unsorted); + } + + return heap_size; +} + +static void ft4_extract_likelihood(const waterfall_t *wf, const candidate_t *cand, float *log174) { + const uint8_t *mag_cand = wf->mag + get_index(wf, cand); + + // Go over FSK tones and skip Costas sync symbols + for (int k = 0; k < FT4_ND; ++k) { + // Skip either 5, 9 or 13 sync symbols + // TODO: replace magic numbers with constants + int sym_idx = k + ((k < 29) ? 5 : ((k < 58) ? 9 : 13)); + int bit_idx = 2 * k; + + // Check for time boundaries + int block = cand->time_offset + sym_idx; + if ((block < 0) || (block >= wf->num_blocks)) { + log174[bit_idx + 0] = 0; + log174[bit_idx + 1] = 0; + } else { + // Pointer to 4 bins of the current symbol + const uint8_t *ps = mag_cand + (sym_idx * wf->block_stride); + + ft4_extract_symbol(ps, log174 + bit_idx); + } + } +} + +//解开可能的FT8信号 +static void ft8_extract_likelihood(const waterfall_t *wf, candidate_t *cand, float *log174) { + const uint8_t *mag_cand = wf->mag + get_index(wf, cand); + + ////FT8总消息的为174位,符号是174/3=58个,加上同步costas阵列的7*3=21个符号,共计58+21=79个符号。 + //log174数组的大小是174 + // Go over FSK tones and skip Costas sync symbols + //浏览FSK音调并跳过Costas同步符号,所以log174 + //FT8_ND=58,k=0..57 + for (int k = 0; k < FT8_ND; ++k) { + // Skip either 7 or 14 sync symbols + // TODO: replace magic numbers with constants + //sym_idx=7..35,43..71 + int sym_idx = k + ((k < 29) ? 7 : 14); + //bit_idx符号位的索引 + int bit_idx = 3 * k; + + // Check for time boundaries + //检测时间边界 + int block = cand->time_offset + sym_idx; + if ((block < 0) || (block >= wf->num_blocks)) { + log174[bit_idx + 0] = 0; + log174[bit_idx + 1] = 0; + log174[bit_idx + 2] = 0; + } else { + // Pointer to 8 bins of the current symbol + //指向当前符号信号量的8个箱子的指针 + //block_stride=960*2*2=3840 + const uint8_t *ps = mag_cand + (sym_idx * wf->block_stride); + + //每个符号,bit_idx是符号的3倍 + ft8_extract_symbol(ps, log174 + bit_idx); + } + } +} + +static void ftx_normalize_logl(float *log174) { + // Compute the variance of log174 + //计算log174的方差 + float sum = 0; + float sum2 = 0; + //FTX_LDPC_N=174 + for (int i = 0; i < FTX_LDPC_N; ++i) { + sum += log174[i];//取和 + sum2 += log174[i] * log174[i];//取平方和 + } + float inv_n = 1.0f / FTX_LDPC_N; + //variance方差 + float variance = (sum2 - (sum * sum * inv_n)) * inv_n; + + // Normalize log174 distribution and scale it with experimentally found coefficient + ////规范化log174分布,并用实验发现的系数对其进行缩放 + float norm_factor = sqrtf(24.0f / variance); + for (int i = 0; i < FTX_LDPC_N; ++i) { + log174[i] *= norm_factor; + } +} + +//推算snr +static void ft8_guess_snr(const waterfall_t *wf, candidate_t *cand) { + const float *mag_signal = wf->mag2 + get_index(wf, cand); + + + float signal = 0, noise = 0; + for (int i = 0; i < 7; ++i) { + if ((cand->time_offset + i >= 0) && (cand->time_offset + i < wf->num_blocks + 8)) { + //LOG_PRINTF("End guess SNR 0..."); + signal += mag_signal[(i) * wf->block_stride + kFT8CostasPattern[i]]; + noise += mag_signal[(i) * wf->block_stride + ((kFT8CostasPattern[i] + 4) % 8)]; + //LOG_PRINTF("End guess SNR 0... done"); + } + if ((cand->time_offset + i + 36 >= 0) && (cand->time_offset + i < wf->num_blocks + 8)) { + //LOG_PRINTF("End guess SNR 36..."); + signal += mag_signal[(i + 36) * wf->block_stride + kFT8CostasPattern[i]]; + noise += mag_signal[(i + 36) * wf->block_stride + ((kFT8CostasPattern[i] + 4) % 8)]; + //LOG_PRINTF("End guess SNR 36... done"); + } + //此处容易产生数组下标越界的问题 +// if ((cand->time_offset+i+72>=0)&&(cand->time_offset+inum_blocks+8)) { +// LOG_PRINTF("End guess SNR 72..."); +// signal += mag_signal[(i + 72) * wf->block_stride + kFT8CostasPattern[i]]; +// noise += mag_signal[(i + 72) * wf->block_stride + ((kFT8CostasPattern[i] + 4) % 8)]; +// LOG_PRINTF("End guess SNR 72... done"); +// } + } + //LOG(LOG_INFO, "Max magnitude:ft8_guess_snr 002\n"); + if (noise != 0) { + float raw = signal / noise; + cand->snr = floor(10 * log10f(1E-12f + raw) - 24 + 0.5); + if (cand->snr < -30) {//-30是最小值了。 + cand->snr = -30; + } + } else { + cand->snr = -100; + } +} + +//max_iterations=20 LDPC的迭代次数。 +bool +ft8_decode(waterfall_t *wf, candidate_t *cand, message_t *message, int max_iterations, + decode_status_t *status) { + //FT8总消息的为174位,符号是174/3=58个,加上同步costas阵列的7*3=21个符号,共计58+21=79个符号。 + //FTX_LDPC_N=174,是把7*3个符号的位去掉后的数组, + float log174[FTX_LDPC_N]; //编码为似然的消息位 message bits encoded as likelihood + if (wf->protocol == PROTO_FT4) { + ft4_extract_likelihood(wf, cand, log174); + } else { + //检测可能的FT8信号,结果在log174中,每3个为一组,与8个格雷码为索引的信号量的平方差的值 + ft8_extract_likelihood(wf, cand, log174); + } + + //规范化 + ftx_normalize_logl(log174); + + //FTX_LDPC_N=174 + uint8_t plain174[FTX_LDPC_N]; // message bits (0/1) + + //bp_decode是原作者采用的,ldpc_decode经测试也是可以用的。 + //结果在plain174中,以0和1为值。包括77位信息+14位冗余校验+83位前向纠错=174位。 + //max_iterations是最大迭代次数,越大速度越慢,精度越高 + bp_decode(log174, max_iterations, plain174, &status->ldpc_errors); + //ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors); + + if (status->ldpc_errors > 0) { + return false; + } + + // Extract payload + CRC (first FTX_LDPC_K bits) packed into a byte array + ////提取压缩到字节数组中的有效负载+CRC(第一个FTX\U LDPC\U K位) + ////FTX_LDPC_K_BYTES:存储91位所需的整字节数(仅限有效负载+CRC) + ////FTX_LDPC_K有效负载位数(包括CRC) + uint8_t a91[FTX_LDPC_K_BYTES]; + //提取出91个位,77位信息+14位冗余校验 + pack_bits(plain174, FTX_LDPC_K, a91); + + // Extract CRC and check it + ////提取CRC并进行检查,后面crc_extracted又作为hash值保存下来 + status->crc_extracted = ftx_extract_crc(a91); + + // [1]: 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits.' + a91[9] &= 0xF8; + a91[10] &= 0x00; + status->crc_calculated = ftx_compute_crc(a91, 96 - 14); + + if (status->crc_extracted != status->crc_calculated) { + return false; + } + + if (wf->protocol == PROTO_FT4) { + // '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages, + // the assembled 77-bit message is bitwise exclusive-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits' + for (int i = 0; i < 10; ++i) { + a91[i] ^= kFT4XORSequence[i]; + } + } + + //从91位解包77位信息,然后返回消息的文本内容。 + //status->unpack_status = unpack77(a91, message->text); + message->call_to[0] = message->call_de[0] = message->maidenGrid[0] = message->extra[0] = '\0'; + message->call_de_hash.hash10 = message->call_de_hash.hash12 = message->call_de_hash.hash22 = 0; + message->call_to_hash.hash10 = message->call_to_hash.hash12 = message->call_to_hash.hash22 = 0; + memcpy(message->a91, a91, FTX_LDPC_K_BYTES);//把数据包保存下来,用于音频相减 + + //LOG_PRINTF("hex:%0x %0x %0x %0x %0x %0x %0x %0x %0x %0x" + // ,a91[0],a91[1],a91[2],a91[3],a91[4],a91[5],a91[6],a91[7],a91[8],a91[9]); + + + status->unpack_status = unpackToMessage_t(a91, message); + //message->call_de_hash.hash12=hashcall(message->call_de,HASH_12) ; + + if (status->unpack_status < 0) { + return false; + } + + // Reuse binary message CRC as hash value for the message + //重用二进制消息CRC作为消息的哈希值 + message->hash = status->crc_extracted; + + + //2022-05-13增加解析i3,n3 + //解出i3和n3 + // Extract i3 (bits 74..76) + //message->i3 = (a91[9] >> 3) & 0x07; + // Extract n3 (bits 71..73) + //message->n3 = ((a91[8] << 2) & 0x04) | ((a91[9] >> 6) & 0x03); + //推算信噪比 + ft8_guess_snr(wf, cand); + + return true; +} + +static float max2(float a, float b) { + return (a >= b) ? a : b; +} + +static float max4(float a, float b, float c, float d) { + return max2(max2(a, b), max2(c, d)); +} + +static void heapify_down(candidate_t heap[], int heap_size) { + // heapify from the root down + int current = 0; + while (true) { + int largest = current; + int left = 2 * current + 1; + int right = left + 1; + + if (left < heap_size && heap[left].score < heap[largest].score) { + largest = left; + } + if (right < heap_size && heap[right].score < heap[largest].score) { + largest = right; + } + if (largest == current) { + break; + } + + candidate_t tmp = heap[largest]; + heap[largest] = heap[current]; + heap[current] = tmp; + current = largest; + } +} + +static void heapify_up(candidate_t heap[], int heap_size) { + // heapify from the last node up + int current = heap_size - 1; + while (current > 0) { + int parent = (current - 1) / 2; + if (heap[current].score >= heap[parent].score) { + break; + } + + candidate_t tmp = heap[parent]; + heap[parent] = heap[current]; + heap[current] = tmp; + current = parent; + } +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of 2 message bits (1 FSK symbol) +static void ft4_extract_symbol(const uint8_t *wf, float *logl) { + // Cleaned up code for the simple case of n_syms==1 + float s2[4]; + + for (int j = 0; j < 4; ++j) { + s2[j] = (float) wf[kFT4GrayMap[j]]; + } + + logl[0] = max2(s2[2], s2[3]) - max2(s2[0], s2[1]); + logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]); +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol) +//计算3个消息位(1个FSK符号)的非规范化对数似然对数log((p(1)/p(0)) +//wf当前符号的信号量的地址,logl当前符号的位数组的地址。 +static void ft8_extract_symbol(const uint8_t *wf, float *logl) { + // Cleaned up code for the simple case of n_syms==1 + //清理了n_syms==1简单案例的代码 + float s2[8];//信号强度数组,格雷码数组内容做偏移索引:{ 0, 1, 3, 2, 5, 6, 4, 7 } + + for (int j = 0; j < 8; ++j) { + s2[j] = (float) wf[kFT8GrayMap[j]];//格雷码值作索引,对应信号的强度保存到 + } + //信号量的值在之前已经是平方过的了,相减实际上是log(p(1)/p(0))。 + logl[0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]); + logl[1] = max4(s2[2], s2[3], s2[6], s2[7]) - max4(s2[0], s2[1], s2[4], s2[5]); + logl[2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]); +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once +static void +ft8_decode_multi_symbols(const uint8_t *wf, int num_bins, int n_syms, int bit_idx, float *log174) { + const int n_bits = 3 * n_syms; + const int n_tones = (1 << n_bits); + + float s2[n_tones]; + + for (int j = 0; j < n_tones; ++j) { + int j1 = j & 0x07; + if (n_syms == 1) { + s2[j] = (float) wf[kFT8GrayMap[j1]]; + continue; + } + int j2 = (j >> 3) & 0x07; + if (n_syms == 2) { + s2[j] = (float) wf[kFT8GrayMap[j2]]; + s2[j] += (float) wf[kFT8GrayMap[j1] + 4 * num_bins]; + continue; + } + int j3 = (j >> 6) & 0x07; + s2[j] = (float) wf[kFT8GrayMap[j3]]; + s2[j] += (float) wf[kFT8GrayMap[j2] + 4 * num_bins]; + s2[j] += (float) wf[kFT8GrayMap[j1] + 8 * num_bins]; + } + + // Extract bit significance (and convert them to float) + // 8 FSK tones = 3 bits + for (int i = 0; i < n_bits; ++i) { + if (bit_idx + i >= FTX_LDPC_N) { + // Respect array size + break; + } + + uint16_t mask = (n_tones >> (i + 1)); + float max_zero = -1000, max_one = -1000; + for (int n = 0; n < n_tones; ++n) { + if (n & mask) { + max_one = max2(max_one, s2[n]); + } else { + max_zero = max2(max_zero, s2[n]); + } + } + + log174[bit_idx + i] = max_one - max_zero; + } +} + +// Packs a string of bits each represented as a zero/non-zero byte in plain[], +// as a string of packed bits starting from the MSB of the first byte of packed[] +static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]) { + int num_bytes = (num_bits + 7) / 8; + for (int i = 0; i < num_bytes; ++i) { + packed[i] = 0; + } + + uint8_t mask = 0x80; + int byte_idx = 0; + for (int i = 0; i < num_bits; ++i) { + if (bit_array[i]) { + packed[byte_idx] |= mask; + } + mask >>= 1; + if (!mask) { + mask = 0x80; + ++byte_idx; + } + } +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/decode.h b/ft8CN/app/src/main/cpp/ft8/decode.h new file mode 100644 index 0000000..eea7b7e --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/decode.h @@ -0,0 +1,121 @@ +#ifndef _INCLUDE_DECODE_H_ +#define _INCLUDE_DECODE_H_ + +#include +#include + +#include "constants.h" +#include "../fft/kiss_fft.h" +#include "../common/debug.h" +/// Input structure to ft8_find_sync() function. This structure describes stored waterfall data over the whole message slot. +/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution. +/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds. +/// Values time_osr > 1 mean each symbol is further subdivided in time. +/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing. +/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis. + +/// ft8_find_sync()函数的输入结构。这种结构描述了整个消息槽中存储的瀑布数据。 +/// time_osr和freq_osr字段指定时间和频率分辨率的额外过采样率。 +/// 如果time_osr=1,则针对每个传输的符号收集一次FFT幅度数据,即每1/6.25=0.16秒收集一次。FSK符号的时间长度是0.16秒。 +/// 如果time_osr>1表示每个符号在时间上进一步过采样。 +/// 如果freq_osr=1,FFT幅度数据中的每个单元对应于6.25 Hz,这是音调间隔(FSK的符号时长)。 +/// 如果freq_osr>1意味着通过FFT分析进一步对音调间隔过采样。 + +typedef struct +{ + int max_blocks; ///< mag阵列中分配的块(符号)数。number of blocks (symbols) allocated in the mag array + int num_blocks; ///< mag阵列中存储的块(符号)编号,时域序列号。number of blocks (symbols) stored in the mag array + //num_bins = 12000 * 0.16 / 2 = 960 + int num_bins; ///< 以6.25 Hz为单位的FFT箱数量(960)。number of FFT bins in terms of 6.25 Hz + int time_osr; ///< 时间过采样率(时间细分数)。number of time subdivisions + int freq_osr; ///< 频率过采样率(频率细分数)。number of frequency subdivisions + uint8_t* mag; ///< FFT的magnitudes(量级)存储。FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins] + int block_stride; ///< 块的步态?没搞懂。Helper value = time_osr * freq_osr * num_bins + ftx_protocol_t protocol; ///< 协议。Indicate if using FT4 or FT8 + float* mag2;///用于存储准确信号量的数组,这是个平方值,真正的值需要开方,为了速度,就不开方了,等后续再计算。 + +} waterfall_t; + +/// Output structure of ft8_find_sync() and input structure of ft8_decode(). +/// Holds the position of potential start of a message in time and frequency. +// 此结构是ft8_find_sync()的输出结构,ft8_decode()的输入 +// 在时间和频率上,保持消息的潜在起始位置。 +typedef struct +{ + int16_t score; ///< score 候选分数(非负数;分数越高表示可能性越大)。Candidate score (non-negative number; higher score means higher likelihood) + int16_t time_offset; ///< 时间段索引。Index of the time block + int16_t freq_offset; ///< 频率段索引。Index of the frequency bin + uint8_t time_sub; ///< 所用时间细分的索引。Index of the time subdivision used + uint8_t freq_sub; ///< 所用频率细分的索引。Index of the frequency subdivision used + int snr;//信噪比 +} candidate_t; + +/// Structure that holds the decoded message +typedef struct { + uint32_t hash22; + uint32_t hash12; + uint32_t hash10; +} hashCode; +// 保存已解码消息的结构 +typedef struct +{ + //2022-05-13增加i3和n3 + uint8_t i3; + uint8_t n3; + + // TODO: check again that this size is enough + //char text[25]; ///< 纯文本,Plain text,原文是25, + char text[48]; ///<但在在unpack.c中,unpack77函数的最大可能是14+14+19= + uint16_t hash; ///用于对消息hash,防止消息重复< Hash value to be used in hash table and quick checking for duplicates + + //2022-05-26新增以下内容 + char call_to[14];//被呼叫的呼号 + char call_de[14];//发起的呼号 + char extra[19];//扩展内容 + + //---TODO------------- + char maidenGrid[5];//梅登海德 + int report;//信号报告 + + hashCode call_to_hash;//22位长度的哈希码 + hashCode call_de_hash;//22位长度的哈希码 + //TO HASH , FROM HASH + + uint8_t a91[FTX_LDPC_K_BYTES];//用于生成减法代码的数据 + +} message_t; + +/// Structure that contains the status of various steps during decoding of a message +/// 包含消息解码过程中各个步骤的状态的结构 +typedef struct +{ + int ldpc_errors; ///< 解码期间的LDPC(稀疏校验矩阵)错误数。Number of LDPC errors during decoding + uint16_t crc_extracted; ///< 从消息中恢复的CRC值。CRC value recovered from the message + uint16_t crc_calculated; ///< 在有效负载上计算的CRC值。CRC value calculated over the payload + int unpack_status; ///< 解包例程的返回值。Return value of the unpack routine +} decode_status_t; + +/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) +/// 根据同步强度(查看Costas符号),在频率和时间上对前N名候选人进行本地化。 +/// We treat and organize the candidate list as a min-heap (empty initially). +/// 我们将候选列表视为一个最小堆(最初为空)。 + +/// @param[in] power 在消息槽期间收集的瀑布数据。Waterfall data collected during message slot +/// @param[in] sync_pattern 同步模式。Synchronization pattern +/// @param[in] num_candidates 最大候选数量(堆数组大小)。Number of maximum candidates (size of heap array) +/// @param[in,out] heap 候选项类型的数组(分配了num个候选项)。Array of candidate_t type entries (with num_candidates allocated entries) +/// @param[in] min_score 删减不太可能的候选项所允许的最低分数(可以为零,没有效果)。Minimal score allowed for pruning unlikely candidates (can be zero for no effect) +/// @return 堆中填写的候选人数。Number of candidates filled in the heap +int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score); + +/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text. +/// 尝试解码候选消息。提取比特概率,运行LDPC解码器,检查CRC,并将消息解压为纯文本。 +/// @param[in] power 在消息槽期间收集的瀑布数据。Waterfall data collected during message slot +/// @param[in] cand 要解码的候选人。Candidate to decode +/// @param[out] message 将接收解码消息的message_t结构。message_t structure that will receive the decoded message +/// @param[in] max_iterations 允许的最大LDPC迭代次数(数字越小,解码速度越快,但精度越低)。Maximum allowed LDPC iterations (lower number means faster decode, but less precise) +/// @param[out] status decode_status_t结构,该结构将填充各种解码步骤的状态。decode_status_t structure that will be filled with the status of various decoding steps +/// @return 如果解码成功,则为True,否则为false(查看状态了解详细信息)。True if the decoding was successful, false otherwise (check status for details) +bool ft8_decode(waterfall_t* power, candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status); + +#endif // _INCLUDE_DECODE_H_ diff --git a/ft8CN/app/src/main/cpp/ft8/encode.c b/ft8CN/app/src/main/cpp/ft8/encode.c new file mode 100644 index 0000000..ad4b57d --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/encode.c @@ -0,0 +1,197 @@ +#include "encode.h" +#include "constants.h" +#include "crc.h" + +#include +#include "../common/debug.h" +// Returns 1 if an odd number of bits are set in x, zero otherwise +static uint8_t parity8(uint8_t x) +{ + x ^= x >> 4; // a b c d ae bf cg dh + x ^= x >> 2; // a b ac bd cae dbf aecg bfdh + x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh + return x % 2; // modulo 2 +} + +// Encode via LDPC a 91-bit message and return a 174-bit codeword. +// The generator matrix has dimensions (87,87). +// The code is a (174,91) regular LDPC code with column weight 3. +// Arguments: +// [IN] message - array of 91 bits stored as 12 bytes (MSB first) +// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) +static void encode174(const uint8_t* message, uint8_t* codeword) +{ + // This implementation accesses the generator bits straight from the packed binary representation in kFTXLDPCGenerator + + // Fill the codeword with message and zeros, as we will only update binary ones later + for (int j = 0; j < FTX_LDPC_N_BYTES; ++j) + { + codeword[j] = (j < FTX_LDPC_K_BYTES) ? message[j] : 0; + } + + // Compute the byte index and bit mask for the first checksum bit + uint8_t col_mask = (0x80u >> (FTX_LDPC_K % 8u)); // bitmask of current byte + uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array + + // Compute the LDPC checksum bits and store them in codeword + for (int i = 0; i < FTX_LDPC_M; ++i) + { + // Fast implementation of bitwise multiplication and parity checking + // Normally nsum would contain the result of dot product between message and kFTXLDPCGenerator[i], + // but we only compute the sum modulo 2. + uint8_t nsum = 0; + for (int j = 0; j < FTX_LDPC_K_BYTES; ++j) + { + uint8_t bits = message[j] & kFTXLDPCGenerator[i][j]; // bitwise AND (bitwise multiplication) + nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) + } + + // Set the current checksum bit in codeword if nsum is odd + if (nsum % 2) + { + codeword[col_idx] |= col_mask; + } + + // Update the byte index and bit mask for the next checksum bit + col_mask >>= 1; + if (col_mask == 0) + { + col_mask = 0x80u; + ++col_idx; + } + } +} + +void ft8_encode(const uint8_t* payload, uint8_t* tones) +{ + uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); + + + + // Message structure: S7 D29 S7 D29 S7 + // Total symbols: 79 (FT8_NN) + + uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword + int i_byte = 0; // Index of the current byte of the codeword + for (int i_tone = 0; i_tone < FT8_NN; ++i_tone) + { + if ((i_tone >= 0) && (i_tone < 7)) + { + tones[i_tone] = kFT8CostasPattern[i_tone]; + } + else if ((i_tone >= 36) && (i_tone < 43)) + { + tones[i_tone] = kFT8CostasPattern[i_tone - 36]; + } + else if ((i_tone >= 72) && (i_tone < 79)) + { + tones[i_tone] = kFT8CostasPattern[i_tone - 72]; + } + else + { + // Extract 3 bits from codeword at i-th position + uint8_t bits3 = 0; + + if (codeword[i_byte] & mask) + bits3 |= 4; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + + tones[i_tone] = kFT8GrayMap[bits3]; + } + } +} + +void ft4_encode(const uint8_t* payload, uint8_t* tones) +{ + uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC + uint8_t payload_xor[10]; // Encoded payload data + + // '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages, + // the assembled 77-bit message is bitwise exclusive-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits' + for (int i = 0; i < 10; ++i) + { + payload_xor[i] = payload[i] ^ kFT4XORSequence[i]; + } + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload_xor, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); // 91 bits -> 174 bits + + // Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R + // Total symbols: 105 (FT4_NN) + + uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword + int i_byte = 0; // Index of the current byte of the codeword + for (int i_tone = 0; i_tone < FT4_NN; ++i_tone) + { + if ((i_tone == 0) || (i_tone == 104)) + { + tones[i_tone] = 0; // R (ramp) symbol + } + else if ((i_tone >= 1) && (i_tone < 5)) + { + tones[i_tone] = kFT4CostasPattern[0][i_tone - 1]; + } + else if ((i_tone >= 34) && (i_tone < 38)) + { + tones[i_tone] = kFT4CostasPattern[1][i_tone - 34]; + } + else if ((i_tone >= 67) && (i_tone < 71)) + { + tones[i_tone] = kFT4CostasPattern[2][i_tone - 67]; + } + else if ((i_tone >= 100) && (i_tone < 104)) + { + tones[i_tone] = kFT4CostasPattern[3][i_tone - 100]; + } + else + { + // Extract 2 bits from codeword at i-th position + uint8_t bits2 = 0; + + if (codeword[i_byte] & mask) + bits2 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits2 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + tones[i_tone] = kFT4GrayMap[bits2]; + } + } +} diff --git a/ft8CN/app/src/main/cpp/ft8/encode.h b/ft8CN/app/src/main/cpp/ft8/encode.h new file mode 100644 index 0000000..d5e2759 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/encode.h @@ -0,0 +1,32 @@ +#ifndef _INCLUDE_ENCODE_H_ +#define _INCLUDE_ENCODE_H_ + +#include + +// typedef struct +// { +// uint8_t tones[FT8_NN]; +// // for waveform readout: +// int n_spsym; // Number of waveform samples per symbol +// float *pulse; // [3 * n_spsym] +// int idx_symbol; // Index of the current symbol +// float f0; // Base frequency, Hertz +// float signal_rate; // Waveform sample rate, Hertz +// } encoder_t; + +// void encoder_init(float signal_rate, float *pulse_buffer); +// void encoder_set_f0(float f0); +// void encoder_process(const message_t *message); // in: message +// void encoder_generate(float *block); // out: block of waveforms + +/// Generate FT8 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7) +void ft8_encode(const uint8_t* payload, uint8_t* tones); + +/// Generate FT4 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3) +void ft4_encode(const uint8_t* payload, uint8_t* tones); + +#endif // _INCLUDE_ENCODE_H_ diff --git a/ft8CN/app/src/main/cpp/ft8/hash22.c b/ft8CN/app/src/main/cpp/ft8/hash22.c new file mode 100644 index 0000000..bf9e97b --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/hash22.c @@ -0,0 +1,65 @@ + + +#include "hash22.h" +#include "stdlib.h" +#include "string.h" +//m为hash的长度,12,22 +//call的长度是12(包括'\0') +uint32_t hashcall(char* call, int m) +{ + const char *chars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"; + char callsign[11]=" "; + + char *temp=call; + + int j=0; + while(temp[0] == ' '){ + ++temp; + if (temp[0]!=' ') + { + break; + } + j++; + } + + for (int i = 0; i < 11-j; i++) + { + if (temp[i]=='\0') + { + break; + }else{ + callsign[i]=temp[i]; + } + + } + + uint64_t x = 0; + for(int i = 0; i < 11; i++){ + + int c = (int)callsign[i]; + const char *p = strchr(chars, c); + if (p==NULL) + { + return 0; + } + + int j = p - chars; + x = 38*x + j; + } + + x = x * 47055833459LL; + x = x >> (64 - m); + + return x; + +} + +uint32_t hashcall_10(char* call){ + return hashcall(call,HASH_10); +} +uint32_t hashcall_12(char* call){ + return hashcall(call,HASH_12); +} +uint32_t hashcall_22(char* call){ + return hashcall(call,HASH_22); +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/hash22.h b/ft8CN/app/src/main/cpp/ft8/hash22.h new file mode 100644 index 0000000..96ce5ab --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/hash22.h @@ -0,0 +1,11 @@ +#include + + +#define HASH_10 (10) ///哈希码长度为10 +#define HASH_12 (12) ///哈希码长度为12 +#define HASH_22 (22) ///哈希码长度为12 +uint32_t hashcall(char* call, int m); + +uint32_t hashcall_10(char* call);//返回长度是10的哈希码 +uint32_t hashcall_12(char* call);//返回长度是12的哈希码 +uint32_t hashcall_22(char* call);//返回长度是22的哈希码 \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/ldpc.c b/ft8CN/app/src/main/cpp/ft8/ldpc.c new file mode 100644 index 0000000..1b3584c --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/ldpc.c @@ -0,0 +1,268 @@ +// +// LDPC decoder for FT8. +// +// given a 174-bit codeword as an array of log-likelihood of zero, +// return a 174-bit corrected codeword, or zero-length array. +// last 87 bits are the (systematic) plain-text. +// this is an implementation of the sum-product algorithm +// from Sarah Johnson's Iterative Error Correction book. +// codeword[i] = log ( P(x=0) / P(x=1) ) +// +////将174位码字作为对数似然为零的数组, +////返回一个174位已更正的码字或零长度数组。 +////最后87位是(系统)纯文本。 +////这是和积算法的一个实现 +////来自Sarah Johnson的迭代纠错手册。 +////码字[i]=对数(P(x=0)/P(x=1)) + +#include "ldpc.h" +#include "constants.h" + +#include +#include +#include +#include + +static int ldpc_check(uint8_t codeword[]); +static float fast_tanh(float x); +static float fast_atanh(float x); + +// codeword is 174 log-likelihoods. +// plain is a return value, 174 ints, to be 0 or 1. +// max_iters is how hard to try. +// ok == 87 means success. +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok) +{ + float m[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB + float e[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB + int min_errors = FTX_LDPC_M; + + for (int j = 0; j < FTX_LDPC_M; j++) + { + for (int i = 0; i < FTX_LDPC_N; i++) + { + m[j][i] = codeword[i]; + e[j][i] = 0.0f; + } + } + + for (int iter = 0; iter < max_iters; iter++) + { + for (int j = 0; j < FTX_LDPC_M; j++) + { + + for (int ii1 = 0; ii1 < kFTX_LDPCNumRows[j]; ii1++) + { + + int i1 = kFTX_LDPC_Nm[j][ii1] - 1; + float a = 1.0f; + for (int ii2 = 0; ii2 < kFTX_LDPCNumRows[j]; ii2++) + { + + int i2 = kFTX_LDPC_Nm[j][ii2] - 1; + if (i2 != i1) + { + a *= fast_tanh(-m[j][i2] / 2.0f); + } + } + e[j][i1] = -2.0f * fast_atanh(a); + } + } + + for (int i = 0; i < FTX_LDPC_N; i++) + { + float l = codeword[i]; + for (int j = 0; j < 3; j++) + l += e[kFTX_LDPC_Mn[i][j] - 1][i]; + plain[i] = (l > 0) ? 1 : 0; + } + + int errors = ldpc_check(plain); + + if (errors < min_errors) + { + // Update the current best result + min_errors = errors; + + if (errors == 0) + { + break; // Found a perfect answer + } + } + + for (int i = 0; i < FTX_LDPC_N; i++) + { + for (int ji1 = 0; ji1 < 3; ji1++) + { + int j1 = kFTX_LDPC_Mn[i][ji1] - 1; + float l = codeword[i]; + for (int ji2 = 0; ji2 < 3; ji2++) + { + if (ji1 != ji2) + { + int j2 = kFTX_LDPC_Mn[i][ji2] - 1; + l += e[j2][i]; + } + } + m[j1][i] = l; + } + } + } + + *ok = min_errors; +} + +// +// does a 174-bit codeword pass the FT8's LDPC parity checks? +// returns the number of parity errors. +// 0 means total success. +// +static int ldpc_check(uint8_t codeword[]) +{ + int errors = 0; + + for (int m = 0; m < FTX_LDPC_M; ++m) + { + uint8_t x = 0; + for (int i = 0; i < kFTX_LDPCNumRows[m]; ++i) + { + x ^= codeword[kFTX_LDPC_Nm[m][i] - 1]; + } + if (x != 0) + { + ++errors; + } + } + return errors; +} + +//// 码字是174个对数可能性。 +//// plain是一个返回值,174 整数,为0或1。 +//// max_iters是迭代次数。 +//// ok==87表示成功。好像不是哦,==0才是 +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok) +{ + float tov[FTX_LDPC_N][3]; + float toc[FTX_LDPC_M][7]; + + //FTX_LDPC_M=83 + int min_errors = FTX_LDPC_M; + + // initialize message data + //FTX_LDPC_N=174 + for (int n = 0; n < FTX_LDPC_N; ++n) + { + tov[n][0] = tov[n][1] = tov[n][2] = 0; + } + + for (int iter = 0; iter < max_iters; ++iter) + { + // Do a hard decision guess (tov=0 in iter 0) + int plain_sum = 0; + for (int n = 0; n < FTX_LDPC_N; ++n) + {//转换成0和1 + plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0; + plain_sum += plain[n]; + } + + if (plain_sum == 0) + { + // message converged to all-zeros, which is prohibited + //消息聚合到所有零,这是禁止的 + break; + } + + // Check to see if we have a codeword (check before we do any iter) + //向LDPC(稀疏校验矩阵)检测LDPC矩阵是预定义的83行91列的矩阵 + int errors = ldpc_check(plain); + + if (errors < min_errors) + { + // we have a better guess - update the result + min_errors = errors; + + if (errors == 0) + { + break; // Found a perfect answer + } + } + + // Send messages from bits to check nodes + for (int m = 0; m < FTX_LDPC_M; ++m) + { + for (int n_idx = 0; n_idx < kFTX_LDPCNumRows[m]; ++n_idx) + { + int n = kFTX_LDPC_Nm[m][n_idx] - 1; + // for each (n, m) + float Tnm = codeword[n]; + for (int m_idx = 0; m_idx < 3; ++m_idx) + { + if ((kFTX_LDPC_Mn[n][m_idx] - 1) != m) + { + Tnm += tov[n][m_idx]; + } + } + toc[m][n_idx] = fast_tanh(-Tnm / 2); + } + } + + // send messages from check nodes to variable nodes + for (int n = 0; n < FTX_LDPC_N; ++n) + { + for (int m_idx = 0; m_idx < 3; ++m_idx) + { + int m = kFTX_LDPC_Mn[n][m_idx] - 1; + // for each (n, m) + float Tmn = 1.0f; + for (int n_idx = 0; n_idx < kFTX_LDPCNumRows[m]; ++n_idx) + { + if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n) + { + Tmn *= toc[m][n_idx]; + } + } + tov[n][m_idx] = -2 * fast_atanh(Tmn); + } + } + } + + *ok = min_errors; +} + +// Ideas for approximating tanh/atanh: +// * https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/ +// * http://functions.wolfram.com/ElementaryFunctions/ArcTanh/10/0001/ +// * https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html +// * https://math.stackexchange.com/a/446411 + +static float fast_tanh(float x) +{ + if (x < -4.97f) + { + return -1.0f; + } + if (x > 4.97f) + { + return 1.0f; + } + float x2 = x * x; + // float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2))); + // float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f)); + // float a = x * (10395.0f + x2 * (1260.0f + x2 * 21.0f)); + // float b = 10395.0f + x2 * (4725.0f + x2 * (210.0f + x2)); + float a = x * (945.0f + x2 * (105.0f + x2)); + float b = 945.0f + x2 * (420.0f + x2 * 15.0f); + return a / b; +} + +static float fast_atanh(float x) +{ + float x2 = x * x; + // float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f))); + // float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f))); + // float a = x * (-1155.0f + x2 * (1190.0f + x2 * -231.0f)); + // float b = (-1155.0f + x2 * (1575.0f + x2 * (-525.0f + x2 * 25.0f))); + float a = x * (945.0f + x2 * (-735.0f + x2 * 64.0f)); + float b = (945.0f + x2 * (-1050.0f + x2 * 225.0f)); + return a / b; +} diff --git a/ft8CN/app/src/main/cpp/ft8/ldpc.h b/ft8CN/app/src/main/cpp/ft8/ldpc.h new file mode 100644 index 0000000..5215d4b --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/ldpc.h @@ -0,0 +1,20 @@ +#ifndef _INCLUDE_LDPC_H_ +#define _INCLUDE_LDPC_H_ + +#include +#include "../common/debug.h" + +//// codeword is 174 log-likelihoods. +//// plain is a return value, 174 ints, to be 0 or 1. +//// iters is how hard to try. +//// ok == 87 means success. +//// 码字是174个对数可能性。 +//// plain是一个返回值,174 整数,为0或1。 +//// max_iters是迭代次数。 +//// ok==87表示成功。??好像不是哦,==0才是 + +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok); + +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok); + +#endif // _INCLUDE_LDPC_H_ diff --git a/ft8CN/app/src/main/cpp/ft8/pack.c b/ft8CN/app/src/main/cpp/ft8/pack.c new file mode 100644 index 0000000..9617aa9 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/pack.c @@ -0,0 +1,373 @@ +#include "pack.h" +#include "text.h" + +#include +#include +#include +#include +#include "../common/debug.h" + +#define NTOKENS ((uint32_t)2063592L) +#define MAX22 ((uint32_t)4194304L) +#define MAXGRID4 ((uint16_t)32400) + +const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"; +const char A1[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const char A2[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const char A3[] = "0123456789"; +const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// Pack a special token, a 22-bit hash code, or a valid base call +// into a 28-bit integer. +int32_t pack28(const char* callsign) +{ + // Check for special tokens first + if (starts_with(callsign, "DE ")) + return 0; + if (starts_with(callsign, "QRZ ")) + return 1; + if (starts_with(callsign, "CQ ")) + return 2; + + if (starts_with(callsign, "CQ_")) + { + int nnum = 0, nlet = 0; + + // TODO: + } + + // TODO: Check for <...> callsign + + char c6[6] = { ' ', ' ', ' ', ' ', ' ', ' ' }; + + int length = 0; // strlen(callsign); // We will need it later + while (callsign[length] != ' ' && callsign[length] != 0) + { + length++; + } + + // Copy callsign to 6 character buffer + if (starts_with(callsign, "3DA0") && length <= 7) + { + // Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ + memcpy(c6, "3D0", 3); + memcpy(c6 + 3, callsign + 4, length - 4); + } + else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) + { + // Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ + memcpy(c6, "Q", 1); + memcpy(c6 + 1, callsign + 2, length - 2); + } + else + { + if (is_digit(callsign[2]) && length <= 6) + { + // AB0XYZ + memcpy(c6, callsign, length); + } + else if (is_digit(callsign[1]) && length <= 5) + { + // A0XYZ -> " A0XYZ" + memcpy(c6 + 1, callsign, length); + } + } + + // Check for standard callsign + int i0, i1, i2, i3, i4, i5; + if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 && (i2 = char_index(A3, c6[2])) >= 0 && (i3 = char_index(A4, c6[3])) >= 0 && (i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0) + { + // This is a standard callsign + int32_t n28 = i0; + n28 = n28 * 36 + i1; + n28 = n28 * 10 + i2; + n28 = n28 * 27 + i3; + n28 = n28 * 27 + i4; + n28 = n28 * 27 + i5; + return NTOKENS + MAX22 + n28; + } + + //char text[13]; + //if (length > 13) return -1; + + // TODO: + // Treat this as a nonstandard callsign: compute its 22-bit hash + return -1; +} + +// Check if a string could be a valid standard callsign or a valid +// compound callsign. +// Return base call "bc" and a logical "cok" indicator. +bool chkcall(const char* call, char* bc) +{ + int length = strlen(call); // n1=len_trim(w) + if (length > 11) + return false; + if (0 != strchr(call, '.')) + return false; + if (0 != strchr(call, '+')) + return false; + if (0 != strchr(call, '-')) + return false; + if (0 != strchr(call, '?')) + return false; + if (length > 6 && 0 != strchr(call, '/')) + return false; + + // TODO: implement suffix parsing (or rework?) + + return true; +} + +uint16_t packgrid(const char* grid4) +{ + if (grid4 == 0) + { + // Two callsigns only, no report/grid + return MAXGRID4 + 1; + } + + // Take care of special cases + if (equals(grid4, "RRR")) + return MAXGRID4 + 2; + if (equals(grid4, "RR73")) + return MAXGRID4 + 3; + if (equals(grid4, "73")) + return MAXGRID4 + 4; + + // Check for standard 4 letter grid + if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && is_digit(grid4[2]) && is_digit(grid4[3])) + { + uint16_t igrid4 = (grid4[0] - 'A'); + igrid4 = igrid4 * 18 + (grid4[1] - 'A'); + igrid4 = igrid4 * 10 + (grid4[2] - '0'); + igrid4 = igrid4 * 10 + (grid4[3] - '0'); + return igrid4; + } + + // Parse report: +dd / -dd / R+dd / R-dd + // TODO: check the range of dd + if (grid4[0] == 'R') + { + int dd = dd_to_int(grid4 + 1, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt) | 0x8000; // ir = 1 + } + else + { + int dd = dd_to_int(grid4, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt); // ir = 0 + } + + return MAXGRID4 + 1; +} + +// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call) +int pack77_1(const char* msg, uint8_t* b77) +{ + // Locate the first delimiter + const char* s1 = strchr(msg, ' '); + if (s1 == 0) + return -1; + + const char* call1 = msg; // 1st call + const char* call2 = s1 + 1; // 2nd call + + LOG(LOG_DEBUG,"call1 :%s", call1); + LOG(LOG_DEBUG,"call2 :%s", call2); + + int32_t n28a = pack28(call1); + int32_t n28b = pack28(call2); + LOG(LOG_DEBUG,"n28a %2X",n28a); + LOG(LOG_DEBUG,"n28b %2X",n28b); + + if (n28a < 0 || n28b < 0) + return -1; + + uint16_t igrid4; + + // Locate the second delimiter + const char* s2 = strchr(s1 + 1, ' '); + if (s2 != 0) + { + LOG(LOG_DEBUG,"GRID: %s",s2); + igrid4 = packgrid(s2 + 1); + } + else + { + // Two callsigns, no grid/report + igrid4 = packgrid(0); + } + LOG(LOG_DEBUG,"G15: %x",igrid4); + + uint8_t i3 = 1; // No suffix or /R + + // TODO: check for suffixes + + // Shift in ipa and ipb bits into n28a and n28b + n28a <<= 1; // ipa = 0 + n28b <<= 1; // ipb = 0 + + // Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits + b77[0] = (n28a >> 21); + b77[1] = (n28a >> 13); + b77[2] = (n28a >> 5); + b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26); + b77[4] = (n28b >> 18); + b77[5] = (n28b >> 10); + b77[6] = (n28b >> 2); + b77[7] = (uint8_t)(n28b << 6) | (uint8_t)(igrid4 >> 10); + b77[8] = (igrid4 >> 2); + b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3); + + return 0; +} + +void packtext77(const char* text, uint8_t* b77) +{ + int length = strlen(text); + + // Skip leading and trailing spaces + while (*text == ' ' && *text != 0) + { + ++text; + --length; + } + while (length > 0 && text[length - 1] == ' ') + { + --length; + } + + // Clear the first 72 bits representing a long number + for (int i = 0; i < 9; ++i) + { + b77[i] = 0; + } + + // Now express the text as base-42 number stored + // in the first 72 bits of b77 + for (int j = 0; j < 13; ++j) + { + // Multiply the long integer in b77 by 42 + uint16_t x = 0; + for (int i = 8; i >= 0; --i) + { + x += b77[i] * (uint16_t)42; + b77[i] = (x & 0xFF); + x >>= 8; + } + + // Get the index of the current char + if (j < length) + { + int q = char_index(A0, text[j]); + x = (q > 0) ? q : 0; + } + else + { + x = 0; + } + // Here we double each added number in order to have the result multiplied + // by two as well, so that it's a 71 bit number left-aligned in 72 bits (9 bytes) + x <<= 1; + + // Now add the number to our long number + for (int i = 8; i >= 0; --i) + { + if (x == 0) + break; + x += b77[i]; + b77[i] = (x & 0xFF); + x >>= 8; + } + } + + // Set n3=0 (bits 71..73) and i3=0 (bits 74..76) + b77[8] &= 0xFE; + b77[9] &= 0x00; +} + +int pack77(const char* msg, uint8_t* c77) +{ + // Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P" + if (0 == pack77_1(msg, c77)) + { + return 0; + } + + // TODO: + // Check 0.5 (telemetry) + + // Check Type 4 (One nonstandard call and one hashed call) + + // Default to free text + // i3=0 n3=0 + packtext77(msg, c77); + return 0; +} + +#ifdef UNIT_TEST + +#include + +bool test1() +{ + const char* inputs[] = { + "", + " ", + "ABC", + "A9", + "L9A", + "L7BC", + "L0ABC", + "LL3JG", + "LL3AJG", + "CQ ", + 0 + }; + + for (int i = 0; inputs[i]; ++i) + { + int32_t result = ft8_v2::pack28(inputs[i]); + printf("pack28(\"%s\") = %d\n", inputs[i], result); + } + + return true; +} + +bool test2() +{ + const char* inputs[] = { + "CQ LL3JG", + "CQ LL3JG KO26", + "L0UAA LL3JG KO26", + "L0UAA LL3JG +02", + "L0UAA LL3JG RRR", + "L0UAA LL3JG 73", + 0 + }; + + for (int i = 0; inputs[i]; ++i) + { + uint8_t result[10]; + int rc = ft8_v2::pack77_1(inputs[i], result); + printf("pack77_1(\"%s\") = %d\t[", inputs[i], rc); + for (int j = 0; j < 10; ++j) + { + printf("%02x ", result[j]); + } + printf("]\n"); + } + + return true; +} + +int main() +{ + test1(); + test2(); + return 0; +} + +#endif \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/pack.h b/ft8CN/app/src/main/cpp/ft8/pack.h new file mode 100644 index 0000000..fbe1286 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/pack.h @@ -0,0 +1,12 @@ +#ifndef _INCLUDE_PACK_H_ +#define _INCLUDE_PACK_H_ + +#include + +// Pack FT8 text message into 72 bits +// [IN] msg - FT8 message (e.g. "CQ TE5T KN01") +// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first) +int pack77(const char* msg, uint8_t* c77); +void packtext77(const char* text, uint8_t* b77); + +#endif // _INCLUDE_PACK_H_ diff --git a/ft8CN/app/src/main/cpp/ft8/text.c b/ft8CN/app/src/main/cpp/ft8/text.c new file mode 100644 index 0000000..68393a9 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/text.c @@ -0,0 +1,253 @@ +#include "text.h" + +#include + +const char* trim_front(const char* str) +{ + // Skip leading whitespace + while (*str == ' ') + { + str++; + } + return str; +} + +void trim_back(char* str) +{ + // Skip trailing whitespace by replacing it with '\0' characters + int idx = strlen(str) - 1; + while (idx >= 0 && str[idx] == ' ') + { + str[idx--] = '\0'; + } +} + +// 1) trims a string from the back by changing whitespaces to '\0' +// 2) trims a string from the front by skipping whitespaces +char* trim(char* str) +{ + str = (char*)trim_front(str); + trim_back(str); + // return a pointer to the first non-whitespace character + return str; +} + +char to_upper(char c) +{ + return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; +} + +bool is_digit(char c) +{ + return (c >= '0') && (c <= '9'); +} + +bool is_letter(char c) +{ + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} + +bool is_space(char c) +{ + return (c == ' '); +} + +bool in_range(char c, char min, char max) +{ + return (c >= min) && (c <= max); +} + +bool starts_with(const char* string, const char* prefix) +{ + return 0 == memcmp(string, prefix, strlen(prefix)); +} + +bool equals(const char* string1, const char* string2) +{ + return 0 == strcmp(string1, string2); +} + +int char_index(const char* string, char c) +{ + for (int i = 0; *string; ++i, ++string) + { + if (c == *string) + { + return i; + } + } + return -1; // Not found +} + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in) +{ + char c; + char last_out = 0; + while ((c = *msg_in)) + { + if (c != ' ' || last_out != ' ') + { + last_out = to_upper(c); + *msg_out = last_out; + ++msg_out; + } + ++msg_in; + } + *msg_out = 0; // Add zero termination +} + +// Parse a 2 digit integer from string +int dd_to_int(const char* str, int length) +{ + int result = 0; + bool negative; + int i; + if (str[0] == '-') + { + negative = true; + i = 1; // Consume the - sign + } + else + { + negative = false; + i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found + } + + while (i < length) + { + if (str[i] == 0) + break; + if (!is_digit(str[i])) + break; + result *= 10; + result += (str[i] - '0'); + ++i; + } + + return negative ? -result : result; +} + +// Convert a 2 digit integer to string +void int_to_dd(char* str, int value, int width, bool full_sign) +{ + if (value < 0) + { + *str = '-'; + ++str; + value = -value; + } + else if (full_sign) + { + *str = '+'; + ++str; + } + + int divisor = 1; + for (int i = 0; i < width - 1; ++i) + { + divisor *= 10; + } + + while (divisor >= 1) + { + int digit = value / divisor; + + *str = '0' + digit; + ++str; + + value -= digit * divisor; + divisor /= 10; + } + *str = 0; // Add zero terminator +} + +// convert integer index to ASCII character according to one of 6 tables: +// table 0: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?" +// table 1: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 2: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 3: "0123456789" +// table 4: " ABCDEFGHIJKLMNOPQRSTUVWXYZ" +// table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/" +char charn(int c, int table_idx) +{ + if (table_idx != 2 && table_idx != 3) + { + if (c == 0) + return ' '; + c -= 1; + } + if (table_idx != 4) + { + if (c < 10) + return '0' + c; + c -= 10; + } + if (table_idx != 3) + { + if (c < 26) + return 'A' + c; + c -= 26; + } + + if (table_idx == 0) + { + if (c < 5) + return "+-./?"[c]; + } + else if (table_idx == 5) + { + if (c == 0) + return '/'; + } + + return '_'; // unknown character, should never get here +} + +// Convert character to its index (charn in reverse) according to a table +int nchar(char c, int table_idx) +{ + int n = 0; + if (table_idx != 2 && table_idx != 3) + { + if (c == ' ') + return n + 0; + n += 1; + } + if (table_idx != 4) + { + if (c >= '0' && c <= '9') + return n + (c - '0'); + n += 10; + } + if (table_idx != 3) + { + if (c >= 'A' && c <= 'Z') + return n + (c - 'A'); + n += 26; + } + + if (table_idx == 0) + { + if (c == '+') + return n + 0; + if (c == '-') + return n + 1; + if (c == '.') + return n + 2; + if (c == '/') + return n + 3; + if (c == '?') + return n + 4; + } + else if (table_idx == 5) + { + if (c == '/') + return n + 0; + } + + // Character not found + return -1; +} diff --git a/ft8CN/app/src/main/cpp/ft8/text.h b/ft8CN/app/src/main/cpp/ft8/text.h new file mode 100644 index 0000000..aac9921 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/text.h @@ -0,0 +1,37 @@ +#ifndef _INCLUDE_TEXT_H_ +#define _INCLUDE_TEXT_H_ + +#include +#include + +// Utility functions for characters and strings + +const char* trim_front(const char* str); +void trim_back(char* str); +char* trim(char* str); + +char to_upper(char c); +bool is_digit(char c); +bool is_letter(char c); +bool is_space(char c); +bool in_range(char c, char min, char max); +bool starts_with(const char* string, const char* prefix); +bool equals(const char* string1, const char* string2); + +int char_index(const char* string, char c); + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in); + +// Parse a 2 digit integer from string +int dd_to_int(const char* str, int length); + +// Convert a 2 digit integer to string +void int_to_dd(char* str, int value, int width, bool full_sign); + +char charn(int c, int table_idx); +int nchar(char c, int table_idx); + +#endif // _INCLUDE_TEXT_H_ diff --git a/ft8CN/app/src/main/cpp/ft8/unpack.c b/ft8CN/app/src/main/cpp/ft8/unpack.c new file mode 100644 index 0000000..7c12a90 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/unpack.c @@ -0,0 +1,626 @@ +#ifdef __linux__ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#endif + +#include "unpack.h" + +#include "text.h" +// #include //为防止警告 +#include +#include "hash22.h" + +#define MAX22 ((uint32_t)4194304L) +#define NTOKENS ((uint32_t)2063592L) +#define MAXGRID4 ((uint16_t)32400L) + + +// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the +// call sign bits from a packed message. +int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result,hashCode * hash) { + // Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa + hash->hash10=0; + hash->hash12=0; + hash->hash22=0; + if (n28 < NTOKENS) { + if (n28 <= 2) { + if (n28 == 0) + strcpy(result, "DE"); + if (n28 == 1) + strcpy(result, "QRZ"); + if (n28 == 2) + strcpy(result, "CQ"); + return 0; // Success + } + if (n28 <= 1002) { + // CQ_nnn with 3 digits + strcpy(result, "CQ "); + int_to_dd(result + 3, n28 - 3, 3, false); + return 0; // Success + } + if (n28 <= 532443L) { + // CQ_aaaa with 4 alphanumeric symbols + uint32_t n = n28 - 1003; + char aaaa[5]; + + aaaa[4] = '\0'; + for (int i = 3; /* */; --i) { + aaaa[i] = charn(n % 27, 4); + if (i == 0) + break; + n /= 27; + } + + strcpy(result, "CQ "); + strcat(result, trim_front(aaaa)); + return 0; // Success + } + // ? TODO: unspecified in the WSJT-X code + return -1; + } + + n28 = n28 - NTOKENS; + if (n28 < MAX22) { + hash->hash10=n28;//把哈希值保存下来 + hash->hash12=n28;//把哈希值保存下来 + hash->hash22=n28;//把哈希值保存下来 + + LOG_PRINTF("N28 HASH: %0x",n28); + + // This is a 22-bit hash of a result + // TODO: implement + strcpy(result, "<...>"); + // result[0] = '<'; + // int_to_dd(result + 1, n28, 7, false); + // result[8] = '>'; + // result[9] = '\0'; + return 0; + } + + // Standard callsign + uint32_t n = n28 - MAX22; + + char callsign[7]; + callsign[6] = '\0'; + callsign[5] = charn(n % 27, 4); + n /= 27; + callsign[4] = charn(n % 27, 4); + n /= 27; + callsign[3] = charn(n % 27, 4); + n /= 27; + callsign[2] = charn(n % 10, 3); + n /= 10; + callsign[1] = charn(n % 36, 2); + n /= 36; + callsign[0] = charn(n % 37, 1); + + // Skip trailing and leading whitespace in case of a short callsign + //短呼号时跳过尾随和前导空格 + strcpy(result, trim(callsign)); + if (strlen(result) == 0) + return -1; + + hash->hash10=hashcall_10(result);//对呼号进行22位的哈希 + hash->hash12=hashcall_12(result);//对呼号进行22位的哈希 + hash->hash22=hashcall_22(result);//对呼号进行22位的哈希 + // Check if we should append /R or /P suffix + //检查是否应附加/R或/P后缀 + if (ip) { + if (i3 == 1) { + strcat(result, "/R"); + } else if (i3 == 2) { + strcat(result, "/P"); + } + } + + return 0; // Success +} + +//int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, char *extra) { +// uint32_t n28a, n28b; +// uint16_t igrid4; +// uint8_t ir; +// +// // Extract packed fields +// n28a = (a77[0] << 21); +// n28a |= (a77[1] << 13); +// n28a |= (a77[2] << 5); +// n28a |= (a77[3] >> 3); +// n28b = ((a77[3] & 0x07) << 26); +// n28b |= (a77[4] << 18); +// n28b |= (a77[5] << 10); +// n28b |= (a77[6] << 2); +// n28b |= (a77[7] >> 6); +// ir = ((a77[7] & 0x20) >> 5); +// igrid4 = ((a77[7] & 0x1F) << 10); +// igrid4 |= (a77[8] << 2); +// igrid4 |= (a77[9] >> 6); +// +// // Unpack both callsigns +// if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to) < 0) { +// return -1; +// } +// if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de) < 0) { +// return -2; +// } +// // Fix "CQ_" to "CQ " -> already done in unpack_callsign() +// +// // TODO: add to recent calls +// // if (call_to[0] != '<' && strlen(call_to) >= 4) { +// // save_hash_call(call_to) +// // } +// // if (call_de[0] != '<' && strlen(call_de) >= 4) { +// // save_hash_call(call_de) +// // } +// +// char *dst = extra; +// +// if (igrid4 <= MAXGRID4) { +// // Extract 4 symbol grid locator +// if (ir > 0) { +// // In case of ir=1 add an "R" before grid +// //dst = stpcpy(dst, "R ");//除错 +// dst = strcpy(dst, "R "); +// dst += 3; +// } +// +// uint16_t n = igrid4; +// dst[4] = '\0'; +// dst[3] = '0' + (n % 10); +// n /= 10; +// dst[2] = '0' + (n % 10); +// n /= 10; +// dst[1] = 'A' + (n % 18); +// n /= 18; +// dst[0] = 'A' + (n % 18); +// // if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1; +// } else { +// // Extract report +// int irpt = igrid4 - MAXGRID4; +// +// // Check special cases first (irpt > 0 always) +// switch (irpt) { +// case 1: +// extra[0] = '\0'; +// break; +// case 2: +// strcpy(dst, "RRR"); +// break; +// case 3: +// strcpy(dst, "RR73"); +// break; +// case 4: +// strcpy(dst, "73"); +// break; +// default: +// // Extract signal report as a two digit number with a + or - sign +// if (ir > 0) { +// *dst++ = 'R'; // Add "R" before report +// } +// int_to_dd(dst, irpt - 35, 2, true); +// break; +// } +// // if (irpt >= 2 && strncmp(call_to, "CQ", 2) == 0) return -1; +// } +// +// return 0; // Success +//} + + +int unpack_type1_(const uint8_t *a77, message_t *message) { + uint32_t n28a, n28b; + uint16_t igrid4; + uint8_t ir; + + // Extract packed fields + n28a = (a77[0] << 21); + n28a |= (a77[1] << 13); + n28a |= (a77[2] << 5); + n28a |= (a77[3] >> 3); + n28b = ((a77[3] & 0x07) << 26); + n28b |= (a77[4] << 18); + n28b |= (a77[5] << 10); + n28b |= (a77[6] << 2); + n28b |= (a77[7] >> 6); + ir = ((a77[7] & 0x20) >> 5); + igrid4 = ((a77[7] & 0x1F) << 10); + igrid4 |= (a77[8] << 2); + igrid4 |= (a77[9] >> 6); + + + + // Unpack both callsigns + if (unpack_callsign(n28a >> 1, n28a & 0x01, message->i3, message->call_to,&message->call_to_hash) < 0) { + return -1; + } + if (unpack_callsign(n28b >> 1, n28b & 0x01, message->i3, message->call_de,&message->call_de_hash) < 0) { + return -2; + } + // Fix "CQ_" to "CQ " -> already done in unpack_callsign() + + // TODO: add to recent calls + // if (call_to[0] != '<' && strlen(call_to) >= 4) { + // save_hash_call(call_to) + // } + // if (call_de[0] != '<' && strlen(call_de) >= 4) { + // save_hash_call(call_de) + // } + + char *dst = message->extra; + message->report=-100;//-100说明没有信号报告 + message->maidenGrid[0]='\0'; + if (igrid4 <= MAXGRID4) { + // // 解码4字符的梅登海德网格,!!!网格数据优先于信号报告 + if (ir > 0) { + // In case of ir=1 add an "R" before grid + dst = strcpy(dst, "R "); + dst += 3; + } + + uint16_t n = igrid4; + dst[4] = '\0'; + dst[3] = '0' + (n % 10); + n /= 10; + dst[2] = '0' + (n % 10); + n /= 10; + dst[1] = 'A' + (n % 18); + n /= 18; + dst[0] = 'A' + (n % 18); + strcpy(message->maidenGrid, dst);//把网格内容复制出来 + + + // if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1; + } else { + // 解码信号报告,数值-35是报告。 + // 网格数据优先于信号报告 + message->report = igrid4 - MAXGRID4 - 35; + + // Check special cases first (irpt > 0 always) + switch (message->report) { + case 1 - 35: + message->extra[0] = '\0'; + break; + case 2 - 35: + strcpy(dst, "RRR"); + break; + case 3 - 35: + strcpy(dst, "RR73"); + break; + case 4 - 35: + strcpy(dst, "73"); + break; + default: + // Extract signal report as a two digit number with a + or - sign + if (ir > 0) { + *dst++ = 'R'; // Add "R" before report + } + int_to_dd(dst, message->report, 2, true); + break; + } + // if (irpt >= 2 && strncmp(call_to, "CQ", 2) == 0) return -1; + } + + + + return 0; // Success +} + + +int unpack_text(const uint8_t *a71, char *text) { + // TODO: test + uint8_t b71[9]; + + // Shift 71 bits right by 1 bit, so that it's right-aligned in the byte array + uint8_t carry = 0; + for (int i = 0; i < 9; ++i) { + b71[i] = carry | (a71[i] >> 1); + carry = (a71[i] & 1) ? 0x80 : 0; + } + + char c14[14]; + c14[13] = 0; + for (int idx = 12; idx >= 0; --idx) { + // Divide the long integer in b71 by 42 + uint16_t rem = 0; + for (int i = 0; i < 9; ++i) { + rem = (rem << 8) | b71[i]; + b71[i] = rem / 42; + rem = rem % 42; + } + c14[idx] = charn(rem, 0); + } + + strcpy(text, trim(c14)); + return 0; // Success +} + +int unpack_telemetry(const uint8_t *a71, char *telemetry) { + uint8_t b71[9]; + + // Shift bits in a71 right by 1 bit + uint8_t carry = 0; + for (int i = 0; i < 9; ++i) { + b71[i] = (carry << 7) | (a71[i] >> 1); + carry = (a71[i] & 0x01); + } + + // Convert b71 to hexadecimal string + for (int i = 0; i < 9; ++i) { + uint8_t nibble1 = (b71[i] >> 4); + uint8_t nibble2 = (b71[i] & 0x0F); + char c1 = (nibble1 > 9) ? (nibble1 - 10 + 'A') : nibble1 + '0'; + char c2 = (nibble2 > 9) ? (nibble2 - 10 + 'A') : nibble2 + '0'; + telemetry[i * 2] = c1; + telemetry[i * 2 + 1] = c2; + } + + telemetry[18] = '\0'; + return 0; +} + +//none standard for wsjt-x 2.0 +//by KD8CEC +int unpack_nonstandard(const uint8_t *a77, message_t *message) { + uint32_t n12, iflip, nrpt, icq; + uint64_t n58; + n12 = (a77[0] << 4); //11 ~4 : 8 + n12 |= (a77[1] >> 4); //3~0 : 12 + uint32_t h12=a77[0]; + + + + + n58 = ((uint64_t) (a77[1] & 0x0F) << 54); //57 ~ 54 : 4 + n58 |= ((uint64_t) a77[2] << 46); //53 ~ 46 : 12 + n58 |= ((uint64_t) a77[3] << 38); //45 ~ 38 : 12 + n58 |= ((uint64_t) a77[4] << 30); //37 ~ 30 : 12 + n58 |= ((uint64_t) a77[5] << 22); //29 ~ 22 : 12 + n58 |= ((uint64_t) a77[6] << 14); //21 ~ 14 : 12 + n58 |= ((uint64_t) a77[7] << 6); //13 ~ 6 : 12 + n58 |= ((uint64_t) a77[8] >> 2); //5 ~ 0 : 765432 10 + + + iflip = (a77[8] >> 1) & 0x01; //76543210 + nrpt = ((a77[8] & 0x01) << 1); + nrpt |= (a77[9] >> 7); //76543210 + icq = ((a77[9] >> 6) & 0x01); + + + if (iflip==1){//h1==1 + message->call_de_hash.hash12=n12; + } else{ + message->call_to_hash.hash12=n12; + } + + char c11[12]; + c11[11] = '\0'; + + for (int i = 10; /* no condition */; --i) { + c11[i] = charn(n58 % 38, 5); + if (i == 0) + break; + n58 /= 38; + } + + char call_3[15]; + // should replace with hash12(n12, call_3); + strcpy(call_3, "<...>"); + // call_3[0] = '<'; + // int_to_dd(call_3 + 1, n12, 4, false); + // call_3[5] = '>'; + // call_3[6] = '\0'; + + char *call_1 = (iflip) ? c11 : call_3; + char *call_2 = (iflip) ? call_3 : c11; + //save_hash_call(c11_trimmed); + + + + if (icq == 0) { + strcpy(message->call_to, trim(call_1)); + if (nrpt == 1) + strcpy(message->extra, "RRR"); + else if (nrpt == 2) + strcpy(message->extra, "RR73"); + else if (nrpt == 3) + strcpy(message->extra, "73"); + else { + message->extra[0] = '\0'; + } + } else { + strcpy(message->call_to, "CQ"); + message->extra[0] = '\0'; + } + strcpy(message->call_de, trim(call_2)); + + return 0; +} + +//int unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extra) { +// call_to[0] = call_de[0] = extra[0] = '\0'; +// +// // Extract i3 (bits 74..76) +// uint8_t i3 = (a77[9] >> 3) & 0x07; +// +// if (i3 == 0) { +// // Extract n3 (bits 71..73) +// uint8_t n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03); +// +// if (n3 == 0) { +// // 0.0 Free text +// return unpack_text(a77, extra); +// } +// // else if (i3 == 0 && n3 == 1) { +// // // 0.1 K1ABC RR73; W9XYZ -11 28 28 10 5 71 DXpedition Mode +// // } +// // else if (i3 == 0 && n3 == 2) { +// // // 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest +// // } +// // else if (i3 == 0 && (n3 == 3 || n3 == 4)) { +// // // 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day +// // // 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day +// // } +// else if (n3 == 5) { +// // 0.5 0123456789abcdef01 71 71 Telemetry (18 hex) +// return unpack_telemetry(a77, extra); +// } +// } else if (i3 == 1 || i3 == 2) { +// // Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest) +// return unpack_type1(a77, i3, call_to, call_de, extra); +// } +// // else if (i3 == 3) { +// // // Type 3: ARRL RTTY Contest +// // } +// else if (i3 == 4) { +// // // Type 4: Nonstandard calls, e.g. PJ4/KA1ABC RR73 +// // // One hashed call or "CQ"; one compound or nonstandard call with up +// // // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73. +// return unpack_nonstandard(a77, call_to, call_de, extra); +// } +// // else if (i3 == 5) { +// // // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest +// // } +// +// // unknown type, should never get here +// return -1; +//} +// + + + + +//int unpack77(const uint8_t* a77, char* message) +//{ +// char call_to[14]; +// char call_de[14]; +// char extra[19]; +// +// //// TO-DO:在此处修改。把Message的格式修改成i3,n3,from,to,extra,方便消息显示。 +// int rc = unpack77_fields(a77, call_to, call_de, extra); +// if (rc < 0) +// return rc; +// +// // int msg_sz = strlen(call_to) + strlen(call_de) + strlen(extra) + 2; +// char* dst = message; +// +// dst[0] = '\0'; +// +// if (call_to[0] != '\0') +// { +// // dst = stpcpy(dst, call_to); //除错 +// dst = strcpy(dst, call_to); +// dst +=strlen(call_to); +// *dst++ = ' '; +// } +// +// if (call_de[0] != '\0') +// { +// // dst = stpcpy(dst, call_de);//除错 +// dst = strcpy(dst, call_de); +// dst +=strlen(call_de); +// *dst++ = ' '; +// } +// +// // dst = stpcpy(dst, extra);//除错 +// dst = strcpy(dst, extra); +// dst += strlen(extra); +// *dst = '\0'; +// +// return 0; +//} + + + +int unpack77_fields_(const uint8_t *a77, message_t *message) { + + //获取指令类型i3.n3 + message->i3 = (a77[9] >> 3) & 0x07; + message->n3 = 0; + + if (message->i3 == 0) { + //n3指令 + message->n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03); + + if (message->n3 == 0) { + // 0.0 Free text + return unpack_text(a77, message->extra); + } + // else if (i3 == 0 && n3 == 1) { + // // 0.1 K1ABC RR73; W9XYZ -11 28 28 10 5 71 DXpedition Mode + // } + // else if (i3 == 0 && n3 == 2) { + // // 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest + // } + // else if (i3 == 0 && (n3 == 3 || n3 == 4)) { + // // 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day + // // 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day + // } + else if (message->n3 == 5) { + // 0.5 0123456789abcdef01 71 71 Telemetry (18 hex) + return unpack_telemetry(a77, message->extra); + } + } else if (message->i3 == 1 || message->i3 == 2) { + // Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest) + return unpack_type1_(a77, message); + } + // else if (i3 == 3) { + // // Type 3: ARRL RTTY Contest + // } + else if (message->i3 == 4) { + // // Type 4: Nonstandard calls, e.g. PJ4/KA1ABC RR73 + // // One hashed call or "CQ"; one compound or nonstandard call with up + // // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73. + //return unpack_nonstandard(a77, message->call_to, message->call_de, message->extra); + return unpack_nonstandard(a77, message); + } + // else if (i3 == 5) { + // // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest + // } + + // unknown type, should never get here + return -1; + + +} + + +//把77位消息解码到message_t +int unpackToMessage_t(const uint8_t *a77, message_t *message) { + //char call_to[14]; + //char call_de[14]; + //char extra[19]; + + //// TO-DO:在此处修改。把Message的格式修改成i3,n3,from,to,extra,方便消息显示。 + int rc = unpack77_fields_(a77, message); + + if (rc < 0) + return rc; + + + // int msg_sz = strlen(call_to) + strlen(call_de) + strlen(extra) + 2; + char *dst = message->text; + + message->text[0] = '\0'; + + if (message->call_to[0] != '\0') { + // dst = stpcpy(dst, call_to); //除错 + dst = strcpy(dst, message->call_to); + dst += strlen(message->call_to); + *dst++ = ' '; + } + + if (message->call_de[0] != '\0') { + // dst = stpcpy(dst, call_de);//除错 + dst = strcpy(dst, message->call_de); + dst += strlen(message->call_de); + *dst++ = ' '; + } + + // dst = stpcpy(dst, extra);//除错 + dst = strcpy(dst, message->extra); + dst += strlen(message->extra); + *dst = '\0'; + + return 0; +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8/unpack.h b/ft8CN/app/src/main/cpp/ft8/unpack.h new file mode 100644 index 0000000..2c716f6 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8/unpack.h @@ -0,0 +1,19 @@ +#ifndef _INCLUDE_UNPACK_H_ +#define _INCLUDE_UNPACK_H_ + +#include +#include "decode.h" +// field1 - at least 14 bytes +// field2 - at least 14 bytes +// field3 - at least 7 bytes + +//int unpack77_fields(const uint8_t* a77, char* field1, char* field2, char* field3); + +// message should have at least 35 bytes allocated (34 characters + zero terminator) +/// 消息应至少分配35个字节(34个字符+零终止符) +int unpack77(const uint8_t* a77, char* message); + +//新增的函数,把消息解码成message_t +int unpackToMessage_t(const uint8_t* a77, message_t* message); + +#endif // _INCLUDE_UNPACK_H_ diff --git a/ft8CN/app/src/main/cpp/ft8Decoder.c b/ft8CN/app/src/main/cpp/ft8Decoder.c new file mode 100644 index 0000000..5f59ad1 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Decoder.c @@ -0,0 +1,292 @@ +// +// Created by jmsmf on 2022/4/24. +// + +#include "ft8Decoder.h" + +#define LOG_LEVEL LOG_INFO +//decoder_t decoder; + +// Hanning窗(汉宁窗)适用于95%的情况。 +static float hann_i(int i, int N) { + float x = sinf((float) M_PI * i / N); + return x * x; +} + +//把信号FFT,在解码decoder中减去信号 +void signalToFFT(decoder_t *decoder, float signal[], int sample_rate) { + int nfft = kFreq_osr * (int) (sample_rate * FT8_SYMBOL_PERIOD);//nfft=一个FSK符号的样本数*频率过采样率 + float fft_norm = 2.0f / nfft;//< FFT归一化因子。FFT normalization factor + float *window = (float *) malloc( + nfft * sizeof(window[0]));// 申请窗空间,大小是fft块大小*sizeof(me->windows[0]) + for (int i = 0; i < nfft; ++i) { + window[i] = hann_i(i, nfft);// 使用Hanning窗 + // window[i] = blackman_i(i, me->nfft);// Blackman-Harris窗 + // window[i] = hamming_i(i, me->nfft);// Hamming窗 + // window[i] = (i < len_window) ? hann_i(i, len_window) : 0; + } + + // 申请当前STFT(短时傅氏变换)分析框架(nfft样本)的空间。 + //last_frame:申请傅里叶变换分析框架用的(nfft样本)。 + // 空间大小=时域数据量*数据类型占用的空间=一个FSK符号占用的采样数据量*频率过采样率=12000*0.16*2*SizeOf(float) + float *last_frame = (float *) malloc(nfft * sizeof(last_frame[0])); + + size_t fft_work_size; + // 第一步,获取可以用的FFT工作区域的大小到fft_work_size + //nfft=一个FSK符号的样本数*频率过采样率=0.16*12000*2=3840 + kiss_fftr_alloc(nfft, 0, 0, &fft_work_size); + + // 申请FFT工作区域的内存,38676个 + void *fft_work = malloc(fft_work_size); + //第二步,返回fft的设置信息 + kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size); + + + free(fft_work); + free(window); + free(last_frame); + +} + +void *init_decoder(int64_t utcTime, int sample_rate, int num_samples, bool is_ft8) { + + //此处,改为一个变量,不是以指针,申请新内存的方式处理了。 + //其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了 + + //此处不用申请内存的方式解决 +// decoder.utcTime = utcTime; +// decoder.num_samples = num_samples; +// decoder.mon_cfg = (monitor_config_t) { +// .f_min = 100,//分析的最低频率边界 +// .f_max = 3000,//分析的最大频率边界 +// .sample_rate = sample_rate,//采样率12000Hz +// .time_osr = kTime_osr,//时间过采样率=2 +// .freq_osr = kFreq_osr,//频率过采样率=2 +// .protocol = is_ft8 ? PROTO_FT8 : PROTO_FT4 +// }; +// //LOGD("Init decoder . address : %lld", decoder); +// monitor_init(&decoder.mon, &decoder.mon_cfg); +// +// +// return &decoder; + + //此部分,是老的解决方式,是动态申请内存。 + decoder_t *decoder; + decoder = malloc(sizeof(decoder_t)); + decoder->utcTime = utcTime; + decoder->num_samples = num_samples; + decoder->mon_cfg = (monitor_config_t) { + .f_min = 100,//分析的最低频率边界 + .f_max = 3000,//分析的最大频率边界 + .sample_rate = sample_rate,//采样率12000Hz + .time_osr = kTime_osr,//时间过采样率=2 + .freq_osr = kFreq_osr,//频率过采样率=2 + .protocol = is_ft8 ? PROTO_FT8 : PROTO_FT4 + }; + + decoder->kLDPC_iterations = fast_kLDPC_iterations; + //LOGD("Init decoder . address : %lld", decoder); + monitor_init(&decoder->mon, &decoder->mon_cfg); + + return decoder; +} + +void delete_decoder(decoder_t *decoder) { + //LOGD("Free decoder , address:%lld", decoder); + monitor_free(&decoder->mon); + free(decoder); +} + +void decoder_monitor_press(float signal[], decoder_t *decoder) { + + // 以每一个FSK符号占用的数据量为单位循环。 + //block_size每个符号的样本数12000*0.16=1920 + + for (int frame_pos = 0; + frame_pos + decoder->mon.block_size <= + decoder->num_samples; frame_pos += decoder->mon.block_size) { + // Process the waveform data frame by frame - you could have a live loop here with data from an audio device + // 逐帧处理波形数据,这个位置可以使用音频设备的数据环。 + //以每一个符号时间长度(0.16)内的数据做瀑布数据,最后会形成一个信号量mag数组。 + // mag数组的总长度是:最大符号块数93*时间过采样率2*频率过采样率2*分析块960,也就是Waterfall size = 357120 + //一次调用monitor_process,处理的是一个符号长度的信号量数据,生成2*2*960=3840个mag数据。 + //一共有93个符号的循环,mag数组的大小:2*2*960*93=357120 + //mag数据保存在monitor.wf.mag中。 + monitor_process(&decoder->mon, signal + frame_pos); + } + + // /data/user/0/com.bg7yoz.ft8cn/cache/ + + //把fft数据保存下来 + //FILE * fp2 = fopen("/data/user/0/com.bg7yoz.ft8cn/cache/fft001.txt", "w");//打开输出文件 + //for (int i = 0; i < 3840; ++i) { + // for (int j = 0; j < 93; ++j) { + // fprintf (fp2,"%d\n", decoder->mon.wf.mag[i*j]);//把数组a逆序写入到输出文件当中 + // } + //} + //fclose(fp2);//关闭输出文件,相当于保存 + + + + LOG(LOG_DEBUG, "Waterfall accumulated %d symbols\n", decoder->mon.wf.num_blocks);//积累的信号 + LOG(LOG_INFO, "Max magnitude: %.1f dB\n", decoder->mon.max_mag);//最大信号值dB + +} + +int decoder_ft8_find_sync(decoder_t *decoder) { + //检测ft8信号,kMax_candidates最大候选人数量=120,candidate_list候选人列表(size=120),kMin_score候选人的最低同步分数阈值=10 + decoder->num_candidates = ft8_find_sync(&decoder->mon.wf, kMax_candidates, + decoder->candidate_list, kMin_score); + LOG(LOG_DEBUG, "ft8_find_sync finished. %d candidates\n", decoder->num_candidates); + + + // Hash table for decoded messages (to check for duplicates) + // 解码消息的哈希表(用于检查重复项) + //int num_decoded = 0; + //message_t decoded[kMax_decoded_messages];//kMax_decoded_messages=50 + // 哈希表指针列表(指针数组) + //message_t *decoded_hashtable[kMax_decoded_messages]; + + // Initialize hash table pointers + // 初始化哈希表指针列表 + for (int i = 0; i < kMax_decoded_messages; ++i) { + decoder->decoded_hashtable[i] = NULL; + } + return decoder->num_candidates; +} + + +ft8_message decoder_ft8_analysis(int idx, decoder_t *decoder) { + + ft8_message ft8Message; + ft8Message.isValid = false; + ft8Message.utcTime = decoder->utcTime; + // 候选列表candidate_list,已经从ft8_fing_sync获得。 + ft8Message.candidate = decoder->candidate_list[idx]; + + + if (ft8Message.candidate.score < kMin_score) { + //ft8Message.isValid = false; + return ft8Message; + } + + ft8Message.freq_hz = + (ft8Message.candidate.freq_offset + + (float) ft8Message.candidate.freq_sub / decoder->mon.wf.freq_osr) / + decoder->mon.symbol_period; + ft8Message.time_sec = + ((ft8Message.candidate.time_offset + (float) ft8Message.candidate.time_sub) + * decoder->mon.symbol_period) / decoder->mon.wf.time_osr; + + //ft8Message.snr=ft8Message.candidate.snr; + //这是原来代码的时间偏移,同样的数据与JTDX的时间差异很大,改用上面的代码,稍微接近一些 + //(ft8Message.candidate.time_offset + + //(float) ft8Message.candidate.time_sub / decoder->mon.wf.time_osr) * + //decoder->mon.symbol_period; + + + // 如果解码失败,跳到下一次循环 kLDPC_iterations=20 LDPC(低密度奇偶校验)的迭代次数。 + if (!ft8_decode(&decoder->mon.wf, &ft8Message.candidate + //, &ft8Message.message, kLDPC_iterations, + , &ft8Message.message, decoder->kLDPC_iterations, + &ft8Message.status)) { + // printf("000000 %3d %+4.2f %4.0f ~ ---\n", cand->score, time_sec, freq_hz); + if (ft8Message.status.ldpc_errors > 0) { + // LDPC:低密度奇偶校验 + LOG(LOG_DEBUG, "LDPC decode: %d errors\n", ft8Message.status.ldpc_errors); + } else if (ft8Message.status.crc_calculated != ft8Message.status.crc_extracted) { + LOG(LOG_DEBUG, "CRC mismatch!\n"); + } else if (ft8Message.status.unpack_status != 0) { + LOG(LOG_DEBUG, "Error while unpacking!\n"); + } + //ft8Message.isValid = false; + return ft8Message; + } + + ft8Message.snr = ft8Message.candidate.snr; + + LOG(LOG_DEBUG, "Checking hash table for %4.1fs / %4.1fHz [%d]...\n", ft8Message.time_sec, + ft8Message.freq_hz, + ft8Message.candidate.score); + + int idx_hash = + ft8Message.message.hash % kMax_decoded_messages;//为啥是取模?稍后研究kMax_decoded_messages=50 + + bool found_empty_slot = false; + bool found_duplicate = false; + //检查哈希表,只有空插槽,或重复插槽(哈希值相同,并且消息相同) + do { + if (decoder->decoded_hashtable[idx_hash] == NULL) { + LOG(LOG_DEBUG, "Found an empty slot\n"); + found_empty_slot = true; + } else if ((decoder->decoded_hashtable[idx_hash]->hash == ft8Message.message.hash) && + (0 == + strcmp(decoder->decoded_hashtable[idx_hash]->text, ft8Message.message.text))) { + LOG(LOG_DEBUG, "Found a duplicate [%s]\n", ft8Message.message.text); + found_duplicate = true; + } else { + LOG(LOG_DEBUG, "Hash table clash!\n"); + // Move on to check the next entry in hash table + idx_hash = (idx_hash + 1) % kMax_decoded_messages; + } + } while (!found_empty_slot && !found_duplicate); + + + if (found_empty_slot) { + // Fill the empty hashtable slot + memcpy(&decoder->decoded[idx_hash], &ft8Message.message, sizeof(ft8Message.message)); + decoder->decoded_hashtable[idx_hash] = &decoder->decoded[idx_hash]; + ++decoder->num_decoded; + + + ft8Message.isValid = true; + + LOG_PRINTF("%3d %+4.2f %4.0f ~ %s report:%d grid:%s,toHash:%x,fromHash:%x", + ft8Message.snr, + ft8Message.time_sec, ft8Message.freq_hz, ft8Message.message.text, + ft8Message.message.report, ft8Message.message.maidenGrid, + ft8Message.message.call_to_hash.hash12, ft8Message.message.call_de_hash.hash12); + } + memcpy(decoder->a91, ft8Message.message.a91, FTX_LDPC_K_BYTES); + return ft8Message; +} + +void decoder_ft8_reset(decoder_t *decoder, long utcTime, int num_samples) { + LOG(LOG_DEBUG, "Monitor is resetting..."); + decoder->mon.wf.num_blocks = 0; + //decoder->mon.max_mag = 0; + decoder->mon.max_mag = -120.0f; + decoder->utcTime = utcTime; + decoder->num_samples = num_samples; +} + +/** + * 对174码,重新编生成79码 + * @param a174 174个int + * @param a79 79个int + */ +void recode(int a174[], int a79[]) { + int i174 = 0; + //int costas[] = { 3, 1, 4, 0, 6, 5, 2 }; + //std::vector out79; + for (int i79 = 0; i79 < 79; i79++) { + if (i79 < 7) { + //out79.push_back(costas[i79]); + a79[i79] = kFT8CostasPattern[i79]; + } else if (i79 >= 36 && i79 < 36 + 7) { + //out79.push_back(costas[i79-36]); + a79[i79] = kFT8CostasPattern[i79 - 36]; + } else if (i79 >= 72) { + //out79.push_back(costas[i79-72]); + a79[i79] = kFT8CostasPattern[i79 - 72]; + } else { + int sym = (a174[i174 + 0] << 2) | (a174[i174 + 1] << 1) | (a174[i174 + 2] << 0); + i174 += 3; + // gray code + int map[] = {0, 1, 3, 2, 5, 6, 4, 7}; + sym = map[sym]; + //out79.push_back(sym); + a79[i79] = sym; + } + } +}; diff --git a/ft8CN/app/src/main/cpp/ft8Decoder.h b/ft8CN/app/src/main/cpp/ft8Decoder.h new file mode 100644 index 0000000..d8e88e1 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Decoder.h @@ -0,0 +1,69 @@ +// +// Created by jmsmf on 2022/4/24. +// +// +#include "ft8/decode.h" +#include "monitor_opr.h" +#include "ft8/constants.h" +#include + +//const int kMin_score = 10; // 候选人的最低同步分数阈值。Minimum sync score threshold for candidates +const int kMax_candidates = 120;//最大候选人数量 + +const int kMax_decoded_messages = 100; +//const int kMax_decoded_messages = 50; +const int kLDPC_iterations = 20;//LDPC(低密度奇偶校验)的迭代次数,数值越大,精度越高,速度越慢 +const int deep_kLDPC_iterations = 200;//LDPC(低密度奇偶校验)的迭代次数 +const int fast_kLDPC_iterations = 20;//LDPC(低密度奇偶校验)的迭代次数 + +typedef struct { + long long utcTime;//UTC时间 + int num_samples;//采样率 + int num_candidates; + int num_decoded; + message_t decoded[kMax_decoded_messages];//kMax_decoded_messages=50 + // 哈希表指针列表(指针数组) + message_t *decoded_hashtable[kMax_decoded_messages]; + + // Find top candidates by Costas sync score and localize them in time and frequency + // 从科斯塔斯阵列(Costas)寻找最佳候选,并在时间和频率上对其进行本地化。候选数组最大120个 + // candidate_t定义在decode.h + candidate_t candidate_list[kMax_candidates];//kMax_candidates=120 + + monitor_t mon; + monitor_config_t mon_cfg; + uint8_t a91[FTX_LDPC_K_BYTES];//用于生成减法代码的数据 + int kLDPC_iterations;//ldpc 迭代次数,数值越大,精度越高,速度越慢:20或100 +} decoder_t; + +typedef struct { + int64_t utcTime;//消息的UTC时间 + bool isValid;//是否为有效消息 + int snr;//信噪比 + candidate_t candidate;//消息的原始信号数据 + float time_sec;//时间偏移值 + float freq_hz;//频率偏移值 + message_t message;//解码后的消息 + decode_status_t status; +} ft8_message; + +static const int kFreq_osr = 2; // 频率过采样率。Frequency oversampling rate (bin subdivision) +static const int kTime_osr = 2; // 时间过采样率。Time oversampling rate (symbol subdivision) + +//把信号FFT,在解码decoder中减去信号 +void signalToFFT(decoder_t *decoder,float signal[], int sample_rate); +//初始化解码器所需要的参数,最后通过指针的方式传递给java +void *init_decoder(int64_t utcTime, int sample_rate, int num_samples, bool is_ft8); + +void delete_decoder(decoder_t *decoder); + +void decoder_monitor_press(float signal[], decoder_t *decoder); + +int decoder_ft8_find_sync(decoder_t *decoder); + +ft8_message decoder_ft8_analysis(int idx, decoder_t *decoder); + +void decoder_ft8_reset(decoder_t *decoder,long utcTime,int num_samples); + +void recode(int a174[],int a79[]); + diff --git a/ft8CN/app/src/main/cpp/ft8Encoder.c b/ft8CN/app/src/main/cpp/ft8Encoder.c new file mode 100644 index 0000000..9363e51 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Encoder.c @@ -0,0 +1,150 @@ +// +// Created by jmsmf on 2022/6/1. +// + +#include "ft8Encoder.h" + +//#define LOG_LEVEL LOG_INFO + +#define FT8_SYMBOL_BT 2.0f /// 符号平滑滤波器带宽因子(BT) +#define FT4_SYMBOL_BT 1.0f /// 符号平滑滤波器带宽因子(BT) +#define GFSK_CONST_K 5.336446f ///< == pi * sqrt(2 / log(2)) + + +/// 生成高斯平滑脉冲 +/// 脉冲理论上是无限长的,然而,这里它被截断为符号长度的3倍。 +/// 这意味着脉冲阵列必须有空间容纳3*n_spsym元素。 +/// @param[in] n_spsym 每个符号的样本数 Number of samples per symbol +/// @param[in] b 形状参数(为FT8/FT4定义的值) +/// @param[out] pulse 脉冲采样输出阵列 +/// +void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse) { + for (int i = 0; i < 3 * n_spsym; ++i) { + float t = i / (float) n_spsym - 1.5f; + float arg1 = GFSK_CONST_K * symbol_bt * (t + 0.5f); + float arg2 = GFSK_CONST_K * symbol_bt * (t - 0.5f); + pulse[i] = (erff(arg1) - erff(arg2)) / 2; + } +} + + +/// 使用GFSK相位整形合成波形数据。 +/// 输出波形将包含n_sym个符号。 +/// @param[in] symbols 符号(音调)数组 (0-7 for FT8) +/// @param[in] n_sym 符号数组中的符号数 +/// @param[in] f0 符号0的音频频率(赫兹) (载波频率) +/// @param[in] symbol_bt 符号平滑滤波器带宽 (2 for FT8, 1 for FT4) +/// @param[in] symbol_period 符号周期(持续时间),秒 +/// @param[in] signal_rate 合成信号的采样率,赫兹 +/// @param[out] signal 信号波形样本的输出阵列(应为n_sym*n_spsym样本留出空间) +/// +void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_period, + int signal_rate, float *signal) { + int n_spsym = (int) (0.5f + (float)signal_rate * symbol_period); // 每个符号的样本数12000*0.16=1920 + int n_wave = n_sym * n_spsym; // 输出样本数79*1920=151680 + float hmod = 1.0f; + + + // 计算平滑的频率波形。 + // Length = (nsym+2)*n_spsym samples, 首个和最后一个扩展符号 + float dphi_peak = 2 * M_PI * hmod / n_spsym; + + //此处是与采样率有关,采样率提高后,可能会有闪退的问题 + float *dphi;//此处使用内存申请的方式,而不是原来数组的方式,因为是采样率过高时,会报内存出错。 + dphi = malloc(sizeof(float) * (n_wave + 2 * n_spsym)); + if (dphi==0) return;//内存申请失败 + //float dphi[n_wave + 2 * n_spsym];//原来的方式 + + // 频率上移f0 + for (int i = 0; i < n_wave + 2 * n_spsym; ++i) { + dphi[i] = 2 * M_PI * f0 / signal_rate; + } + + //float pulse[3 * n_spsym]; + float *pulse=(float *) malloc(sizeof(float)*3 * n_spsym); + gfsk_pulse(n_spsym, symbol_bt, pulse); + + for (int i = 0; i < n_sym; ++i) { + int ib = i * n_spsym; + for (int j = 0; j < 3 * n_spsym; ++j) { + dphi[j + ib] += dphi_peak * symbols[i] * pulse[j]; + } + } + // 在开头和结尾添加伪符号,音调值分别等于第一个符号和最后一个符号 + for (int j = 0; j < 2 * n_spsym; ++j) { + dphi[j] += dphi_peak * pulse[j + n_spsym] * symbols[0]; + dphi[j + n_sym * n_spsym] += dphi_peak * pulse[j] * symbols[n_sym - 1]; + } + // 计算并插入音频波形 + float phi = 0; + for (int k = 0; k < n_wave; ++k) { // 不包括虚拟符号 + signal[k] = sinf(phi); + phi = fmodf(phi + dphi[k + n_spsym], 2 * M_PI); + } + // 对第一个和最后一个符号应用封套成形,此处是前后增加斜坡函数, + int n_ramp = n_spsym / 8;//240个样本,20毫秒,T/8 + for (int i = 0; i < n_ramp; ++i) { + float env = (1 - cosf(2 * M_PI * i / (2 * n_ramp))) / 2; + signal[i] *= env; + signal[n_wave - 1 - i] *= env; + } + free(pulse); + free(dphi);//要释放掉内存 +} + +//此代码已经弃用 +void generateFt8ToBuffer(char *message, float frequency, short *buffer) { +// 首先,将文本数据打包为二进制消息 + uint8_t packed[FTX_LDPC_K_BYTES];//91位,包括CRC。 + int rc = pack77(message, packed);//生成数据 + if (rc < 0) { + //LOGE("Cannot parse message!\n"); + //LOGE("RC = %d\n", rc); + return; + } + + + //int num_tones = FT8_NN;//符号数量:FT8是79个,FT4是105个。 + //float symbol_period = FT8_SYMBOL_PERIOD;//FT8_SYMBOL_PERIOD=0.160f + float symbol_bt = FT8_SYMBOL_BT;//FT8_SYMBOL_BT=2.0f + float slot_time = FT8_SLOT_TIME;//FT8_SLOT_TIME=15f + + // 其次,将二进制消息编码为FSK音调序列 + uint8_t tones[FT8_NN]; // 79音调(符号)数组 + ft8_encode(packed, tones); + + + + // 第三,将FSK音调转换为音频信号b + //int sample_rate = FT8_SAMPLE_RATE;//采样率 + int num_samples = (int) (0.5f + FT8_NN * FT8_SYMBOL_PERIOD * + FT8_SAMPLE_RATE); // 数据信号中的采样数0.5+79*0.16*12000 + //int num_silence = (slot_time * sample_rate - num_samples) / 2; // 两端填充静音到15秒(15*12000-num_samples)/2(1.18秒的样本数) + int num_silence = 20;//把前面的静音时长缩短为20毫秒,留出时间给解码 + //int num_total_samples = num_silence + num_samples + num_silence; // 填充信号中的样本数2.36秒+12.64秒=15秒的样本数 + float signal[Ft8num_samples]; + //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 + for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 + { + signal[i] = 0; + //buffer[i + num_samples + num_silence] = 0; + } + + // 合成波形数据(信号)并将其保存为WAV文件 + synth_gfsk(tones, FT8_NN, frequency, symbol_bt, FT8_SYMBOL_PERIOD, FT8_SAMPLE_RATE, + signal + num_silence); + + + for (int i = 0; i < Ft8num_samples; i++) { + float x = signal[i]; + if (x > 1.0) + x = 1.0; + else if (x < -1.0) + x = -1.0; + buffer[i] = (short) (0.5 + (x * 32767.0)); + } + + + //save_wav(signal, num_total_samples, sample_rate, wav_path); + +} diff --git a/ft8CN/app/src/main/cpp/ft8Encoder.h b/ft8CN/app/src/main/cpp/ft8Encoder.h new file mode 100644 index 0000000..b33cdac --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Encoder.h @@ -0,0 +1,25 @@ +// +// Created by jmsmf on 2022/6/1. +// + +#ifndef FT8CN_FT8ENCODER_H +#define FT8CN_FT8ENCODER_H + +#endif //FT8CN_FT8ENCODER_H +#include +#include +#include +#include +#include + +#include "common/common.h" +//#include "common/wave.h" +#include "common/debug.h" +#include "ft8/pack.h" +#include "ft8/encode.h" +#include "ft8/constants.h" + +const int Ft8num_samples = 15*12000;//FT8采样数,不是字节数,16bit,字节数要乘以2 +//void generateFt8ToFile(char* message,float frequency,char* wav_path,bool is_ft4); +void generateFt8ToBuffer(char* message,float frequency,short * buffer); +void synth_gfsk(const uint8_t* symbols, int n_sym, float f0, float symbol_bt, float symbol_period, int signal_rate, float* signal); \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8Listener.cpp b/ft8CN/app/src/main/cpp/ft8Listener.cpp new file mode 100644 index 0000000..0280973 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Listener.cpp @@ -0,0 +1,333 @@ +// +// Created by jmsmf on 2022/6/2. +// + +#include +#include + + +extern "C" { +#include "common/debug.h" +#include "ft8Decoder.h" +#include "ft8Encoder.h" +} + +// +////将char类型转换成jstring类型 +//jstring CStr2Jstring(JNIEnv *env, const char *pat) { +// // 定义java String类 strClass +// jclass strClass = (env)->FindClass("java/lang/String"); +// // 获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String +// jmethodID ctorID = (env)->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); +// // 建立byte数组 +// jbyteArray bytes = (env)->NewByteArray((jsize) strlen(pat)); +// // 将char* 转换为byte数组 +// (env)->SetByteArrayRegion(bytes, 0, (jsize) strlen(pat), (jbyte *) pat); +// //设置String, 保存语言类型,用于byte数组转换至String时的参数 +// jstring encoding = (env)->NewStringUTF("GB2312"); +// //将byte数组转换为java String,并输出 +// return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding); +// +//} +// +//char *Jstring2CStr(JNIEnv *env, jstring jstr) { +// char *rtn = NULL; +// jclass clsstring = env->FindClass("java/lang/String"); +// jstring strencode = env->NewStringUTF("GB2312"); +// jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); +// jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); +// jsize alen = env->GetArrayLength(barr); +// jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE); +// if (alen > 0) { +// rtn = (char *) malloc(alen + 1); //new char[alen+1]; +// memcpy(rtn, ba, alen); +// rtn[alen] = 0; +// } +// env->ReleaseByteArrayElements(barr, ba, 0); +// +// return rtn; +//} + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderFt8Reset(JNIEnv *env, jobject thiz, + jlong decoder, jlong utcTime, + jint num_samples) { + decoder_t *dd; + dd = (decoder_t *) decoder; + decoder_ft8_reset(dd, utcTime, num_samples); + //dd->utcTime=utcTime; +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DeleteDecoder(JNIEnv *env, jobject, + jlong decoder) { + decoder_t *dd; + dd = (decoder_t *) decoder; + delete_decoder(dd); +} +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderFt8Analysis(JNIEnv *env, jobject, + jint idx, + jlong decoder, + jobject ft8Message) { + decoder_t *dd; + dd = (decoder_t *) decoder; + + + ft8_message message = decoder_ft8_analysis(idx, dd); + + + jclass objectClass = env->FindClass("com/bg7yoz/ft8cn/Ft8Message"); + + jfieldID utcTime = env->GetFieldID(objectClass, "utcTime", "J"); + jfieldID isValid = env->GetFieldID(objectClass, "isValid", "Z"); + jfieldID time_sec = env->GetFieldID(objectClass, "time_sec", "F"); + jfieldID freq_hz = env->GetFieldID(objectClass, "freq_hz", "F"); + jfieldID score = env->GetFieldID(objectClass, "score", "I"); + jfieldID snr = env->GetFieldID(objectClass, "snr", "I"); + jfieldID messageHash = env->GetFieldID(objectClass, "messageHash", "I"); + env->SetBooleanField(ft8Message, isValid, message.isValid); + + jfieldID i3 = env->GetFieldID(objectClass, "i3", "I"); + jfieldID n3 = env->GetFieldID(objectClass, "n3", "I"); + jfieldID callsignFrom = env->GetFieldID(objectClass, "callsignFrom", "Ljava/lang/String;"); + jfieldID callsignTo = env->GetFieldID(objectClass, "callsignTo", "Ljava/lang/String;"); + jfieldID extraInfo = env->GetFieldID(objectClass, "extraInfo", "Ljava/lang/String;"); + jfieldID maidenGrid = env->GetFieldID(objectClass, "maidenGrid", "Ljava/lang/String;"); + jfieldID report = env->GetFieldID(objectClass, "report", "I"); + jfieldID callFromHash10 = env->GetFieldID(objectClass, "callFromHash10", "J"); + jfieldID callFromHash12 = env->GetFieldID(objectClass, "callFromHash12", "J"); + jfieldID callFromHash22 = env->GetFieldID(objectClass, "callFromHash22", "J"); + jfieldID callToHash10 = env->GetFieldID(objectClass, "callToHash10", "J"); + jfieldID callToHash12 = env->GetFieldID(objectClass, "callToHash12", "J"); + jfieldID callToHash22 = env->GetFieldID(objectClass, "callToHash22", "J"); + + + if (message.isValid) { + + env->SetLongField(ft8Message, utcTime, message.utcTime); + env->SetFloatField(ft8Message, time_sec, message.time_sec); + env->SetFloatField(ft8Message, freq_hz, message.freq_hz); + env->SetIntField(ft8Message, score, message.candidate.score); + env->SetIntField(ft8Message, snr, message.snr); + //env->SetObjectField(ft8Message,messageText,env->NewStringUTF(message.message.text)); + env->SetIntField(ft8Message, messageHash, message.message.hash); + + env->SetIntField(ft8Message, i3, message.message.i3); + env->SetIntField(ft8Message, n3, message.message.n3); + env->SetObjectField(ft8Message, callsignFrom, env->NewStringUTF(message.message.call_de)); + env->SetObjectField(ft8Message, callsignTo, env->NewStringUTF(message.message.call_to)); + env->SetObjectField(ft8Message, extraInfo, env->NewStringUTF(message.message.extra)); + env->SetObjectField(ft8Message, maidenGrid, env->NewStringUTF(message.message.maidenGrid)); + env->SetIntField(ft8Message, report, message.message.report); + env->SetLongField(ft8Message, callFromHash10, + (long long) message.message.call_de_hash.hash10); + env->SetLongField(ft8Message, callFromHash12, + (long long) message.message.call_de_hash.hash12); + env->SetLongField(ft8Message, callFromHash22, + (long long) message.message.call_de_hash.hash22); + env->SetLongField(ft8Message, callToHash10, + (long long) message.message.call_to_hash.hash10); + env->SetLongField(ft8Message, callToHash12, + (long long) message.message.call_to_hash.hash12); + env->SetLongField(ft8Message, callToHash22, + (long long) message.message.call_to_hash.hash22); + + } + return message.isValid; +} +extern "C" +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderFt8FindSync(JNIEnv *env, jobject, + jlong decoder) { + decoder_t *dd; + dd = (decoder_t *) decoder; + return decoder_ft8_find_sync(dd); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderMonitorPress(JNIEnv *env, jobject, + jintArray buffer, + jlong decoder) { + decoder_t *dd; + dd = (decoder_t *) decoder; + + + // jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(buffer, NULL); +// decoder_monitor_press(arr, dd); + int arr_len = env->GetArrayLength(buffer); + //将java数组复制到c数组中 + auto *c_array = (jint *) malloc(arr_len * sizeof(arr_len)); + + //env->GetFloatArrayRegion(buffer,0,arr_len,c_array); + (*env).GetIntArrayRegion(buffer, 0, arr_len, c_array); + auto *raw_data = (float_t *) malloc(sizeof(float_t) * arr_len); + + for (int i = 0; i < arr_len; i++) { + raw_data[i] = c_array[i] / 32768.0f; + } + + decoder_monitor_press(raw_data, dd); + free(raw_data); + free(c_array); + + + +// jint *arr; +// jint length; +// arr = (*env).GetIntArrayElements(buffer, nullptr); +// length = (*env).GetArrayLength(buffer); +// auto *raw_data = (float_t *) malloc(sizeof(float_t) * length); +// +// for (int i = 0; i < length; i++) { +// raw_data[i] = arr[i] / 32768.0f; +// } +// +// decoder_monitor_press(raw_data, dd); +// +// free(raw_data); + +} +extern "C" +JNIEXPORT jlong JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_InitDecoder(JNIEnv *env, jobject, jlong utcTime, + jint sampleRate, jint num_samples, + jboolean isFt8) { + return (long) init_decoder(utcTime, sampleRate, num_samples, isFt8); +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderMonitorPressFloat(JNIEnv *env, + jobject thiz, + jfloatArray buffer, + jlong decoder) { + decoder_t *dd; + dd = (decoder_t *) decoder; + + +// jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(buffer, NULL); +// decoder_monitor_press(arr, dd); + int arr_len = env->GetArrayLength(buffer); + //将java数组复制到c数组中 + auto *c_array = (jfloat *) malloc(arr_len * sizeof(arr_len)); + + //env->GetFloatArrayRegion(buffer,0,arr_len,c_array); + (*env).GetFloatArrayRegion(buffer, 0, arr_len, c_array); + decoder_monitor_press(c_array, dd); + free(c_array); + +} + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_DecoderGetA91(JNIEnv *env, jobject thiz, + jlong decoder) { + decoder_t *dd; + dd = (decoder_t *) decoder; + + jbyteArray array; + array = env->NewByteArray(FTX_LDPC_K_BYTES); + + jbyte buf[FTX_LDPC_K_BYTES]; + memcpy(buf, dd->a91, FTX_LDPC_K_BYTES); + + // 使用 setIntArrayRegion 来赋值 + env->SetByteArrayRegion(array, 0, FTX_LDPC_K_BYTES, buf); + return array; +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_FT8SignalListener_setDecodeMode(JNIEnv *env, jobject thiz, + jlong decoder, jboolean is_deep) { + decoder_t *dd; + dd = (decoder_t *) decoder; + if (is_deep) { + dd->kLDPC_iterations = deep_kLDPC_iterations; + } else { + dd->kLDPC_iterations = fast_kLDPC_iterations; + } +} + +/** + * 把频率减去 + * @param dd + * @param index + * @param max_block_size + */ +void setMagToZero(decoder_t * dd ,int index,int max_block_size){ + if (index>0 && indexmon.wf.mag[index]=0; + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8listener_ReBuildSignal_doSubtractSignal(JNIEnv *env, jclass clazz, + jlong decoder, + jbyteArray payload, + jint sample_rate, + jfloat frequency, + jfloat time_sec) { + decoder_t *dd; + dd = (decoder_t *) decoder; + + int arr_len = env->GetArrayLength(payload); + //将java数组复制到c数组中 + auto *c_array = (jbyte *) malloc(arr_len * sizeof(arr_len)); + + + //env->GetFloatArrayRegion(buffer,0,arr_len,c_array); + (*env).GetByteArrayRegion(payload, 0, arr_len, c_array); + + uint8_t tones[FT8_NN];// 79音调(符号)数组, + //此处是12个字节(91+7)/8,可以使用a91生成音频 + ft8_encode((uint8_t *) c_array, tones); + + //相当于二维数组,freq优先 + int max_block_size=(int) (FT8_SLOT_TIME / FT8_SYMBOL_PERIOD) * kTime_osr * kFreq_osr + * (int) (sample_rate * FT8_SYMBOL_PERIOD / 2); + LOG_PRINTF("max_block_size:%d",max_block_size); + int block_size = FT8_SYMBOL_PERIOD * dd->mon_cfg.sample_rate;//1920,一个0.08秒的数据块大小,x轴的总长度 + LOG_PRINTF("block_size +++:%d", block_size); + int freq_offset = (int) (frequency * FT8_SYMBOL_PERIOD) * kFreq_osr;//频率的偏移量,x轴 + int time_offset = (int) ((time_sec / FT8_SYMBOL_PERIOD) * kTime_osr+0.5f);// + 0.5);,y轴 + LOG_PRINTF("freq_offset +++:%f,%d", (frequency * FT8_SYMBOL_PERIOD) * kFreq_osr, freq_offset); + LOG_PRINTF("time_offset +++:%f ,%d,time_offset:%d, time_sec:%f, freq_offset:%d, freq:%f", + (time_sec / 0.08), + (int) (time_sec / 0.08 + 0.5), time_offset, time_sec, freq_offset, frequency); + for (int i = 0; i < FT8_NN; ++i) {//y轴自增循环 + int index = (i + time_offset) * 2; + int index1 = index * block_size + freq_offset+tones[i]; + int index2 = (index + 1) * block_size + freq_offset+tones[i]; + int index3 =index1+1; + int index4 =index2+1; + int index5 =index1-1; + int index6 =index2-1; + int index7 =index1-2; + int index8 =index2-2; + int index9 =index1+2; + int index10 =index2+2; + + setMagToZero(dd,index1,max_block_size); + setMagToZero(dd,index2,max_block_size); + setMagToZero(dd,index3,max_block_size); + setMagToZero(dd,index4,max_block_size); + setMagToZero(dd,index5,max_block_size); + setMagToZero(dd,index6,max_block_size); + setMagToZero(dd,index7,max_block_size); + setMagToZero(dd,index8,max_block_size); + setMagToZero(dd,index9,max_block_size); + setMagToZero(dd,index10,max_block_size); + + } + free(c_array); + +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/ft8Spectrum.cpp b/ft8CN/app/src/main/cpp/ft8Spectrum.cpp new file mode 100644 index 0000000..206d334 --- /dev/null +++ b/ft8CN/app/src/main/cpp/ft8Spectrum.cpp @@ -0,0 +1,293 @@ +#include + +// +// Created by jmsmf on 2022/6/11. +// + +extern "C" { +#include "common/debug.h" +#include "spectrum_data.h" +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumFragment_getFFTData(JNIEnv *env, jobject thiz, jintArray data, + jintArray fft_data) { + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto c_array=(jint *) malloc(arr_len * sizeof(arr_len)); + + env->GetIntArrayRegion(data,0,arr_len,c_array); + auto *raw_data = (float *) malloc(sizeof(float) * arr_len); + for (int i = 0; i < arr_len; i++) { + raw_data[i] = c_array[i] / 32768.0f; + } + + + jint temp[arr_len/2]; + + do_fftr(raw_data,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + free(raw_data); + +// +// jint *arr; +// jint length; +// arr = (*env).GetIntArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// float *raw_data = (float *) malloc(sizeof(float) * length); +// for (int i = 0; i < length; i++) { +// raw_data[i] = arr[i] / 32768.0f; +// } +// +// jint temp[length/2]; +// +// do_fftr(raw_data,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +// free(raw_data); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumFragment_getFFTDataRaw(JNIEnv *env, jobject thiz, jintArray data, + jintArray fft_data) { + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto c_array=(jint *) malloc(arr_len * sizeof(arr_len)); + + env->GetIntArrayRegion(data,0,arr_len,c_array); + auto *raw_data = (float *) malloc(sizeof(float) * arr_len); + for (int i = 0; i < arr_len; i++) { + raw_data[i] = c_array[i] / 32768.0f; + } + + + jint temp[arr_len/2]; + + do_fftr_raw(raw_data,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + free(raw_data); + + +// +// jint *arr; +// jint length; +// arr = (*env).GetIntArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// float *raw_data = (float *) malloc(sizeof(float) * length); +// for (int i = 0; i < length; i++) { +// raw_data[i] = arr[i] / 32768.0f; +// } +// +// jint temp[length/2]; +// +// do_fftr_raw(raw_data,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +// free(raw_data); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumView_getFFTData(JNIEnv *env, jobject thiz, jintArray data, + jintArray fft_data) { + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto c_array=(jint *) malloc(arr_len * sizeof(arr_len)); + + env->GetIntArrayRegion(data,0,arr_len,c_array); + auto *raw_data = (float *) malloc(sizeof(float) * arr_len); + for (int i = 0; i < arr_len; i++) { + raw_data[i] = c_array[i] / 32768.0f; + } + + + jint temp[arr_len/2]; + + do_fftr(raw_data,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + free(raw_data); + + +// jint *arr; +// jint length; +// arr = (*env).GetIntArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// float *raw_data = (float *) malloc(sizeof(float) * length); +// for (int i = 0; i < length; i++) { +// raw_data[i] = arr[i] / 32768.0f; +// } +// +// jint temp[length/2]; +// +// do_fftr(raw_data,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +// free(raw_data); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumView_getFFTDataRaw(JNIEnv *env, jobject thiz, jintArray data, + jintArray fft_data) { + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto c_array=(jint *) malloc(arr_len * sizeof(arr_len)); + + env->GetIntArrayRegion(data,0,arr_len,c_array); + auto *raw_data = (float *) malloc(sizeof(float) * arr_len); + for (int i = 0; i < arr_len; i++) { + raw_data[i] = c_array[i] / 32768.0f; + } + + + jint temp[arr_len/2]; + + do_fftr_raw(raw_data,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + free(raw_data); + +// jint *arr; +// jint length; +// arr = (*env).GetIntArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// float *raw_data = (float *) malloc(sizeof(float) * length); +// for (int i = 0; i < length; i++) { +// raw_data[i] = arr[i] / 32768.0f; +// } +// +// jint temp[length/2]; +// //jint *fftdata; +// //fftdata=(*env).GetIntArrayElements(fft_data, NULL); +// +// do_fftr_raw(raw_data,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +// free(raw_data); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumFragment_getFFTDataFloat(JNIEnv *env, jobject thiz, + jfloatArray data, jintArray fft_data) { + + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto *c_array=(jfloat *) malloc(arr_len * sizeof(arr_len)); + + env->GetFloatArrayRegion(data,0,arr_len,c_array); + jint temp[arr_len/2]; + + do_fftr(c_array,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + + + +// jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// +// jint temp[length/2]; +// +// do_fftr(arr,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumFragment_getFFTDataRawFloat(JNIEnv *env, jobject thiz, + jfloatArray data, jintArray fft_data) { + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto *c_array=(jfloat *) malloc(arr_len * sizeof(arr_len)); + + env->GetFloatArrayRegion(data,0,arr_len,c_array); + jint temp[arr_len/2]; + + do_fftr_raw(c_array,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + + + +// jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// +// jint temp[length/2]; +// do_fftr_raw(arr,length,temp); + +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumView_getFFTDataFloat(JNIEnv *env, jobject thiz, jfloatArray data, + jintArray fft_data) { + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto *c_array=(jfloat *) malloc(arr_len * sizeof(arr_len)); + + //env->GetFloatArrayRegion(data,0,arr_len,c_array); + (*env).GetFloatArrayRegion(data,0,arr_len,c_array); + jint temp[arr_len/2]; + + do_fftr(c_array,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + free(c_array); + +// jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// +// jint temp[length/2]; +// +// do_fftr(arr,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ui_SpectrumView_getFFTDataRawFloat(JNIEnv *env, jobject thiz, + jfloatArray data, jintArray fft_data) { + + + int arr_len=env->GetArrayLength(data); + //将java数组复制到c数组中 + auto *c_array=(jfloat *) malloc(arr_len * sizeof(arr_len)); + + //env->GetFloatArrayRegion(data,0,arr_len,c_array); + (*env).GetFloatArrayRegion(data,0,arr_len,c_array); + jint temp[arr_len/2]; + + do_fftr_raw(c_array,arr_len,temp); + (*env).SetIntArrayRegion(fft_data,0,arr_len/2,temp); + + free(c_array); + +// jfloat *arr; +// jint length; +// arr = (*env).GetFloatArrayElements(data, NULL); +// length = (*env).GetArrayLength(data); +// +// jint temp[length/2]; +// +// do_fftr_raw(arr,length,temp); +// +// (*env).SetIntArrayRegion(fft_data,0,length/2,temp); +} diff --git a/ft8CN/app/src/main/cpp/generate_ft8.cpp b/ft8CN/app/src/main/cpp/generate_ft8.cpp new file mode 100644 index 0000000..7d460f1 --- /dev/null +++ b/ft8CN/app/src/main/cpp/generate_ft8.cpp @@ -0,0 +1,148 @@ +// +// Created by jmsmf on 2022/6/1. +// +#include +#include + +extern "C" { +#include "common/debug.h" +#include "ft8Encoder.h" +#include "ft8/pack.h" +#include "ft8/encode.h" +#include "ft8/hash22.h" +} +#define GFSK_CONST_K 5.336446f ///< == pi * sqrt(2 / log(2)) + +char *Jstring2CStr(JNIEnv *env, jstring jstr) { + char *rtn = nullptr; + jclass clsstring = env->FindClass("java/lang/String"); + jstring strencode = env->NewStringUTF("GB2312"); + jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); + auto barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode); + int alen = env->GetArrayLength(barr); + jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE); + if (alen > 0) { + rtn = (char *) malloc(alen + 1); //new char[alen+1]; + memcpy(rtn, ba, alen); + rtn[alen] = 0; + } + env->ReleaseByteArrayElements(barr, ba, 0); + + return rtn; +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_FT8TransmitSignal_GenerateFt8(JNIEnv *env, jobject, + jstring message, + jfloat frequency, + jshortArray buffer) { + jshort *_buffer; + _buffer = (*env).GetShortArrayElements(buffer, nullptr); + char *str=Jstring2CStr(env, message); + generateFt8ToBuffer(str, frequency, _buffer); + (*env).ReleaseShortArrayElements(buffer,_buffer,JNI_COMMIT); + free(str); +} + +extern "C" +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_GenerateFT8_pack77(JNIEnv *env, jclass, jstring msg, + jbyteArray c77) { + jbyte *_buffer; + _buffer = (*env).GetByteArrayElements(c77, nullptr); + char *str=Jstring2CStr(env, msg); + int result=pack77(str,(uint8_t *)_buffer); + (*env).ReleaseByteArrayElements(c77,_buffer,JNI_COMMIT); + free(str); + return result; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_GenerateFT8_ft8_1encode(JNIEnv *env, jclass clazz, + jbyteArray payload, jbyteArray tones) { + jbyte *_payload; + jbyte *_tones; + _payload = (*env).GetByteArrayElements(payload, nullptr); + _tones = (*env).GetByteArrayElements(tones, nullptr); + ft8_encode((uint8_t *)_payload,(uint8_t *)_tones); + (*env).ReleaseByteArrayElements(payload,_payload,JNI_COMMIT); + (*env).ReleaseByteArrayElements(tones,_tones,JNI_COMMIT); + + +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_GenerateFT8_gfsk_1pulse(JNIEnv *env, jclass clazz, jint n_spsym, + jfloat symbol_bt, jfloatArray pulse) { + jfloat *_pulse; + _pulse=(*env).GetFloatArrayElements(pulse, nullptr); + + for (int i = 0; i < 3 * n_spsym; ++i) + { + float t = i / (float)n_spsym - 1.5f; + float arg1 = GFSK_CONST_K * symbol_bt * (t + 0.5f); + float arg2 = GFSK_CONST_K * symbol_bt * (t - 0.5f); + _pulse[i] = (erff(arg1) - erff(arg2)) / 2; + } + (*env).ReleaseFloatArrayElements(pulse,_pulse,JNI_COMMIT); + +} +extern "C" +JNIEXPORT void JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_GenerateFT8_synth_1gfsk(JNIEnv *env, jclass clazz, + jbyteArray symbols, jint n_sym, jfloat f0, + jfloat symbol_bt, jfloat symbol_period, + jint signal_rate, jfloatArray signal, + jint offset) { + jbyte *_symbols; + jfloat *_signal; + _symbols = (*env).GetByteArrayElements(symbols, nullptr); + _signal = (*env).GetFloatArrayElements(signal, nullptr); + synth_gfsk((uint8_t *)_symbols,n_sym,f0,symbol_bt,symbol_period,signal_rate,_signal+offset); + + (*env).ReleaseByteArrayElements(symbols,_symbols,JNI_COMMIT); + (*env).ReleaseFloatArrayElements(signal,_signal,JNI_COMMIT); + +} +extern "C" +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8signal_FT8Package_getHash12(JNIEnv *env, jclass clazz, jstring callsign) { + char *str=Jstring2CStr(env, callsign); + uint32_t hash=hashcall_12(str); + free(str); + return hash; +} +extern "C" + + +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8transmit_GenerateFT8_packFreeTextTo77(JNIEnv *env, jclass clazz, + jstring msg, jbyteArray c77) { + + jbyte *_buffer; + _buffer = (*env).GetByteArrayElements(c77, nullptr); + char *str=Jstring2CStr(env, msg); + packtext77(str,(uint8_t *)_buffer); + (*env).ReleaseByteArrayElements(c77,_buffer,JNI_COMMIT); + free(str); + return 0; +} +extern "C" +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8signal_FT8Package_getHash10(JNIEnv *env, jclass clazz, jstring callsign) { + char *str=Jstring2CStr(env, callsign); + uint32_t hash=(hashcall_10(str)); + free(str); + return hash; +} +extern "C" +JNIEXPORT jint JNICALL +Java_com_bg7yoz_ft8cn_ft8signal_FT8Package_getHash22(JNIEnv *env, jclass clazz, jstring callsign) { + char *str=Jstring2CStr(env, callsign); + u_int32_t hash=hashcall_22(str); + free(str); + return hash; +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/monitor_opr.c b/ft8CN/app/src/main/cpp/monitor_opr.c new file mode 100644 index 0000000..8cdaaae --- /dev/null +++ b/ft8CN/app/src/main/cpp/monitor_opr.c @@ -0,0 +1,257 @@ +// +// Created by jmsmf on 2022/4/22. +// + +#include "monitor_opr.h" +#include "ft8/constants.h" +//#define LOG_LEVEL LOG_DEBUG +#define LOG_LEVEL LOG_FATAL + + +// FFT转换,采用加窗函数,减少频谱泄露的问题。 +// Hanning窗(汉宁窗)适用于95%的情况。 +static float hann_i(int i, int N) { + float x = sinf((float) M_PI * i / N); + return x * x; +} + +static float hamming_i(int i, int N) { + const float a0 = (float) 25 / 46; + const float a1 = 1 - a0; + + float x1 = cosf(2 * (float) M_PI * i / N); + return a0 - a1 * x1; +} + +static float blackman_i(int i, int N) { + const float alpha = 0.16f; // or 2860/18608 + const float a0 = (1 - alpha) / 2; + const float a1 = 1.0f / 2; + const float a2 = alpha / 2; + + float x1 = cosf(2 * (float) M_PI * i / N); + float x2 = 2 * x1 * x1 - 1; // Use double angle formula + + return a0 - a1 * x1 + a2 * x2; +} + +static void +waterfall_init(waterfall_t *me, int max_blocks, int num_bins, int time_osr, int freq_osr) { + //mag_size,信号量数组的大小。最大块数93*时间过采样率2*频率过采样率2*分析块960*sizeOf(U_int8) + size_t mag_size = max_blocks * time_osr * freq_osr * num_bins * sizeof(me->mag[0]); + me->max_blocks = max_blocks; + me->num_blocks = 0; + me->num_bins = num_bins;//num_bins = 12000 * 0.16 / 2 = 960 + me->time_osr = time_osr; + me->freq_osr = freq_osr; + me->block_stride = (time_osr * freq_osr * num_bins); + me->mag = (uint8_t *) malloc(mag_size);//申请信号量数组,用于计算得分,357120个 + me->mag2 = (float *) malloc(mag_size * sizeof(float));//申请信号量数组,用于计算信噪比 + LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size); +} + +static void waterfall_free(waterfall_t *me) { + free(me->mag); + free(me->mag2); +} + + +void monitor_init(monitor_t *me, const monitor_config_t *cfg) { + LOG(LOG_DEBUG, "Monitor is initializing..."); + //协议的时长,FT8_SLOT_TIME=15.0f,FT4_SLOT_TIME=7.5f + float slot_time = (cfg->protocol == PROTO_FT4) ? FT4_SLOT_TIME : FT8_SLOT_TIME; + //协议每个符号的时长,FT8_SYMBOL_PERIOD=0.160f,FT4_SYMBOL_PERIOD=0.048f + float symbol_period = (cfg->protocol == PROTO_FT4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD; + + //************************************************** + // Compute DSP parameters that depend on the sample rate + // 根据采样率计算DSP参数 + // block_size:每一个FSK符号占用的样本数,FT8:12000*0.16=1920个 + // subblock_size:分析移动的大小(样本数),每符号样本数/时间过采样率。FT8:1920/2=960 + // nfft:fft size。fft大小=每个FSK符号占用的样本数*频率过采样率=1920*2 + // fft_norm:FFT归一化因子。2/fft size。 + me->block_size = (int) (cfg->sample_rate * + symbol_period); //12000*0.16 对应于一个FSK符号的样本=1920 + me->subblock_size = me->block_size / cfg->time_osr;//移动的样本数。一个FSK符号的样本数/过采样率 + //nfft是傅里叶变换前,时域的实数序列的数量。目前是一个FSK符号的样本数*频率过采样率 + me->nfft = me->block_size * cfg->freq_osr;//nfft=一个FSK符号的样本数*频率过采样率 + me->fft_norm = 2.0f / me->nfft;//< FFT归一化因子。FFT normalization factor + // const int len_window = 1.8f * me->block_size; // hand-picked and optimized + //************************************************** + + // 申请窗空间,大小是fft块大小*me->windows[0]的大小 + // 采集周期如果是实际信号的非整数时,端点是不连续的。 + // 这些不连续片段在FFT中显示为高频成分。这些高频成分不存在于原信号中。 + // 这些频率可能远高于奈奎斯特频率,在0~ 采样率的一半的频率区间内产生混叠。 + // 使用FFT获得的频率,不是原信号的实际频率,而是一个改变过的频率。 + // 类似于某个频率的能量泄漏至其他频率。 这种现象叫做频谱泄漏。 + // 频率泄漏使好的频谱线扩散到更宽的信号范围中。这些不连续片段在FFT中显示为高频成分。 + // 通过加窗来尽可能减少在非整数个周期上进行FFT产生的误差。 + // 加窗可减少这些不连续部分的幅值。 + // 加窗包括将时间记录乘以有限长度的窗,窗的幅值逐渐变小,在边沿处为0。 + // 加窗的结果是尽可能呈现出一个连续的波形,减少剧烈的变化。 这种方法也叫应用一个加窗。 + // 窗函数分很多种,常见的的如:Hamming窗、Hanning窗、Blackman-Harris窗、Kaiser-Bessel窗、Flat top窗。 + // Hanning窗适用于95%的情况。 + me->window = (float *) malloc( + me->nfft * sizeof(me->window[0]));// 申请窗空间,大小是fft块大小*sizeof(me->windows[0]) + //此处是窗函数的设置,使用常用的hanning窗 + for (int i = 0; i < me->nfft; ++i) { + // window[i] = 1; + me->window[i] = hann_i(i, me->nfft);// 使用Hanning窗 + // me->window[i] = blackman_i(i, me->nfft);// Blackman-Harris窗 + // me->window[i] = hamming_i(i, me->nfft);// Hamming窗 + // me->window[i] = (i < len_window) ? hann_i(i, len_window) : 0; + } + + + // 申请当前STFT(短时傅氏变换)分析框架(nfft样本)的空间。 + //last_frame:申请傅里叶变换分析框架用的(nfft样本)。 + // 空间大小=时域数据量*数据类型占用的空间=一个FSK符号占用的采样数据量*频率过采样率=12000*0.16*2*SizeOf(float) + me->last_frame = (float *) malloc(me->nfft * sizeof(me->last_frame[0])); + + // size_t 类型定义在cstddef头文件中,该文件是C标准库的头文件stddef.h的C++版。 + // 它是一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小。 + size_t fft_work_size; + // 第一步,获取可以用的FFT工作区域的大小到fft_work_size + //nfft=一个FSK符号的样本数*频率过采样率=0.16*12000*2=3840 + kiss_fftr_alloc(me->nfft, 0, 0, &fft_work_size); + //fft_work_size + + + // 申请FFT工作区域的内存,38676个 + me->fft_work = malloc(fft_work_size); + //第二步,返回fft的设置信息 + me->fft_cfg = kiss_fftr_alloc(me->nfft, 0, me->fft_work, &fft_work_size); + + // 最大块数,FT8的周期时长/每个符号时长,FT8:15/0.16 =93 + const int max_blocks = (int) (slot_time / symbol_period); + + // num_bins:如果以6.25 Hz为单位的FFT箱数量。6.25:每秒6.25个FSK符号,0.16*6.25=1。 + // num_bins = 12000 * 0.16 / 2 = 960。2是啥?是生成的频域数据量是采集数据量的一半 + const int num_bins = (int) (cfg->sample_rate * symbol_period / 2); + + //初始化瀑布图。 + waterfall_init(&me->wf, max_blocks, num_bins, cfg->time_osr, cfg->freq_osr); + me->wf.protocol = cfg->protocol; + me->symbol_period = symbol_period; + + me->max_mag = -120.0f; + + + LOG(LOG_INFO, "Block size = %d\n", me->block_size); + LOG(LOG_INFO, "Subblock size = %d\n", me->subblock_size); + LOG(LOG_INFO, "N_FFT = %d\n", me->nfft); + LOG(LOG_DEBUG, "FFT work area = %zu\n", fft_work_size); + // 瀑布图中最多能申请max_blocks个块数 + LOG(LOG_DEBUG, "Waterfall allocated %d symbols\n", me->wf.max_blocks); +} + +void monitor_free(monitor_t *me) { + + waterfall_free(&me->wf); + free(me->fft_work); + free(me->last_frame); + free(me->window); + LOG(LOG_DEBUG, "Monitor is free ."); +} + + +// Compute FFT magnitudes (log wf) for a frame in the signal and update waterfall data +// 计算信号中一帧的FFT幅度(log wf),并更新瀑布数据 +void monitor_process(monitor_t *me, const float *frame) { + // Check if we can still store more waterfall data + //防止溢出 + //mag阵列中存储的块(符号)编号 + if (me->wf.num_blocks >= me->wf.max_blocks) + return; + + //num_bins 的值是 12000 * 0.16 / 2 = 960 + //wf.block_strid= (time_osr * freq_osr * num_bins)=2*2*960 + //offset是在mag数组中的偏移量。wf.num_blocks是当前符号的块编号,以num_bins(数据片段)*时间过采样*频率过采样为单位。 + //mag的数组大小实际上是时间过采样*频率过采样*符号的最大量(93)*每符号真实采样数据(过采样的数据,960). + int offset = me->wf.num_blocks * me->wf.block_stride; + int frame_pos = 0; + + // Loop over block subdivisions + //循环块细分,wf.time_osr=2 时间过采样率 + for (int time_sub = 0; time_sub < me->wf.time_osr; ++time_sub) { + kiss_fft_scalar timedata[me->nfft]; + kiss_fft_cpx freqdata[me->nfft / 2 + 1]; + + // Shift the new data into analysis frame + //将新数据转移到分析框架中。 + // subblock_size:分析移动的大小(样本数)blocksize/2 每秒块数/时间过采样率=FT8:12000*0.16/2=1920/2=960个 + //last_frame的空间已经申请好了。空间大小=时域数据量*数据类型占用的空间=一个FSK符号占用的采样数据量*频率过采样率=12000*0.16*2*SizeOf(float)=3840 + //nfft=一个FSK符号的样本数*频率过采样率=1920*2=3840 + //subblock_size。移动的样本数。一个FSK符号的样本数/过采样率,1920/2=960 + //第一个循环,把过采样的后半段数据向前移960个数据, + //第二个循环,把新的声音数据导入到last_frame的后半部分,新的声音数据960个。外面的时间过采样率循环2遍,正好960*2=1920,一个符号 + //这样就可以对一个符号周期的时域数据做傅里叶变换了 + for (int pos = 0; pos < me->nfft - me->subblock_size; ++pos) { + me->last_frame[pos] = me->last_frame[pos + me->subblock_size]; + } + for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos) { + me->last_frame[pos] = frame[frame_pos]; + ++frame_pos; + } + + + // Compute windowed analysis frame + //用窗函数做一次转换,汉宁窗。 + for (int pos = 0; pos < me->nfft; ++pos) { + //把last_frame中的数据赋值到timedata中来,timedata是时域数据,要做一次归一化、窗函数处理 + timedata[pos] = me->fft_norm * me->window[pos] * me->last_frame[pos]; + //timedata[pos] =me->window[pos] * me->last_frame[pos]; + } + + //傅里叶变换把timedata的时域数据(长度是nfft)转换到频域数据上来。频域数据是复数数组,数组长度是nfft/2+1 + //nfft=一个FSK符号的样本数*频率过采样率=12000*0.16*2=3840 + kiss_fftr(me->fft_cfg, timedata, freqdata); + + // Loop over two possible frequency bin offsets (for averaging) + //在两个可能的频率单元偏移上循环(用于平均) + //两个循环的意义是:在一个符号采样数据的范围内(12000*0.16)对freqdata的能量做计算 + for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub) { + for (int bin = 0; bin < me->wf.num_bins; ++bin) { //num_bins 的值是 12000 * 0.16 / 2 = 960 + + //循环次数=2*960=1920 + //信号量位置src_bin, + int src_bin = (bin * me->wf.freq_osr) + freq_sub; + + ////此位置可能是计算信噪比的位置 + //求各频率点上的信号强度是傅里叶之后的平方?少了开方,mag2应当是信号量的平方 + float mag2 = (freqdata[src_bin].i * freqdata[src_bin].i) + + (freqdata[src_bin].r * freqdata[src_bin].r); + float db = 10.0f * log10f(1E-12f + mag2); + + //把信号量保存下来 + //offset=me->wf.num_blocks * me->wf.block_stride; + //wf.block_strid= (time_osr * freq_osr * num_bins)=2*2*960 + //偏移量就是当前块编号*每个符号ftt数据量 + + me->wf.mag2[offset] = mag2; + + //每循环一次,偏移量移一位。共移动time_osr*freq_osr*num_bins=2*2*960=3840 + + // Scale decibels to unsigned 8-bit range and clamp the value + //将分贝缩放到无符号8位范围,并钳制该值 + // Range 0-240 covers -120..0 dB in 0.5 dB steps + int scaled = (int) (2 * db + 240); + + //0~255之间 + me->wf.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled); + ++offset; + + if (db > me->max_mag) + me->max_mag = db; + } + } + } + ++me->wf.num_blocks;//mag阵列中存储的块(符号)编号,块大小:2*2*960 +} + +void monitor_reset(monitor_t *me) { + LOG(LOG_DEBUG, "Monitor is resetting..."); + me->wf.num_blocks = 0; + me->max_mag = -120.0f; +} diff --git a/ft8CN/app/src/main/cpp/monitor_opr.h b/ft8CN/app/src/main/cpp/monitor_opr.h new file mode 100644 index 0000000..dd7cc61 --- /dev/null +++ b/ft8CN/app/src/main/cpp/monitor_opr.h @@ -0,0 +1,47 @@ + +#include +#include +#include +#include +#include +#include "ft8/decode.h" +#include "fft/kiss_fftr.h" +#include "common/debug.h" + +#define LOG_LEVEL LOG_INFO + + + +/// Configuration options for FT4/FT8 monitor +typedef struct { + float f_min; ///< 最低频率界限,Lower frequency bound for analysis + float f_max; ///< 最高频率界限,Upper frequency bound for analysis + int sample_rate; ///< 采样率,Sample rate in Hertz + int time_osr; ///< 时间过采样率,Number of time subdivisions + int freq_osr; ///< 频率过采样率,Number of frequency subdivisions + ftx_protocol_t protocol; ///< Protocol: FT4 or FT8 +} monitor_config_t; + +/// FT4/FT8 monitor object that manages DSP processing of incoming audio data +/// and prepares a waterfall object +typedef struct { + float symbol_period; ///< FT4/FT8符号周期(秒)。FT4/FT8 symbol period in seconds + int block_size; ///< 每个符号(FSK)的样本数。Number of samples per symbol (block) + int subblock_size; ///< 分析移动的大小(样本数)。Analysis shift size (number of samples) + int nfft; ///< FFT size + float fft_norm; ///< FFT归一化因子。FFT normalization factor + float *window; ///< STFT分析的窗口函数(nfft样本)。Window function for STFT analysis (nfft samples) + float *last_frame; ///< 当前STFT分析框架(nfft样本)。Current STFT analysis frame (nfft samples) + waterfall_t wf; ///< 瀑布对象。Waterfall object + float max_mag; ///< 最大检测量(调试统计)。Maximum detected magnitude (debug stats) + + // KISS FFT housekeeping variables + void *fft_work; ///< FFT需要的工作区域。Work area required by Kiss FFT + kiss_fftr_cfg fft_cfg; ///< Kiss FFT需要的设置信息。Kiss FFT housekeeping object +} monitor_t; + + +void monitor_init(monitor_t *me, const monitor_config_t *cfg); +void monitor_free(monitor_t* me); +void monitor_process(monitor_t *me, const float *frame); +void monitor_reset(monitor_t *me); diff --git a/ft8CN/app/src/main/cpp/spectrum_data.c b/ft8CN/app/src/main/cpp/spectrum_data.c new file mode 100644 index 0000000..a608ff0 --- /dev/null +++ b/ft8CN/app/src/main/cpp/spectrum_data.c @@ -0,0 +1,125 @@ +#include +#include "spectrum_data.h" + +static float hann_i(int i, int N) { + float x = sinf((float) M_PI * i / N); + return x * x; +} + +void do_fftr(float *voiceData, int dataSize, int *fftData) { +// int block_size = FT8_SYMBOL_PERIOD * 12000; //=1920 + int fftSize = FT8_SYMBOL_PERIOD * 12000; //=1920 + float *window = (float *) malloc( + fftSize * sizeof(window[0])); // 申请窗空间,大小是fft块大小*sizeof(windows[0]) + for (int i = 0; i < fftSize; ++i) //汉宁窗 + { + window[i] = hann_i(i, fftSize); + } + // 申请当前STFT分析框架(nfft样本)的空间。 + //float *last_frame = (float *) malloc(fftSize * sizeof(last_frame[0])); + + size_t fft_work_size; + + // 获取FFT工作区域的大小到fft_work_size + kiss_fftr_alloc(fftSize, 0, 0, &fft_work_size); + + // 申请FFT工作区域的内存 + void *fft_work = malloc(fft_work_size); + kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(fftSize, 0, fft_work, &fft_work_size); + + // 最大块数,FT8的周期时长/每个符号时长,FT8:15/0.16 =93 + const int max_blocks = (int) (FT8_SLOT_TIME / FT8_SYMBOL_PERIOD); + const int num_bins = (int) (12000 * FT8_SYMBOL_PERIOD / 2); + int fftOffset = 0; + int offset = 0; + float maxMag = 0; + float minMag = 65535.0f; + float mags[dataSize / 2]; + for (int pos = 0; pos < dataSize / fftSize; pos++) { + kiss_fft_scalar timedata[fftSize]; + kiss_fft_cpx freqdata[fftSize / 2 + 1]; + for (int i = 0; i < fftSize; i++) {//fftSize=3840 + timedata[i] = window[i] * voiceData[offset];//window[i] * + offset++; + } + kiss_fftr(fft_cfg, timedata, freqdata); + + for (int i = 1; i < fftSize / 2 + 1; i++) { + float mag2 = sqrtf(freqdata[i].i * freqdata[i].i + freqdata[i].r * freqdata[i].r); + mags[fftOffset] = mag2; + if (maxMag < mag2) { + maxMag = mag2; + } + if (minMag > mag2) { + minMag = mag2; + } + fftOffset++; + } + + float normal = (maxMag - minMag) / 256; + for (int i = 0; i < dataSize / 2; ++i) { + fftData[i] = roundf((mags[i] - minMag) / normal); + } + } + free(fft_work); + free(window); + //free(last_frame); + //free(fft_cfg); +} + +void do_fftr_raw(float *voiceData, int dataSize, int *fftData) { + //int block_size = FT8_SYMBOL_PERIOD * 12000; //=1920 + int fftSize = FT8_SYMBOL_PERIOD * 12000; //=1920 + float *window = (float *) malloc( + fftSize * sizeof(window[0])); // 申请窗空间,大小是fft块大小*sizeof(windows[0]) + for (int i = 0; i < fftSize; ++i) //汉宁窗 + { + window[i] = hann_i(i, fftSize); + } + // 申请当前STFT分析框架(nfft样本)的空间。 + //float *last_frame = (float *) malloc(fftSize * sizeof(last_frame[0])); + + size_t fft_work_size; + + // 获取FFT工作区域的大小到fft_work_size + kiss_fftr_alloc(fftSize, 0, 0, &fft_work_size); + + // 申请FFT工作区域的内存 + void *fft_work = malloc(fft_work_size); + kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(fftSize, 0, fft_work, &fft_work_size); + + // 最大块数,FT8的周期时长/每个符号时长,FT8:15/0.16 =93 + const int max_blocks = (int) (FT8_SLOT_TIME / FT8_SYMBOL_PERIOD); + const int num_bins = (int) (12000 * FT8_SYMBOL_PERIOD / 2); + int fftOffset = 0; + int offset = 0; + + //float mags[dataSize / 2]; + for (int pos = 0; pos < dataSize / fftSize; pos++) { + kiss_fft_scalar timedata[fftSize]; + kiss_fft_cpx freqdata[fftSize / 2 + 1]; + for (int i = 0; i < fftSize; i++) {//fftSize=3840 + timedata[i] = window[i] * voiceData[offset];//window[i] * + offset++; + } + kiss_fftr(fft_cfg, timedata, freqdata); + + for (int i = 1; i < fftSize / 2 + 1; i++) { + float mag2 =(freqdata[i].i * freqdata[i].i + freqdata[i].r * freqdata[i].r); + mag2= 10.0f * log10f(1E-12f + mag2); + int scaled = (int) (mag2 +20)*4; + + //0~255之间 + fftData[fftOffset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled); + + fftOffset++; + } + +// float normal = (maxMag - minMag) / 256; +// for (int i = 0; i < dataSize / 2; ++i) { +// fftData[i] = roundf((mags[i] - minMag) / normal); +// } + } + free(fft_work); + free(window); +} \ No newline at end of file diff --git a/ft8CN/app/src/main/cpp/spectrum_data.h b/ft8CN/app/src/main/cpp/spectrum_data.h new file mode 100644 index 0000000..9714f25 --- /dev/null +++ b/ft8CN/app/src/main/cpp/spectrum_data.h @@ -0,0 +1,21 @@ + + +#include +#include +#include +#include + +#include "ft8/constants.h" +#include "common/debug.h" +#include "fft/kiss_fftr.h" + +/** + * 对符号进行快速傅里叶变换,以1920块数据变换,生成960块。0~3000Hz + * normalization=1对数据归一化处理,方便显示FT8信号的频率 + * @param voiceData 声音数据 + * @param dataSize 声音数据的大小 + * @param fftData fft数据 + * @param normalization 是否是归一化处理 + */ +void do_fftr(float* voiceData, int dataSize, int* fftData); +void do_fftr_raw(float *voiceData, int dataSize, int *fftData); diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FAQActivity.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java old mode 100755 new mode 100644 index 4799f9a..67e2312 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/FT8Common.java @@ -20,4 +20,6 @@ public final class FT8Common { public static final int FT8_5_SYMBOLS_TIME_M =8;//5个符号的时间长度0.8秒 public static final int FT4_SLOT_TIME_M=75;//7.5秒 public static final int FT8_TRANSMIT_DELAY=500;//默认发射延迟时长,毫秒 + public static final long DEEP_DECODE_TIMEOUT=7*1000;//深度解码的最长时间范围 + public static final int DECODE_MAX_ITERATIONS=1;//迭代次数 } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java old mode 100755 new mode 100644 index d755700..fe6519a --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/Ft8Message.java @@ -17,6 +17,7 @@ import androidx.annotation.NonNull; import com.bg7yoz.ft8cn.database.DatabaseOpr; import com.bg7yoz.ft8cn.ft8signal.FT8Package; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; import com.bg7yoz.ft8cn.ft8transmit.TransmitCallsign; import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid; import com.bg7yoz.ft8cn.rigs.BaseRigOperation; @@ -34,7 +35,7 @@ public class Ft8Message { public long utcTime;//UTC时间 public boolean isValid;//是否是有效信息 public int snr = 0;//信噪比 - public float time_sec = 0;//时间偏移 + public float time_sec = 0;//时间偏移(秒) public float freq_hz = 0;//频率 public int score = 0;//得分 public int messageHash;//消息的哈希 @@ -74,6 +75,10 @@ public class Ft8Message { public LatLng fromLatLng = null; public LatLng toLatLng = null; + public boolean isWeakSignal=false; + + + @NonNull @SuppressLint({"SimpleDateFormat", "DefaultLocale"}) @@ -182,6 +187,14 @@ public class Ft8Message { return String.format("%04.0f", freq_hz); } + public String getMessageText(boolean showWeekSignal){ + if (isWeakSignal && showWeekSignal){ + return "*"+getMessageText(); + }else { + return getMessageText(); + } + } + /** * 返回解码消息的文本内容 * diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java old mode 100755 new mode 100644 index 09f605a..d1fd408 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/GeneralVariables.java @@ -14,6 +14,8 @@ import com.bg7yoz.ft8cn.connector.ConnectMode; import com.bg7yoz.ft8cn.database.ControlMode; import com.bg7yoz.ft8cn.database.DatabaseOpr; import com.bg7yoz.ft8cn.ft8transmit.QslRecordList; +import com.bg7yoz.ft8cn.html.HtmlContext; +import com.bg7yoz.ft8cn.icom.IcomAudioUdp; import com.bg7yoz.ft8cn.log.QSLRecord; import com.bg7yoz.ft8cn.rigs.BaseRigOperation; import com.bg7yoz.ft8cn.timer.UtcTimer; @@ -33,6 +35,11 @@ public class GeneralVariables { public static boolean saveSWLMessage=false;//保存解码消息开关 public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关 + public static boolean deepDecodeMode=false;//是否开启深度解码 + + public static boolean audioOutput32Bit =true;//音频输出类型true=float,false=int16 + public static int audioSampleRate=12000;//发射音频的采样率 + public static MutableLiveData mutableVolumePercent = new MutableLiveData<>(); public static float volumePercent = 0.5f;//播放音频的音量,是百分比 @@ -151,7 +158,7 @@ public class GeneralVariables { //private static final Map callsignAndGrids=new HashMap<>(); public static String myCallsign = "";//我的呼号 - public static String toModifier = "ADFG";//呼叫的修饰符 + public static String toModifier = "";//呼叫的修饰符 private static float baseFrequency = 1000;//声音频率 public static MutableLiveData mutableBaseFrequency = new MutableLiveData<>(); @@ -543,21 +550,9 @@ public class GeneralVariables { int order = 0; for (String key : callsignAndGrids.keySet()) { order++; - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - result.append(""); - result.append(key); - result.append("\n"); - result.append(""); - result.append(callsignAndGrids.get(key)); - result.append("\n"); - + HtmlContext.tableKeyRow(result,order % 2 != 0,key,callsignAndGrids.get(key)); } return result.toString(); - } public static synchronized void deleteArrayListMore(ArrayList list) { @@ -582,4 +577,11 @@ public class GeneralVariables { return false; } + /** + * 输出音频的数据类型,网络模式不可用 + */ + public enum AudioOutputBitMode{ + Float32, + Int16 + } } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainActivity.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java old mode 100755 new mode 100644 index b4bdd6b..ebdc682 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MainViewModel.java @@ -5,19 +5,16 @@ package com.bg7yoz.ft8cn; * 1.解码的总条数。decoded_counter和mutable_Decoded_Counter。 * 2.解码消息的列表。消息以Ft8Message展示,列表用ArrayList泛型实现。ft8Messages,mutableFt8MessageList。 * 3.解码和录音都需要时间同步,也就是以UTC时间的每15秒为一个周期。同步事件的触发由UtcTimer类来实现。 - * 录音方法runRecode,解码暂时用testFt8实现,后续应改为类方式。--------TODO------------ * 4.当前的UTC时间。timerSec,更新频率(心跳频率)由UtcTimer确定,暂定100毫秒。 - *

* 5.通过类方法getInstance获取当前的MainViewModel的实例,确保有唯一的实例。 * 6.用HamAudioRecorder类实现录音,目前只实现录音成文件,然后读取文件的数据给解码模块,后面要改成直接给数组的方式----TO DO--- - *

* 7.解码采用JNI接口调用原生C语言。调用接口名时ft8cn,由cpp文件夹下的CMakeLists.txt维护。各函数的调用接口在decode_ft8.cpp中。 - *

* -----2022.5.9----- * 如果系统没有发射信号,触发器会在每一个周期触发录音动作,因录音开始和结束要浪费一些时间,如果不干预上一个录音的动作,将出现 * 连续的周期内录音动作重叠,造成第二个录音动作失败。所以,第二个周期的录音开始前,要停止前一个周期的录音,造成的结果就是每一次录音 * 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右 *

+ * * @author BG7YOZ * @date 2022.5.6 */ @@ -71,6 +68,7 @@ import com.bg7yoz.ft8cn.rigs.GuoHeQ900Rig; import com.bg7yoz.ft8cn.rigs.IcomRig; import com.bg7yoz.ft8cn.rigs.InstructionSet; import com.bg7yoz.ft8cn.rigs.KenwoodKT90Rig; +import com.bg7yoz.ft8cn.rigs.KenwoodTS2000Rig; import com.bg7yoz.ft8cn.rigs.KenwoodTS590Rig; import com.bg7yoz.ft8cn.rigs.OnRigStateChanged; import com.bg7yoz.ft8cn.rigs.XieGu6100Rig; @@ -97,7 +95,7 @@ import java.util.concurrent.Executors; public class MainViewModel extends ViewModel { String TAG = "ft8cn MainViewModel"; - public boolean configIsLoaded=false; + public boolean configIsLoaded = false; private static MainViewModel viewModel = null;//当前存在的实例。 //public static Application application; @@ -123,12 +121,12 @@ public class MainViewModel extends ViewModel { public MutableLiveData mutableIsDecoding = new MutableLiveData<>();//会触发频谱图中的标记动作 public ArrayList currentMessages = null;//本周期解码的消息(用于画到频谱上) - public MutableLiveData mutableIsFlexRadio=new MutableLiveData<>();//是不是flex电台 + public MutableLiveData mutableIsFlexRadio = new MutableLiveData<>();//是不是flex电台 private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool(); private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool(); - private final GetQTHRunnable getQTHRunnable=new GetQTHRunnable(this); - private final SendWaveDataRunnable sendWaveDataRunnable=new SendWaveDataRunnable(); + private final GetQTHRunnable getQTHRunnable = new GetQTHRunnable(this); + private final SendWaveDataRunnable sendWaveDataRunnable = new SendWaveDataRunnable(); public HamRecorder hamRecorder;//用于录音的对象 @@ -140,7 +138,7 @@ public class MainViewModel extends ViewModel { //控制电台的方式 public OperationBand operationBand = null; - private SWLQsoList swlQsoList=new SWLQsoList();//用于记录SWL的QSO对象,对SWL QSO做判断,防止重复。 + private SWLQsoList swlQsoList = new SWLQsoList();//用于记录SWL的QSO对象,对SWL QSO做判断,防止重复。 public MutableLiveData> mutableSerialPorts = new MutableLiveData<>(); @@ -189,7 +187,7 @@ public class MainViewModel extends ViewModel { //发射信号用的消息列表 //public ArrayList transmitMessages = new ArrayList<>(); //public MutableLiveData> mutableTransmitMessages = new MutableLiveData<>(); - public MutableLiveData mutableTransmitMessagesCount=new MutableLiveData<>(); + public MutableLiveData mutableTransmitMessagesCount = new MutableLiveData<>(); public boolean deNoise = false;//在频谱中抑制噪声 @@ -199,7 +197,7 @@ public class MainViewModel extends ViewModel { public String queryKey = "";//查询的关键字 public int queryFilter = 0;//过滤,0全部,1,确认,2,未确认 public MutableLiveData mutableQueryFilter = new MutableLiveData<>(); - public ArrayList callsignRecords=new ArrayList<>(); + public ArrayList callsignRecords = new ArrayList<>(); //public ArrayList qslRecords=new ArrayList<>(); //******************************************** //关注呼号的列表 @@ -279,7 +277,9 @@ public class MainViewModel extends ViewModel { } @Override - public void afterDecode(long utc, float time_sec, int sequential, ArrayList messages) { + public void afterDecode(long utc, float time_sec, int sequential + , ArrayList messages, boolean isDeep) { + if (messages.size() == 0) return;//没有解码出消息,不触发动作 synchronized (ft8Messages) { ft8Messages.addAll(messages);//添加消息到列表 @@ -293,20 +293,37 @@ public class MainViewModel extends ViewModel { findIncludedCallsigns(messages);//查找符合条件的消息,放到呼叫列表中 //检查发射程序。从消息列表中解析发射的程序 - ft8TransmitSignal.parseMessageToFunction(messages); + //超出周期2秒钟,就不应该解析了 + if (!ft8TransmitSignal.isTransmitting() + && (ft8SignalListener.timeSec + + GeneralVariables.pttDelay + + GeneralVariables.transmitDelay <= 2000)) {//考虑网络模式,发射时长是13秒 + ft8TransmitSignal.parseMessageToFunction(messages);//解析消息,并处理 + } + + currentMessages = messages; + + if (isDeep) { + currentDecodeCount += messages.size(); + } else { + currentDecodeCount = messages.size(); + } mutableIsDecoding.postValue(false);//解码的状态,会触发频谱图中的标记动作 - currentMessages = messages;//保存本次解码的消息 - getQTHRunnable.messages=messages; + getQTHRunnable.messages = messages; getQTHThreadPool.execute(getQTHRunnable);//用线程池的方式查询归属地 + //此变量也是告诉消息列表变化的 + mutable_Decoded_Counter.postValue( + currentDecodeCount);//告知界面消息的总数量 + if (GeneralVariables.saveSWLMessage) { databaseOpr.writeMessage(messages);//把SWL消息写到数据库 } //检查QSO of SWL,并保存到SWLQSOTable中的通联列表qsoList中 - if (GeneralVariables.saveSWL_QSO){ + if (GeneralVariables.saveSWL_QSO) { swlQsoList.findSwlQso(messages, ft8Messages, new SWLQsoList.OnFoundSwlQso() { @Override public void doFound(QSLRecord record) { @@ -366,12 +383,12 @@ public class MainViewModel extends ViewModel { } @Override - public void onAfterGenerate(float[] data) { + public void onTransmitByWifi(Ft8Message msg) { if (GeneralVariables.connectMode == ConnectMode.NETWORK) { if (baseRig != null) { if (baseRig.isConnected()) { - sendWaveDataRunnable.baseRig=baseRig; - sendWaveDataRunnable.data=data; + sendWaveDataRunnable.baseRig = baseRig; + sendWaveDataRunnable.message = msg; //以线程池的方式执行网络数据包发送 sendWaveDataThreadPool.execute(sendWaveDataRunnable); } @@ -406,13 +423,14 @@ public class MainViewModel extends ViewModel { } } - public void setTransmitIsFreeText(boolean isFreeText){ - if (ft8TransmitSignal!=null) { + public void setTransmitIsFreeText(boolean isFreeText) { + if (ft8TransmitSignal != null) { ft8TransmitSignal.setTransmitFreeText(isFreeText); } } - public boolean getTransitIsFreeText(){ - if (ft8TransmitSignal!=null){ + + public boolean getTransitIsFreeText() { + if (ft8TransmitSignal != null) { return ft8TransmitSignal.isTransmitFreeText(); } return false; @@ -424,12 +442,12 @@ public class MainViewModel extends ViewModel { * * @param messages 消息 */ - private void findIncludedCallsigns(ArrayList messages) { + private synchronized void findIncludedCallsigns(ArrayList messages) { Log.d(TAG, "findIncludedCallsigns: 查找关注的呼号"); if (ft8TransmitSignal.isActivated() && ft8TransmitSignal.sequential != UtcTimer.getNowSequential()) { return; } - int count=0; + int count = 0; for (Ft8Message msg : messages) { //与我的呼号有关,与关注的呼号有关 if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign) @@ -746,9 +764,12 @@ public class MainViewModel extends ViewModel { case InstructionSet.XIEGU_6100: baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100 break; + case InstructionSet.KENWOOD_TS2000: + baseRig = new KenwoodTS2000Rig();//建伍TS2000 + break; } - mutableIsFlexRadio.postValue(GeneralVariables.instructionSet==InstructionSet.FLEX_NETWORK); + mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK); } @@ -863,8 +884,9 @@ public class MainViewModel extends ViewModel { private static class GetQTHRunnable implements Runnable { MainViewModel mainViewModel; ArrayList messages; + public GetQTHRunnable(MainViewModel mainViewModel) { - this.mainViewModel=mainViewModel; + this.mainViewModel = mainViewModel; } @@ -872,20 +894,19 @@ public class MainViewModel extends ViewModel { public void run() { CallsignDatabase.getMessagesLocation( GeneralVariables.callsignDatabase.getDb(), messages); - - mainViewModel.currentDecodeCount = messages.size(); - //此变量也是告诉消息列表变化的 - mainViewModel.mutable_Decoded_Counter.postValue( - mainViewModel.currentDecodeCount);//告知界面消息的总数量 + mainViewModel.mutableFt8MessageList.postValue(mainViewModel.ft8Messages); } } - private static class SendWaveDataRunnable implements Runnable{ + + private static class SendWaveDataRunnable implements Runnable { BaseRig baseRig; - float[] data; + //float[] data; + Ft8Message message; + @Override public void run() { - if (baseRig!=null&&data!=null){ - baseRig.sendWaveData(data);//实际生成的数据是12.64+0.04,0.04是生成的0数据 + if (baseRig != null && message != null) { + baseRig.sendWaveData(message);//实际生成的数据是12.64+0.04,0.04是生成的0数据 } } } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/MessageHashMap.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothConstants.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialListener.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialService.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothSerialSocket.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/bluetooth/BluetoothStateBroadcastReceive.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java old mode 100755 new mode 100644 index 353b01f..dde5f67 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignDatabase.java @@ -105,9 +105,12 @@ public class CallsignDatabase extends SQLiteOpenHelper { /** * 更新消息中的位置及经纬度信息 * - * @param messages 消息列表 + * @param ft8Messages 消息列表 */ - public static void getMessagesLocation(SQLiteDatabase db, ArrayList messages) { + public static synchronized void getMessagesLocation(SQLiteDatabase db, ArrayList ft8Messages ) { + if (ft8Messages==null) return; + ArrayList messages = new ArrayList<>(ft8Messages);//防止线程访问冲突 + for (Ft8Message msg : messages) { if (msg.i3==0&&msg.n3==0) continue;//如果是自由文本,就不查了 CallsignInfo fromCallsignInfo = getCallsignInfo(db, @@ -142,7 +145,6 @@ public class CallsignDatabase extends SQLiteOpenHelper { } msg.toLatLng = new LatLng(toCallsignInfo.Latitude, toCallsignInfo.Longitude*-1); } - } } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignFileOperation.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/CallsignInfo.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/callsign/OnAfterQueryCallsignLocation.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/BluetoothRigConnector.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/ConnectMode.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/IComWifiConnector.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/OnConnectorStateChanged.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/connector/OnConnectorStateChanged.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountDbOpr.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountDbOpr.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountFragment.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/count/CountInfoAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/ControlMode.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/ControlMode.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index fa524c1..0a9feb2 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DatabaseOpr.java @@ -823,8 +823,6 @@ public class DatabaseOpr extends SQLiteOpenHelper { return false; } - //record.getToCallsign() - String querySQL; if (!checkQSLCallsign(record)) {//如果不存在记录,就添加 querySQL = "INSERT INTO QslCallsigns (callsign" + @@ -1147,9 +1145,19 @@ public class DatabaseOpr extends SQLiteOpenHelper { @Override protected Void doInBackground(Void... voids) { String querySQL; - querySQL = "INSERT INTO SWLQSOTable(call, gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + - "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare," + - "comment)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + //删除之前重复的记录 + querySQL = "DELETE FROM SWLQSOTable where ([call]=?) and (station_callsign=?) and (qso_date=?) and(time_on=?) and (freq=?)"; + databaseOpr.db.execSQL(querySQL, new String[]{ + qslRecord.getToCallsign() + , qslRecord.getMyCallsign() + , qslRecord.getQso_date() + , qslRecord.getTime_on() + , BaseRigOperation.getFrequencyFloat(qslRecord.getBandFreq()) + }); + //添加记录 + querySQL = "INSERT INTO SWLQSOTable([call], gridsquare, mode, rst_sent, rst_rcvd, qso_date, " + + "time_on, qso_date_off, time_off, band, freq, station_callsign, my_gridsquare,comment)\n" + + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; databaseOpr.db.execSQL(querySQL, new String[]{qslRecord.getToCallsign() , qslRecord.getToMaidenGrid() @@ -1449,7 +1457,8 @@ public class DatabaseOpr extends SQLiteOpenHelper { } String querySQL = "select * from QSLTable where ([call] like ?) \n" + filterStr + - " order by ID desc\n"+ + " ORDER BY qso_date DESC, time_off DESC\n"+ + //" order by ID desc\n"+ limitStr; Cursor cursor = db.rawQuery(querySQL, new String[]{"%" + callsign + "%"}); ArrayList records = new ArrayList<>(); @@ -1774,8 +1783,7 @@ public class DatabaseOpr extends SQLiteOpenHelper { GeneralVariables.baudRate = result.equals("") ? 19200 : Integer.parseInt(result); } if (name.equalsIgnoreCase("bandFreq")) { - //--todo---把波段的索引数改成频率。用bandFreq值 - GeneralVariables.band = result.equals("") ? 14074000 : Long.valueOf(result); + GeneralVariables.band = result.equals("") ? 14074000 : Long.parseLong(result); GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band); } if (name.equalsIgnoreCase("ctrMode")) { @@ -1833,6 +1841,15 @@ public class DatabaseOpr extends SQLiteOpenHelper { if (name.equalsIgnoreCase("saveSWLQSO")) {//保存解码信息 GeneralVariables.saveSWL_QSO = result.equals("1"); } + if (name.equalsIgnoreCase("audioBits")) {//输出音频是否32位浮点 + GeneralVariables.audioOutput32Bit = result.equals("1"); + } + if (name.equalsIgnoreCase("audioRate")) {//输出音频是否32位浮点 + GeneralVariables.audioSampleRate =Integer.parseInt( result); + } + if (name.equalsIgnoreCase("deepMode")) {//是不是深度解码模式 + GeneralVariables.deepDecodeMode =result.equals("1"); + } } cursor.close(); @@ -1843,106 +1860,6 @@ public class DatabaseOpr extends SQLiteOpenHelper { onAfterQueryConfig.doOnAfterQueryConfig(null, null); } - /* - String result = ""; - GeneralVariables.setMyMaidenheadGrid(getConfigByKey("grid")); - - GeneralVariables.myCallsign = getConfigByKey("callsign"); - - GeneralVariables.toModifier = getConfigByKey("toModifier"); - - String callsign = GeneralVariables.myCallsign; - if (callsign.length() > 0) { - Ft8Message.hashList.addHash(FT8Package.getHash22(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash12(callsign), callsign); - Ft8Message.hashList.addHash(FT8Package.getHash10(callsign), callsign); - } - result = getConfigByKey("freq"); - float freq = 1000; - try { - freq = Float.parseFloat(result); - } catch (Exception e) { - Log.e(TAG, "doInBackground: " + e.getMessage()); - } - //GeneralVariables.setBaseFrequency(result.equals("") ? 1000 : Float.parseFloat(result)); - GeneralVariables.setBaseFrequency(freq); - - result = getConfigByKey("synFreq"); - GeneralVariables.synFrequency = !(result.equals("") || result.equals("0")); - - result = getConfigByKey("transDelay"); - if (result.matches("^\\d{1,4}$")) {//正则表达式,1-4位长度的数字 - GeneralVariables.transmitDelay = Integer.parseInt(result); - } else { - GeneralVariables.transmitDelay = FT8Common.FT8_TRANSMIT_DELAY; - } - - result = getConfigByKey("civ"); - GeneralVariables.civAddress = result.equals("") ? 0xa4 : Integer.parseInt(result, 16); - - result = getConfigByKey("baudRate"); - GeneralVariables.baudRate = result.equals("") ? 19200 : Integer.parseInt(result); - - //--todo---把波段的索引数改成频率。用bandFreq值 - result = getConfigByKey("bandFreq"); - - GeneralVariables.band = result.equals("") ? 14074000 : Long.valueOf(result); - GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(GeneralVariables.band); - - - result = getConfigByKey("ctrMode"); - GeneralVariables.controlMode = result.equals("") ? ControlMode.VOX : Integer.parseInt(result); - - result = getConfigByKey("model");//电台型号 - GeneralVariables.modelNo = result.equals("") ? 0 : Integer.parseInt(result); - - result = getConfigByKey("instruction");//指令集 - GeneralVariables.instructionSet = result.equals("") ? 0 : Integer.parseInt(result); - - - result = getConfigByKey("launchSupervision");//发射监管 - GeneralVariables.launchSupervision = result.equals("") ? - GeneralVariables.DEFAULT_LAUNCH_SUPERVISION : Integer.parseInt(result); - - result = getConfigByKey("noReplyLimit");//发射监管 - GeneralVariables.noReplyLimit = result.equals("") ? 0 : Integer.parseInt(result); - - result = getConfigByKey("autoFollowCQ");//自动关注CQ - GeneralVariables.autoFollowCQ = (result.equals("") || result.equals("1")); - - result = getConfigByKey("autoCallFollow");//自动关注CQ - GeneralVariables.autoCallFollow = (result.equals("") || result.equals("1")); - - result = getConfigByKey("pttDelay");//ptt延时设置 - GeneralVariables.pttDelay = result.equals("") ? 100 : Integer.parseInt(result); - - - //获取icom网络连接参数 - result = getConfigByKey("icomIp");//IcomIp地址 - GeneralVariables.icomIp = result.equals("") ? "255.255.255.255" : result; - result = getConfigByKey("icomPort");//Icom端口 - GeneralVariables.icomUdpPort = result.equals("") ? 50001 : Integer.parseInt(result); - result = getConfigByKey("icomUserName");//Icom用户名 - GeneralVariables.icomUserName = result.equals("") ? "ic705" : result; - result = getConfigByKey("icomPassword");//Icom密码 - GeneralVariables.icomPassword = result; - - //输出音量大小 - result = getConfigByKey("volumeValue"); - GeneralVariables.volumePercent = result.equals("") ? 1.0f : Float.parseFloat(result) / 100f; - - //排除的呼号 - result = getConfigByKey("excludedCallsigns"); - GeneralVariables.addExcludedCallsigns(result); - - GetAllQSLCallsign.get(db);//获取通联过的呼号 - - if (onAfterQueryConfig != null) { - onAfterQueryConfig.doOnAfterQueryConfig(null, null); - } - - - */ return null; } } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/DxccObject.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryConfig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryConfig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryFollowCallsigns.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterQueryFollowCallsigns.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnAfterWriteConfig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OnGetCallsign.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/OperationBand.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/database/RigNameList.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexCommand.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexCommand.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterInfos.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterInfos.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeterType.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeters.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeters.java deleted file mode 100755 index d5b7bee..0000000 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexMeters.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.bg7yoz.ft8cn.flex; -/** - * flexRadio仪表处理 - * @author BG7YOZ - */ - -import android.util.Log; - -import java.util.HashMap; - -public class FlexMeters extends HashMap { - private static final String TAG="FlexMeters"; - public FlexMeters(String content) { - String[] temp =content.substring(content.indexOf("meter ")+"meter ".length()).split("#"); - for (int i = 0; i < temp.length; i++) { - String[] val = temp[i].split("="); - if (val.length == 2) { - if (val[0].contains(".")) { - int index = Integer.parseInt(val[0].substring(0, val[0].indexOf("."))); - FlexMeter meter; - if (this.containsKey(index)){ - meter=this.get(index); - }else { - meter=new FlexMeter(); - this.put(index,meter); - } - - if (val[0].toLowerCase().contains(".src")) { - meter.src = val[1]; - } - if (val[0].toLowerCase().contains(".num")) { - meter.num = val[1]; - } - if (val[0].toLowerCase().contains(".nam")) { - meter.nam = val[1]; - } - if (val[0].toLowerCase().contains(".low")) { - meter.low = val[1]; - } - if (val[0].toLowerCase().contains(".hi")) { - meter.hi = val[1]; - } - if (val[0].toLowerCase().contains(".desc")) { - meter.desc = val[1]; - } - if (val[0].toLowerCase().contains(".unit")) { - meter.unit = val[1]; - } - if (val[0].toLowerCase().contains(".fps")) { - meter.fps = val[1]; - } - if (val[0].toLowerCase().contains(".peak")) { - meter.peak = val[1]; - } - } - } - } - } - - public void getAllMeters(){ - for (int key:this.keySet()) { - //s.append(String.format("%d->%s\n",key,get(key))); - Log.e(TAG, "getAllMeters: "+String.format("ID:%d FIELDS:%s\n",key,get(key)) ); - } - } - - - public static class FlexMeter { - public String src; - public String num; - public String nam; - public String low; - public String hi; - public String desc; - public String unit; - public String fps; - public String peak; - - @Override - public String toString() { - return "{" + - "src='" + src + '\'' + - ", num='" + num + '\'' + - ", nam='" + nam + '\'' + - ", low='" + low + '\'' + - ", hi='" + hi + '\'' + - ", desc='" + desc + '\'' + - ", unit='" + unit + '\'' + - ", fps='" + fps + '\'' + - ", peak='" + peak + '\'' + - '}'; - } - } -} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexRadio.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexResponseStyle.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/flex/FlexResponseStyle.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java old mode 100755 new mode 100644 index 4e6e38c..618b442 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatView.java @@ -215,7 +215,7 @@ public class FloatView extends ConstraintLayout { WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); width = wm.getDefaultDisplay().getWidth(); height = wm.getDefaultDisplay().getHeight(); - } else {//--TODO--这部分基本没有执行过 + } else {//这部分基本没有执行过 width = parentViewWidth; height = parentViewHeight; } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/floatview/FloatViewButton.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java new file mode 100644 index 0000000..c516314 --- /dev/null +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/A91List.java @@ -0,0 +1,31 @@ +package com.bg7yoz.ft8cn.ft8listener; + +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; + +import java.util.ArrayList; + +public class A91List { + + public ArrayList list=new ArrayList<>(); + public void clear(){ + list.clear(); + } + public void add(byte[] data,float freq,float sec){ + A91 a91=new A91(data,sec,freq); + list.add(a91); + } + public int size(){ + return list.size(); + } + public static class A91{ + public byte[] a91 ;//= new byte[GenerateFT8.FTX_LDPC_K_BYTES]; + public float time_sec = 0;//时间偏移(秒) + public float freq_hz = 0;//频率 + + public A91(byte[] a91, float time_sec, float freq_hz) { + this.a91 = a91; + this.time_sec = time_sec; + this.freq_hz = freq_hz; + } + } +} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java old mode 100755 new mode 100644 index 81bfffa..850d04d --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/FT8SignalListener.java @@ -14,9 +14,12 @@ import com.bg7yoz.ft8cn.FT8Common; import com.bg7yoz.ft8cn.Ft8Message; import com.bg7yoz.ft8cn.GeneralVariables; import com.bg7yoz.ft8cn.database.DatabaseOpr; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; import com.bg7yoz.ft8cn.timer.OnUtcTimer; import com.bg7yoz.ft8cn.timer.UtcTimer; import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone; +import com.bg7yoz.ft8cn.wave.WaveFileReader; +import com.bg7yoz.ft8cn.wave.WaveFileWriter; import java.util.ArrayList; @@ -27,15 +30,15 @@ public class FT8SignalListener { private final OnFt8Listen onFt8Listen;//当开始监听,解码结束后触发的事件 //private long band; public MutableLiveData decodeTimeSec = new MutableLiveData<>();//解码的时长 + public long timeSec=0;//解码的时长 + private OnWaveDataListener onWaveDataListener; + private DatabaseOpr db; - //用于保存解码数据的地址 -// private final long ft8Decoder=InitDecoder(0, FT8Common.SAMPLE_RATE -// , 15*24000, true);; + private final A91List a91List = new A91List();//a91列表 - //private String myCallsign; static { System.loadLibrary("ft8cn"); @@ -50,7 +53,6 @@ public class FT8SignalListener { this.onFt8Listen = onFt8Listen; this.db = db; - //创建动作触发器,与UTC时间同步,以15秒一个周期,DoOnSecTimer是在周期起始时触发的事件。150是15秒 utcTimer = new UtcTimer(FT8Common.FT8_SLOT_TIME_M, false, new OnUtcTimer() { @Override @@ -108,6 +110,7 @@ public class FT8SignalListener { } public void decodeFt8(long utc, float[] voiceData) { + new Thread(new Runnable() { @Override public void run() { @@ -115,70 +118,137 @@ public class FT8SignalListener { if (onFt8Listen != null) { onFt8Listen.beforeListen(utc); } - ArrayList ft8Messages = new ArrayList<>(); - Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE); - ft8Message.utcTime = utc; - ft8Message.band = GeneralVariables.band; - //此处,改为reset,是因为在cpp部分,改为一个变量,不是以指针,申请新内存的方式处理了。 + + ///读入音频数据,并做预处理 //其实这种方式要注意一个问题,在一个周期之内,必须解码完毕,否则新的解码又要开始了 - long ft8Decoder = InitDecoder(ft8Message.utcTime, FT8Common.SAMPLE_RATE + long ft8Decoder = InitDecoder(utc, FT8Common.SAMPLE_RATE , voiceData.length, true); - - //DecoderFt8Reset(ft8Decoder, ft8Message.utcTime, voiceData.length); - - DecoderMonitorPressFloat(voiceData, ft8Decoder); + DecoderMonitorPressFloat(voiceData, ft8Decoder);//读入音频数据 - int num_candidates = DecoderFt8FindSync(ft8Decoder); - float dt = 0; - int dtAverage = 0; - for (int idx = 0; idx < num_candidates; ++idx) { + ArrayList allMsg = new ArrayList<>(); + ArrayList msgs = runDecode(ft8Decoder, utc, false); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, false); + } - try {//做一下解码失败保护 - if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) { - if (ft8Message.isValid) { - Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉 - if (checkMessageSame(ft8Messages, msg)) { - continue; - } - dt += ft8Message.time_sec; - dtAverage++; - //ft8Messages.add(new Ft8Message(ft8Message)); - ft8Messages.add(msg); - } - } - } catch (Exception e) { - Log.e(TAG, "run: " + e.getMessage()); + + if (GeneralVariables.deepDecodeMode) {//进入深度解码模式 + msgs = runDecode(ft8Decoder, utc, true); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); } - } - float time_sec = 0f; - if (dtAverage != 0) { - time_sec = dt / dtAverage; - //utcTimer.setTime_sec(Math.round(time_sec * 1000)); - } else {//当检测不到时,不要偏移时间 - utcTimer.setTime_sec(Math.round(0)); - } + do { + if (timeSec > FT8Common.DEEP_DECODE_TIMEOUT) break;//此处做超时检测,超过一定时间(7秒),就不做减码操作了 + //减去解码的信号 + ReBuildSignal.subtractSignal(ft8Decoder, a91List); + + //再做一次解码 + msgs = runDecode(ft8Decoder, utc, true); + addMsgToList(allMsg, msgs); + timeSec = System.currentTimeMillis() - time; + decodeTimeSec.postValue(timeSec);//解码耗时 + if (onFt8Listen != null) { + onFt8Listen.afterDecode(utc, averageOffset(allMsg), UtcTimer.sequential(utc), msgs, true); + } + + } while (msgs.size() > 0 ); + + } //移到finalize() 方法中调用了 DeleteDecoder(ft8Decoder); - - if (onFt8Listen != null) { - onFt8Listen.afterDecode(utc, time_sec, UtcTimer.sequential(utc), ft8Messages); - } - - decodeTimeSec.postValue(System.currentTimeMillis() - time);//解码耗时 - Log.d(TAG, String.format("解码耗时:%d毫秒", System.currentTimeMillis() - time)); + } }).start(); } + private ArrayList runDecode(long ft8Decoder, long utc, boolean isDeep) { + ArrayList ft8Messages = new ArrayList<>(); + Ft8Message ft8Message = new Ft8Message(FT8Common.FT8_MODE); + + ft8Message.utcTime = utc; + ft8Message.band = GeneralVariables.band; + a91List.clear(); + + setDecodeMode(ft8Decoder, isDeep);//设置迭代次数,isDeep==true,迭代次数增加 + + int num_candidates = DecoderFt8FindSync(ft8Decoder);//最多120个 + //long startTime = System.currentTimeMillis(); + for (int idx = 0; idx < num_candidates; ++idx) { + //todo 应当做一下超时计算 + try {//做一下解码失败保护 + if (DecoderFt8Analysis(idx, ft8Decoder, ft8Message)) { + + if (ft8Message.isValid) { + Ft8Message msg = new Ft8Message(ft8Message);//此处使用msg,是因为有的哈希呼号会把<...>替换掉 + byte[] a91 = DecoderGetA91(ft8Decoder); + a91List.add(a91, ft8Message.freq_hz, ft8Message.time_sec); + + if (checkMessageSame(ft8Messages, msg)) { + continue; + } + + msg.isWeakSignal = isDeep;//是不是弱信号 + ft8Messages.add(msg); + + } + } + } catch (Exception e) { + Log.e(TAG, "run: " + e.getMessage()); + } + + } + + + return ft8Messages; + } + + /** + * 计算平均时间偏移值 + * + * @param messages 消息列表 + * @return 偏移值 + */ + private float averageOffset(ArrayList messages) { + if (messages.size() == 0) return 0f; + float dt = 0; + //int dtAverage = 0; + for (Ft8Message msg : messages) { + dt += msg.time_sec; + } + return dt / messages.size(); + } + + /** + * 把消息添加到列表中 + * + * @param allMsg 消息列表 + * @param newMsg 新的消息 + */ + private void addMsgToList(ArrayList allMsg, ArrayList newMsg) { + for (int i = newMsg.size() - 1; i >= 0; i--) { + if (checkMessageSame(allMsg, newMsg.get(i))) { + newMsg.remove(i); + } else { + allMsg.add(newMsg.get(i)); + } + } + } + /** * 检查消息列表里同样的内容是否存在 * @@ -204,14 +274,15 @@ public class FT8SignalListener { super.finalize(); } - public OnWaveDataListener getOnWaveDataListener() { - return onWaveDataListener; - } + public void setOnWaveDataListener(OnWaveDataListener onWaveDataListener) { this.onWaveDataListener = onWaveDataListener; } + + + /** * 解码的第一步,初始化解码器,获取解码器的地址。 * @@ -261,5 +332,7 @@ public class FT8SignalListener { public native void DecoderFt8Reset(long decoder, long utcTime, int num_samples); + public native byte[] DecoderGetA91(long decoder);//获取当前message的a91数据 + public native void setDecodeMode(long decoder, boolean isDeep);//设置解码的模式,isDeep=true是多次迭代,=false是快速迭代 } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java old mode 100755 new mode 100644 index 919625f..04492d0 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/OnFt8Listen.java @@ -23,5 +23,5 @@ public interface OnFt8Listen { * @param sequential 当前的时序 * @param messages 消息列表 */ - void afterDecode(long utc,float time_sec,int sequential, ArrayList messages); + void afterDecode(long utc,float time_sec,int sequential, ArrayList messages,boolean isDeep); } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java new file mode 100644 index 0000000..5e2a3a5 --- /dev/null +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8listener/ReBuildSignal.java @@ -0,0 +1,28 @@ +package com.bg7yoz.ft8cn.ft8listener; + +import android.util.Log; + +import com.bg7yoz.ft8cn.FT8Common; +import com.bg7yoz.ft8cn.Ft8Message; +import com.bg7yoz.ft8cn.ft8transmit.GenerateFT8; +import com.bg7yoz.ft8cn.wave.WaveFileWriter; + +import java.util.ArrayList; + +public class ReBuildSignal { + private static String TAG = "ReBuildSignal"; + static { + System.loadLibrary("ft8cn"); + } + + + public static void subtractSignal(long decoder,A91List a91List){ + for (A91List.A91 a91 : a91List.list) { + doSubtractSignal(decoder,a91.a91,FT8Common.SAMPLE_RATE,a91.freq_hz,a91.time_sec); + } + } + + private static native void doSubtractSignal(long decoder,byte[] payload,int sample_rate + ,float frequency,float time_sec); + +} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8signal/FT8Package.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index 8e3607c..14626ce --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FT8TransmitSignal.java @@ -1,6 +1,7 @@ package com.bg7yoz.ft8cn.ft8transmit; /** * 与发射信号有关的类。包括分析通联过程的自动程序。 + * * @author BGY70Z * @date 2023-03-20 */ @@ -33,8 +34,8 @@ import java.util.concurrent.Executors; public class FT8TransmitSignal { private static final String TAG = "FT8TransmitSignal"; - private boolean transmitFreeText=false; - private String freeText="FREE TEXT"; + private boolean transmitFreeText = false; + private String freeText = "FREE TEXT"; private final DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库 private TransmitCallsign toCallsign;//目标呼号 @@ -64,7 +65,7 @@ public class FT8TransmitSignal { private int receivedReport = 0;//我接收到的报告 private int receiveTargetReport = -100;//发送给对方的信号报告 //******************************************** - private final OnTransmitSuccess onTransmitSuccess ;//一般是用于保存QSL数据 + private final OnTransmitSuccess onTransmitSuccess;//一般是用于保存QSL数据 //防止播放中止,变量不能放在方法中 @@ -80,7 +81,7 @@ public class FT8TransmitSignal { private final OnDoTransmitted onDoTransmitted;//一般是用于打开关闭PTT private final ExecutorService doTransmitThreadPool = Executors.newCachedThreadPool(); - private final DoTransmitRunnable doTransmitRunnable=new DoTransmitRunnable(this); + private final DoTransmitRunnable doTransmitRunnable = new DoTransmitRunnable(this); static { System.loadLibrary("ft8cn"); @@ -107,7 +108,7 @@ public class FT8TransmitSignal { GeneralVariables.mutableVolumePercent.observeForever(new Observer() { @Override public void onChanged(Float aFloat) { - if (audioTrack!=null){ + if (audioTrack != null) { audioTrack.setVolume(aFloat); } } @@ -187,7 +188,6 @@ public class FT8TransmitSignal { // @SuppressLint("DefaultLocale") // @Override // public void run() { -// //--todo---- // //此处可能要修改,维护一个列表。把每个呼号,网格,时间,波段,记录下来 // if (functionOrder == 1 || functionOrder == 2) {//当消息处于1或2时,说明开始了通联 // messageStartTime = UtcTimer.getSystemTime(); @@ -336,9 +336,9 @@ public class FT8TransmitSignal { //发射模式6,CQ BG7YOZ OL50 case 6: resetTargetReport();//把给对方的信号报告,接收到对方的信号报告记录复位成-100 - Ft8Message msg=new Ft8Message(1, 0, "CQ", GeneralVariables.myCallsign + Ft8Message msg = new Ft8Message(1, 0, "CQ", GeneralVariables.myCallsign , GeneralVariables.getMyMaidenhead4Grid()); - msg.modifier=GeneralVariables.toModifier; + msg.modifier = GeneralVariables.toModifier; return msg; } @@ -365,18 +365,42 @@ public class FT8TransmitSignal { setCurrentFunctionOrder(functionOrder);//设置当前消息 } + /** + * 为了最大限度兼容,把32位浮点转换成16位整型,有些声卡不支持32位的浮点。 + * @param buffer 32位浮点音频 + * @return 16位整型 + */ + private short[] float2Short(float[] buffer) { + short[] temp = new short[buffer.length + 8];//多出8个为0的数据包,是为了兼容QP-7C的RP2040音频判断 + for (int i = 0; i < buffer.length; i++) { + float x = buffer[i]; + if (x > 1.0) + x = 1.0f; + else if (x < -1.0) + x = -1.0f; + temp[i] = (short) (x * 32767.0); + } + return temp; + } - private void playFT8Signal(float[] buffer) { + //private void playFT8Signal(float[] buffer) { + private void playFT8Signal(Ft8Message msg) { - //todo--实现网络发送模式 if (GeneralVariables.connectMode == ConnectMode.NETWORK) {//网络方式就不播放音频了 Log.d(TAG, "playFT8Signal: 进入网络发射程序,等待音频发送。"); + + + if (onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 + onDoTransmitted.onTransmitByWifi(msg); + } + + long now = System.currentTimeMillis(); while (isTransmitting) {//等待音频数据包发送完毕再退出,以触发afterTransmitting try { Thread.sleep(1); long current = System.currentTimeMillis() - now; - if (current > 13500) {//实际发射的时长 + if (current > 13000) {//实际发射的时长 isTransmitting = false; break; } @@ -390,26 +414,45 @@ public class FT8TransmitSignal { } - Log.d(TAG, "playFT8Signal: 准备声卡播放...."); + //进入声卡模式 + float[] buffer; + buffer = GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency() + , GeneralVariables.audioSampleRate); + if (buffer == null) { + afterPlayAudio(); + return; + } + + Log.d(TAG, String.format("playFT8Signal: 准备声卡播放....位数:%s,采样率:%d" + , GeneralVariables.audioOutput32Bit ? "Float32" : "Int16" + , GeneralVariables.audioSampleRate)); attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); - myFormat = new AudioFormat.Builder().setSampleRate(FT8Common.SAMPLE_RATE) - //.setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setEncoding(AudioFormat.ENCODING_PCM_FLOAT) + //myFormat = new AudioFormat.Builder().setSampleRate(FT8Common.SAMPLE_RATE) + myFormat = new AudioFormat.Builder().setSampleRate(GeneralVariables.audioSampleRate) + .setEncoding(GeneralVariables.audioOutput32Bit ? //浮点与整型 + AudioFormat.ENCODING_PCM_FLOAT : AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(); int mySession = 0; audioTrack = new AudioTrack(attributes, myFormat - , 12000 * 15 * 4, AudioTrack.MODE_STATIC + , GeneralVariables.audioOutput32Bit ? GeneralVariables.audioSampleRate * 15 * 4 + : GeneralVariables.audioSampleRate * 15 * 2//浮点与整型 + , AudioTrack.MODE_STATIC , mySession); - - //写入数据大小 array 就是预先将音频数据加载到array数组中 - int writeResult = audioTrack.write(buffer, 0, buffer.length - , AudioTrack.WRITE_NON_BLOCKING); - + //区分32浮点和整型 + int writeResult; + if (GeneralVariables.audioOutput32Bit) { + writeResult = audioTrack.write(buffer, 0, buffer.length + , AudioTrack.WRITE_NON_BLOCKING); + } else { + short[] audio_data = float2Short(buffer); + writeResult = audioTrack.write(audio_data, 0, audio_data.length + , AudioTrack.WRITE_NON_BLOCKING); + } if (buffer.length > writeResult) { Log.e(TAG, String.format("播放缓冲区不足:%d--->%d", buffer.length, writeResult)); @@ -463,10 +506,7 @@ public class FT8TransmitSignal { messageEndTime = UtcTimer.getSystemTime();//获取结束的时间 //如对方没有网格,就从历史呼号与网格对应表中查找 - toMaidenheadGrid =GeneralVariables.getGridByCallsign(toCallsign.callsign,databaseOpr); -// if (toMaidenheadGrid.equals("")) { -// toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign,databaseOpr); -// } + toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); if (messageStartTime == 0) {//如果起始时间没有,就取现在的 messageStartTime = UtcTimer.getSystemTime(); @@ -557,6 +597,29 @@ public class FT8TransmitSignal { } } + /** + * 检查消息中from中有目标呼号的数量。当有目标呼号呼叫我的消息,返回0, + * @param messages 消息列表 + * @return 0:有目标呼叫我的,1:没有任何目标呼号发出的消息,>1:有目标呼号呼叫别人的消息 + */ + private int checkTargetCallMe(ArrayList messages){ + int fromCount=1; + for (int i = messages.size() - 1; i >= 0; i--) { + Ft8Message ft8Message = messages.get(i); + if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 + if (toCallsign == null) { + continue; + } + if (ft8Message.getCallsignTo().equals(GeneralVariables.myCallsign) + && checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)) { + return 0; + } + if (checkCallsignIsCallTo(ft8Message.getCallsignFrom(), toCallsign.callsign)){ + fromCount++;//计数器,from是目标呼号的情况 + } + } + return fromCount; + } /** * 检测本消息列表中对方回复消息的序号,如果没有,返回-1 * @@ -566,6 +629,7 @@ public class FT8TransmitSignal { private int checkFunctionOrdFromMessages(ArrayList messages) { for (int i = messages.size() - 1; i >= 0; i--) { Ft8Message ft8Message = messages.get(i); + if (ft8Message.getSequence() == sequential) continue;//同一个时序下的消息不做解析 if (toCallsign == null) { continue; } @@ -627,7 +691,7 @@ public class FT8TransmitSignal { if (msg.band != GeneralVariables.band) {//如果消息不在相同的波段内,不呼叫 continue; } - if (GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)){//如果是在过滤范围内的呼叫,不理会 + if (GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//如果是在过滤范围内的呼叫,不理会 continue; } @@ -693,7 +757,7 @@ public class FT8TransmitSignal { QSLRecord record = GeneralVariables.qslRecordList.getRecordByCallsign(toCall.callsign); if (record == null) { - toMaidenheadGrid=GeneralVariables.getGridByCallsign(toCallsign.callsign,databaseOpr); + toMaidenheadGrid = GeneralVariables.getGridByCallsign(toCallsign.callsign, databaseOpr); record = GeneralVariables.qslRecordList.addQSLRecord(new QSLRecord( messageStartTime, messageEndTime, @@ -739,20 +803,19 @@ public class FT8TransmitSignal { /** * 从关注列表解码的消息中,此处是变化发射程序的入口 * - * @param messages 消息列表 + * @param msgList 消息列表 */ //@RequiresApi(api = Build.VERSION_CODES.N) - public void parseMessageToFunction(ArrayList messages) { + public void parseMessageToFunction(ArrayList msgList) { if (GeneralVariables.myCallsign.length() < 3) { return; } - if (messages.size() > 0) {//如果当前消息的时序与发射的时序相同,不解析 - if (messages.get(0).getSequence() == sequential) { - return; - } - } else {//没有消息解析,返回 + if (msgList.size() == 0) return;//没有消息解析,返回 + + if (msgList.get(0).getSequence() == sequential) { return; } + ArrayList messages =new ArrayList<>(msgList);//防止线程冲突 int newOrder = checkFunctionOrdFromMessages(messages);//检查消息中对方回复的消息序号,-1为没有收到 @@ -760,16 +823,20 @@ public class FT8TransmitSignal { GeneralVariables.noReplyCount = 0; } - //更新一下通联的列表检查是不是在通联列表中,如果没有记录下来,就保存 updateQSlRecordList(newOrder, toCallsign); - //判断通联成功:对方回73(5)||我是73(5),且对方没回(-1)||我是RR73(4),且已经达到无回应阈值,且有无回应限制 + + // 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) + // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 + // 或我是RR73(4),且对方开始呼叫别人了,解决RR73卡死的问题 if (newOrder == 5 - || (functionOrder == 5 && newOrder == -1) + || (functionOrder == 5 && newOrder == -1)// 判断通联成功:对方回73(5)||我是73(5),且对方没回(-1) || (functionOrder == 4 && (GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit * 2) - && (GeneralVariables.noReplyLimit > 0))) { + && (GeneralVariables.noReplyLimit > 0)) // 或者我是RR73(4),且已经达到无回应阈值,且有无回应限制 + || (functionOrder ==4 && checkTargetCallMe(messages)>1) + ) { // 或我是RR73(4),且对方开始呼叫别人了 //doComplete();//做保存的动作 //进入到CQ状态 resetToCQ(); @@ -812,8 +879,10 @@ public class FT8TransmitSignal { } - //到此位置,说明没有回应,错误次数要加1 - GeneralVariables.noReplyCount++; + //到此位置,说明没有回应,错误次数要加1,弱信号检测不记无回应 + if (!messages.get(0).isWeakSignal) { + GeneralVariables.noReplyCount++; + } //如果超出无反应限定值,复位到CQ状态 if ((GeneralVariables.noReplyCount > GeneralVariables.noReplyLimit) && (GeneralVariables.noReplyLimit > 0)) { //检查关注消息列表,如果没有新的CQ,就进入到CQ状态,如果有,就转入到呼叫新的目标。 @@ -911,7 +980,7 @@ public class FT8TransmitSignal { if (GeneralVariables.myCallsign.length() < 3) { return; } - //todo---要判断我的呼号类型,才能确定i3n3 !!! + //要判断我的呼号类型,才能确定i3n3 !!! int i3 = GenerateFT8.checkI3ByCallsign(GeneralVariables.myCallsign); setTransmit(new TransmitCallsign(i3, 0, "CQ", UtcTimer.getNowSequential()) , 6, ""); @@ -964,15 +1033,15 @@ public class FT8TransmitSignal { public void setTransmitFreeText(boolean transmitFreeText) { this.transmitFreeText = transmitFreeText; - if (transmitFreeText){ + if (transmitFreeText) { ToastMessage.show(GeneralVariables.getStringFromResource(R.string.trans_free_text_mode)); - }else { + } else { ToastMessage.show((GeneralVariables.getStringFromResource(R.string.trans_standard_messge_mode))); } } - private static class DoTransmitRunnable implements Runnable{ + private static class DoTransmitRunnable implements Runnable { FT8TransmitSignal transmitSignal; public DoTransmitRunnable(FT8TransmitSignal transmitSignal) { @@ -992,25 +1061,19 @@ public class FT8TransmitSignal { //用于显示将要发射的消息内容 Ft8Message msg; - if (transmitSignal.transmitFreeText){ - msg=new Ft8Message("CQ",GeneralVariables.myCallsign,transmitSignal.freeText); - msg.i3=0; - msg.n3=0; - }else { + if (transmitSignal.transmitFreeText) { + msg = new Ft8Message("CQ", GeneralVariables.myCallsign, transmitSignal.freeText); + msg.i3 = 0; + msg.n3 = 0; + } else { msg = transmitSignal.getFunctionCommand(transmitSignal.functionOrder); } - msg.modifier=GeneralVariables.toModifier; + msg.modifier = GeneralVariables.toModifier; if (transmitSignal.onDoTransmitted != null) { //此处用于处理PTT等事件 transmitSignal.onDoTransmitted.onBeforeTransmit(msg, transmitSignal.functionOrder); } - //short[] buffer = new short[FT8Common.SAMPLE_RATE * FT8Common.FT8_SLOT_TIME]; - //79个符号,每个符号0.16秒,采样率12000, -// short[] buffer = new short[(int) (0.5f + -// GenerateFT8.num_tones * GenerateFT8.symbol_period -// * GenerateFT8.sample_rate)]; // 数据信号中的采样数0.5+79*0.16*12000]; - transmitSignal.isTransmitting = true; transmitSignal.mutableIsTransmitting.postValue(true); @@ -1020,11 +1083,11 @@ public class FT8TransmitSignal { , GeneralVariables.getBaseFrequency() , msg.getMessageText())); //生成信号 - float[] buffer=GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency()); - if (buffer==null) { - return; - } - ; +// float[] buffer=GenerateFT8.generateFt8(msg, GeneralVariables.getBaseFrequency()); +// if (buffer==null) { +// return; +// } + //电台动作可能有要有个延迟时间,所以时间并不一定完全准确 try {//给电台一个100毫秒的响应时间 Thread.sleep(GeneralVariables.pttDelay);//给PTT指令后,电台一个响应时间,默认100毫秒 @@ -1032,11 +1095,12 @@ public class FT8TransmitSignal { e.printStackTrace(); } - if (transmitSignal.onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 - transmitSignal.onDoTransmitted.onAfterGenerate(buffer); - } +// if (transmitSignal.onDoTransmitted != null) {//处理音频数据,可以给ICOM的网络模式发送 +// transmitSignal.onDoTransmitted.onAfterGenerate(buffer); +// } //播放音频 - transmitSignal.playFT8Signal(buffer); + //transmitSignal.playFT8Signal(buffer); + transmitSignal.playFT8Signal(msg); } } } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/FunctionOfTransmit.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index 5fc3a6e..675252b --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/GenerateFT8.java @@ -16,7 +16,7 @@ import com.bg7yoz.ft8cn.ui.ToastMessage; public class GenerateFT8 { private static final String TAG = "GenerateFT8"; private static final int FTX_LDPC_K = 91; - private static final int FTX_LDPC_K_BYTES = (FTX_LDPC_K + 7) / 8; + public static final int FTX_LDPC_K_BYTES = (FTX_LDPC_K + 7) / 8; private static final int FT8_NN = 79; private static final float FT8_SYMBOL_PERIOD = 0.160f; private static final float FT8_SYMBOL_BT = 2.0f; @@ -29,7 +29,7 @@ public class GenerateFT8 { private static final float symbol_bt = FT8_SYMBOL_BT;//FT8_SYMBOL_BT=2.0f private static final float slot_time = FT8_SLOT_TIME;//FT8_SLOT_TIME=15f //public static int sample_rate = 48000;//采样率 - public static int sample_rate = 12000;//采样率 + //public static int sample_rate = 12000;//采样率 static { @@ -120,8 +120,19 @@ public class GenerateFT8 { return !extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]"); } + public static float[] generateFt8(Ft8Message msg, float frequency,int sample_rate){ + return generateFt8(msg,frequency,sample_rate,true); + } - public static float[] generateFt8(Ft8Message msg, float frequency) { + /** + * 生成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; @@ -131,7 +142,11 @@ public class GenerateFT8 { //把"<>"去掉 msg.callsignTo = msg.callsignTo.replace("<", "").replace(">", ""); msg.callsignFrom = msg.callsignFrom.replace("<", "").replace(">", ""); - msg.modifier=GeneralVariables.toModifier;//修饰符 + if (hasModifier) { + msg.modifier = GeneralVariables.toModifier;//修饰符 + }else { + msg.modifier=""; + } //msg.callsignTo="CQ AzCz"; //判定用非标准呼号i3=4的条件: @@ -160,13 +175,49 @@ public class GenerateFT8 { packFreeTextTo77(msg.getMessageText(), packed); } + return generateFt8ByA91(packed,frequency,sample_rate); + /* // 其次,将二进制消息编码为FSK音调序列,79个字节 byte[] tones = new byte[num_tones]; // 79音调(符号)数组 + //此处是88个字节(91+7)/8,可以使用a91生成音频 ft8_encode(packed, tones); // 第三,将FSK音调转换为音频信号b + + int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 + + //float[] signal = new float[Ft8num_samples]; + float[] signal = new float[num_samples]; + + //Ft8num_sampleFT8声音的总采样数,不是字节数。15*12000 + //for (int i = 0; i < Ft8num_samples; i++)//把数据全部静音。 + for (int i = 0; i < num_samples; i++)//把数据全部静音。 + { + signal[i] = 0; + } + + // 用79个字节符号,生成FT8音频 + synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); + for (int i = 0; i < num_samples; i++)//把数据全部静音。 + { + if (signal[i]>1.0||signal[i]<-1.0){ + Log.e(TAG, "generateFt8: "+signal[i] ); + } + } + return signal; + */ + } + + public static float[] generateFt8ByA91(byte[] a91, float frequency,int sample_rate){ + byte[] tones = new byte[num_tones]; // 79音调(符号)数组 + //此处是12个字节(91+7)/8,可以使用a91生成音频 + ft8_encode(a91, tones); + + // 第三,将FSK音调转换为音频信号b + + int num_samples = (int) (0.5f + num_tones * symbol_period * sample_rate); // 数据信号中的采样数0.5+79*0.16*12000 @@ -184,17 +235,18 @@ public class GenerateFT8 { signal[i] = 0; } + // 用79个字节符号,生成FT8音频 synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal, 0); - for (int i = 0; i < num_samples; i++)//把数据全部静音。 - { - if (signal[i]>1.0||signal[i]<-1.0){ - Log.e(TAG, "generateFt8: "+signal[i] ); - } - } +// for (int i = 0; i < num_samples; i++)//把数据全部静音。 +// { +// if (signal[i]>1.0||signal[i]<-1.0){ +// Log.e(TAG, "generateFt8: "+signal[i] ); +// } +// } return signal; - } + private static native int packFreeTextTo77(String msg, byte[] c77); private static native int pack77(String msg, byte[] c77); diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java old mode 100755 new mode 100644 index 9dca03a..c519e83 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnDoTransmitted.java @@ -10,5 +10,5 @@ import com.bg7yoz.ft8cn.Ft8Message; public interface OnDoTransmitted { void onBeforeTransmit(Ft8Message message,int functionOder); void onAfterTransmit(Ft8Message message, int functionOder); - void onAfterGenerate(float[] data); + void onTransmitByWifi(Ft8Message message); } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/OnTransmitSuccess.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QSLRecord.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/QslRecordList.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ft8transmit/TransmitCallsign.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridInfoWindow.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java old mode 100755 new mode 100644 index 84f03b8..eff74c3 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridOsmMapView.java @@ -449,13 +449,11 @@ public class GridOsmMapView { if (fromLatLng == null) { //todo 把呼号转为国家的经纬度 return null; - //fromLatLng = message.fromLatLng; } if (toLatLng == null) { //todo 把呼号转为国家的经纬度 return null; - //toLatLng = message.toLatLng; } final GridPolyLine line = new GridPolyLine(gridMapView, fromLatLng, toLatLng, recordStr); return line; diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/grid_tracker/GridRecordInfoWindow.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index 3e195a3..baf5424 --- 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 @@ -1,6 +1,7 @@ package com.bg7yoz.ft8cn.grid_tracker; /** * 网格追踪的主窗口。 + * * @author BGY70Z * @date 2023-03-20 */ @@ -78,7 +79,6 @@ public class GridTrackerMainActivity extends AppCompatActivity { private MutableLiveData> qslRecordList = new MutableLiveData<>(); - @SuppressLint("NotifyDataSetChanged") protected void doAfterCreate() { //设置消息列表 @@ -101,7 +101,7 @@ public class GridTrackerMainActivity extends AppCompatActivity { } //画日志界面查询出的全部消息 String queryKey = intentGet.getStringExtra("qslAll"); - int queryFilter=intentGet.getIntExtra("queryFilter",0); + int queryFilter = intentGet.getIntExtra("queryFilter", 0); if (queryKey != null) { ToastMessage.show(GeneralVariables.getStringFromResource(R.string.tracker_query_qso_info)); mainViewModel.databaseOpr.getQSLRecordByCallsign(true, 0, queryKey, queryFilter @@ -187,30 +187,68 @@ public class GridTrackerMainActivity extends AppCompatActivity { @SuppressLint({"DefaultLocale", "NotifyDataSetChanged"}) @Override public void onChanged(Integer integer) { - callingListAdapter.notifyDataSetChanged(); +// callingListAdapter.notifyDataSetChanged(); //当列表下部稍微多出一些,自动上移 +// if (callMessagesRecyclerView.computeVerticalScrollRange() +// - callMessagesRecyclerView.computeVerticalScrollExtent() +// - callMessagesRecyclerView.computeVerticalScrollOffset() < 500) { +// callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); +// } +// if (mainViewModel.currentMessages != null) { +// +// ToastMessage.show(String.format(GeneralVariables.getStringFromResource( +// R.string.tracker_decoded_new) +// , mainViewModel.currentDecodeCount) +// + " " + String.format( +// getString(R.string.decoding_takes_milliseconds) +// , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); +// //画电台之间的连线 +// //对CQ的电台打点 +// gridOsmMapView.clearLines(); +// gridOsmMapView.clearMarkers(); +// for (Ft8Message msg : mainViewModel.currentMessages) { +// drawMessage(msg);//在地图上画每一个消息 +// } +// gridOsmMapView.showInfoWindows(); +// } + } + }); + mainViewModel.mutableIsDecoding.observe(this, new Observer() { + @Override + public void onChanged(Boolean aBoolean) { + if (aBoolean) { + gridOsmMapView.clearLines(); + gridOsmMapView.clearMarkers(); + } + } + }); + mainViewModel.mutableFt8MessageList.observe(this, new Observer>() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onChanged(ArrayList messages) { + if (mainViewModel.currentMessages == null) return; + ArrayList tempMsg = new ArrayList<>(mainViewModel.currentMessages); + callingListAdapter.notifyDataSetChanged(); if (callMessagesRecyclerView.computeVerticalScrollRange() - callMessagesRecyclerView.computeVerticalScrollExtent() - callMessagesRecyclerView.computeVerticalScrollOffset() < 500) { callMessagesRecyclerView.scrollToPosition(callingListAdapter.getItemCount() - 1); } - if (mainViewModel.currentMessages != null) { - ToastMessage.show(String.format(GeneralVariables.getStringFromResource( - R.string.tracker_decoded_new) - , mainViewModel.currentDecodeCount) - + " " + String.format( - getString(R.string.decoding_takes_milliseconds) - , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); - //画电台之间的连线 - //对CQ的电台打点 - gridOsmMapView.clearLines(); - gridOsmMapView.clearMarkers(); - for (Ft8Message msg : mainViewModel.currentMessages) { - drawMessage(msg);//在地图上画每一个消息 - } - gridOsmMapView.showInfoWindows(); + binding.gridMessageTextView.setText(String.format("%s %s" + , String.format(GeneralVariables.getStringFromResource( + R.string.tracker_decoded_new) + , mainViewModel.currentDecodeCount), String.format( + getString(R.string.decoding_takes_milliseconds) + , mainViewModel.ft8SignalListener.decodeTimeSec.getValue()))); + + //画电台之间的连线 + //对CQ的电台打点 + for (Ft8Message msg : tempMsg) { + drawMessage(msg);//在地图上画每一个消息 } + gridOsmMapView.showInfoWindows(); + //} } }); diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java old mode 100755 new mode 100644 index a876798..9d4a309 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/HtmlContext.java @@ -5,6 +5,7 @@ package com.bg7yoz.ft8cn.html; * @date 2023-03-20 */ +import android.annotation.SuppressLint; import android.database.Cursor; import com.bg7yoz.ft8cn.BuildConfig; @@ -29,9 +30,10 @@ public class HtmlContext { " UL {\tCOLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + " P {\tCOLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + " BODY {\tCOLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + - " TD {\tCOLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + + " FIELDSET {max-width: fit-content}\n" + + " TD {\tCOLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif; FONT-SIZE: 8pt}\n" + " TR {\tBACKGROUND-COLOR: WHITE;COLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + - " TR.bbb {\tBACKGROUND-COLOR: #F6FBFF; COLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + + " TR.bbb {\tBACKGROUND-COLOR: #DDF0FE; COLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif }\n" + " TH {\tBACKGROUND-COLOR: C8C2FF;COLOR: #333333; FONT-FAMILY: tahoma,helvetica,sans-serif;FONT-SIZE: 8pt; }\n" + " FONT.title {\tBACKGROUND-COLOR: white; COLOR: #363636; FONT-FAMILY:tahoma,helvetica,verdana,lucida console,utopia; FONT-SIZE: 10pt; FONT-WEIGHT: bold }\n" + " FONT.sub {\tBACKGROUND-COLOR: white; COLOR: #000000; FONT-FAMILY:tahoma,helvetica,verdana,lucida console,utopia; FONT-SIZE: 10pt }\n" + @@ -48,11 +50,11 @@ public class HtmlContext { "\n" + "\n" + "\n"; - private static final String HTML_TITLE = "",c)); + } + sb.append("\n"); + return sb; + } + + /** + * 生成表格标题 + * @param sb html + * @param s 标题 + * @return html + */ + public static StringBuilder tableCellHeader(StringBuilder sb, String... s){ + for (String c: s) { + sb.append(String.format("",c)); + } + sb.append("\n"); + return sb; + } + @SuppressLint("DefaultLocale") + public static StringBuilder tableKeyRow(StringBuilder sb , Boolean darkMode, String key, int value){ + sb.append(String.format("\n" + ,darkMode ? "class=\"bbb\"" : "" + ,key,value)); + return sb; + } + + public static StringBuilder tableKeyRow(StringBuilder sb ,Boolean darkMode,String key,String value){ + sb.append(String.format("\n" + ,darkMode ? "class=\"bbb\"" : "" + ,key,value)); + return sb; + } + + public static StringBuilder tableRowBegin(StringBuilder sb){ + return tableRowBegin(sb,false,false); + } + public static StringBuilder tableRowBegin(StringBuilder sb,Boolean alignCenter,boolean darkMode){ + sb.append(String.format("" + ,alignCenter?"align=center":"" + ,darkMode ? "class=\"bbb\"":"")); + return sb; + } + public static StringBuilder tableRowEnd(StringBuilder sb){ + sb.append(""); + return sb; + } + + public static StringBuilder tableBegin(StringBuilder sb){ + return tableBegin(sb,false,1,true); + } + public static StringBuilder tableBegin(StringBuilder sb,boolean hasBorder,boolean fullWidth){ + return tableBegin(sb,hasBorder,1,fullWidth); + } + @SuppressLint("DefaultLocale") + public static StringBuilder tableBegin(StringBuilder sb, boolean hasBorder, int cellpadding,boolean fullWidth){ + sb.append(String.format("
" + + private static final String HTML_TITLE = "
" + "Welcome to FT8CN "+ BuildConfig.VERSION_NAME+"
" +GeneralVariables.getStringFromResource(R.string.html_return) +"
\n"; - private static final String HTML_FOOTER = "" + + private static final String HTML_FOOTER = "
" + "
BG7YOZ
" + ""+GeneralVariables.getStringFromResource(R.string.html_return)+"
\n"; @@ -66,7 +68,7 @@ public class HtmlContext { public static String DEFAULT_HTML() { - return HTML_STRING("" + + return HTML_STRING("
" + "" + "" + + "" + + "" + @@ -109,36 +114,104 @@ public class HtmlContext { "
" + GeneralVariables.getStringFromResource(R.string.html_track_operation_information) +"
" @@ -99,6 +101,9 @@ public class HtmlContext { "
" +GeneralVariables.getStringFromResource(R.string.html_callsign_qth)+"
"//查询日志 + +GeneralVariables.getStringFromResource(R.string.html_query_logs)+"
" +GeneralVariables.getStringFromResource(R.string.html_export_log) +""+GeneralVariables.getStringFromResource(R.string.html_to_the_third_party)+"
"); } - public static String ListTableContext(Cursor cursor) { + /** + * 生成表格的内容 + * @param sb html + * @param s 内容 + * @return html + */ + public static StringBuilder tableCell(StringBuilder sb, String... s){ + for (String c: s) { + sb.append(String.format("
%s%s
%s%d
%s%s
" + ,hasBorder ? "border=\"1\"" :"" + ,cellpadding + ,fullWidth ? "width=\"100%\"":"")); + return sb; + } + + public static StringBuilder tableEnd(StringBuilder sb){ + sb.append("
"); + return sb; + } + public static String ListTableContext(Cursor cursor,boolean fullWidth){ + return ListTableContext(cursor,false,0,fullWidth); + } + public static String ListTableContext(Cursor cursor,boolean hasBorder,int cellpadding ,boolean fullWidth) { StringBuilder result = new StringBuilder(); - result.append(""); + HtmlContext.tableBegin(result,hasBorder,cellpadding,fullWidth).append("\n"); //写字段名 - result.append(""); + HtmlContext.tableRowBegin(result); for (int i = 0; i < cursor.getColumnCount(); i++) { - result.append(""); + HtmlContext.tableCellHeader(result,cursor.getColumnName(i)); } - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); int order=0; while (cursor.moveToNext()) { - if (order%2==0) { - result.append(""); - }else { - result.append(""); - } + HtmlContext.tableRowBegin(result,false,order % 2 !=0); for (int i = 0; i < cursor.getColumnCount(); i++) { - result.append(""); + HtmlContext.tableCell(result,(cursor.getString(i)!=null) ? cursor.getString(i) :""); } - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); order++; } - result.append("
"); - result.append(cursor.getColumnName(i)); - result.append("
"); - if (cursor.getString(i)!=null) { - result.append(cursor.getString(i)); - } - result.append("
"); + HtmlContext.tableEnd(result).append("\n"); cursor.close(); return result.toString(); } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java old mode 100755 new mode 100644 index ea80ce8..49f32a4 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/html/LogHttpServer.java @@ -1,6 +1,7 @@ package com.bg7yoz.ft8cn.html; /** * Http服务的具体内容。数据库访问不需要异步方式。 + * * @author BGY70Z * @date 2023-03-20 */ @@ -35,6 +36,7 @@ import java.util.Objects; import fi.iki.elonen.NanoHTTPD; +@SuppressWarnings("ConstantConditions") public class LogHttpServer extends NanoHTTPD { private final MainViewModel mainViewModel; public static int DEFAULT_PORT = 7050; @@ -70,12 +72,14 @@ public class LogHttpServer extends NanoHTTPD { msg = HTML_STRING(getNewMessages()); } else if (uri.equalsIgnoreCase("MESSAGE")) {//查保存的SWL通联消息表 return getMessages(session); - }else if (uri.equalsIgnoreCase("QSOSWLMSG")) {//查SWL QSO通联消息表 + } else if (uri.equalsIgnoreCase("QSOSWLMSG")) {//查SWL QSO通联消息表 return getSWLQsoMessages(session); + } else if (uri.equalsIgnoreCase("QSOLogs")) {//查QSO日志 + return getQsoLogs(session); } else if (uri.equalsIgnoreCase("CALLSIGNGRID")) {//查呼号与网格的对应关系 msg = HTML_STRING(showCallGridList()); } else if (uri.equalsIgnoreCase("GETCALLSIGNQTH")) { - msg = HTML_STRING(getCallsignQTH()); + msg = HTML_STRING(getCallsignQTH(session)); } else if (uri.equalsIgnoreCase("ALLTABLE")) {//查所有的表 msg = HTML_STRING(getAllTableName()); } else if (uri.equalsIgnoreCase("FOLLOWCALLSIGNS")) {//查关注的呼号 @@ -151,9 +155,8 @@ public class LogHttpServer extends NanoHTTPD { //判断是不是POST日志文件 if (session.getMethod().equals(Method.POST) || session.getMethod().equals(Method.PUT)) { - //Log.e(TAG, "serve uri: --------->" + session.getUri()); - Map files = new HashMap(); - Map header = session.getHeaders(); + Map files = new HashMap<>(); + //Map header = session.getHeaders(); try { session.parseBody(files); String param = files.get("file1");//这个是post或put文件的key @@ -183,9 +186,8 @@ public class LogHttpServer extends NanoHTTPD { temp.append(""); } + mainViewModel.databaseOpr.getQslDxccToMap();//更新一下已经通联的分区 return temp.toString(); -// return String.format(GeneralVariables.getStringFromResource(R.string.html_import_count) + "
" -// , recordList.size(), importCount,logFileImport.getErrorCount()); } catch (IOException | ResponseException e) { e.printStackTrace(); return String.format(GeneralVariables.getStringFromResource(R.string.html_import_failed) @@ -202,8 +204,8 @@ public class LogHttpServer extends NanoHTTPD { */ private String getConfig() { Cursor cursor = mainViewModel.databaseOpr.getDb() - .rawQuery("select * from config", null); - return HtmlContext.ListTableContext(cursor); + .rawQuery("select KeyName,Value from config", null); + return HtmlContext.ListTableContext(cursor, true, 4, false); } /** @@ -233,7 +235,7 @@ public class LogHttpServer extends NanoHTTPD { "where q.[call] like ?\n" + "group by q.[call] ,q.gridsquare,q.freq ,q.qso_date,q.time_on,q.band\n" + "HAVING q.qso_date||q.time_on =MAX(q2.qso_date||q2.time_on) \n", new String[]{where}); - return html + HtmlContext.ListTableContext(cursor); + return html + HtmlContext.ListTableContext(cursor, true, 3, false); } @@ -245,51 +247,68 @@ public class LogHttpServer extends NanoHTTPD { private String getAllTableName() { Cursor cursor = mainViewModel.databaseOpr.getDb() .rawQuery("select * from sqlite_master where type='table'", null); - return HtmlContext.ListTableContext(cursor); + return HtmlContext.ListTableContext(cursor, true, 4, true); } @SuppressLint({"Range", "DefaultLocale"}) - private String getCallsignQTH() { - Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select callsign ,grid,updateTime from CallsignQTH", null); + private String getCallsignQTH(IHTTPSession session) { + String callsign = ""; + String grid = ""; + //读取查询的参数 + Map pars = session.getParms(); + + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("grid") != null) { + grid = Objects.requireNonNull(pars.get("grid")); + } + String whereCallsign = String.format("%%%s%%", callsign.toUpperCase()); + String whereGrid = String.format("%%%s%%", grid.toUpperCase()); + Cursor cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select callsign ,grid,updateTime from CallsignQTH where (callsign like ?) and (grid like ?)" + , new String[]{whereCallsign, whereGrid}); StringBuilder result = new StringBuilder(); - result.append(""); + HtmlContext.tableBegin(result, true, 2, false).append("\n"); + result.append(String.format("%s
\n" + + "%s\n" + + "

\n" + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , grid + , GeneralVariables.getStringFromResource(R.string.html_message_query))); //写字段名 - result.append(""); + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_distance) + , GeneralVariables.getStringFromResource(R.string.html_update_time) + ).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_callsign))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_grid))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_distance))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_update_time))); - result.append("\n"); @SuppressLint("SimpleDateFormat") SimpleDateFormat formatTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); int order = 0; while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - - result.append("\n" - , cursor.getString(cursor.getColumnIndex("callsign")) - , cursor.getString(cursor.getColumnIndex("grid")))); - result.append(String.format("", - MaidenheadGrid.getDistStr(GeneralVariables.getMyMaidenhead4Grid() - , cursor.getString(cursor.getColumnIndex("grid"))))); - + HtmlContext.tableRowBegin(result, true, order % 2 != 0); Date date = new Date(cursor.getLong(cursor.getColumnIndex("updateTime"))); - result.append(String.format("", - formatTime.format(date))); - - result.append("\n"); + HtmlContext.tableCell(result + , cursor.getString(cursor.getColumnIndex("callsign")) + , cursor.getString(cursor.getColumnIndex("grid")) + , MaidenheadGrid.getDistStr(GeneralVariables.getMyMaidenhead4Grid() + , cursor.getString(cursor.getColumnIndex("grid"))) + , formatTime.format(date) + ).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); order++; } - result.append("
%s%s%s%s
"); - result.append(String.format("%s%s%s%s

"); + HtmlContext.tableEnd(result).append("
\n"); result.append(String.format("%d", order)); + cursor.close(); return result.toString(); } @@ -302,33 +321,32 @@ public class LogHttpServer extends NanoHTTPD { private String getFollowCallsigns() { Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from followCallsigns", null); StringBuilder result = new StringBuilder(); - result.append(""); + HtmlContext.tableBegin(result, true, 3, false).append("\n"); //写字段名 - result.append(""); + HtmlContext.tableRowBegin(result).append("\n"); for (int i = 0; i < cursor.getColumnCount(); i++) { - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_callsign))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_operation))); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); } - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); int order = 0; while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); for (int i = 0; i < cursor.getColumnCount(); i++) { - result.append("\n" - , cursor.getString(i), cursor.getString(i).replace("/", "_") - , GeneralVariables.getStringFromResource(R.string.html_delete))); - + HtmlContext.tableCell(result + , cursor.getString(i) + , String.format("%s" + , cursor.getString(i).replace("/", "_") + , GeneralVariables.getStringFromResource(R.string.html_delete)) + ).append("\n"); } - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); order++; } - result.append("
%s%s
"); - result.append(String.format("%s%s
"); + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); return result.toString(); } @@ -357,84 +375,49 @@ public class LogHttpServer extends NanoHTTPD { Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( "select * from QslCallsigns order by ID desc", null); StringBuilder result = new StringBuilder(); - result.append("\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); //写字段名 - result.append(""); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_start_time))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_end_time))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_callsign))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_mode))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_grid))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_band))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_freq))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_manual_confirmation))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_lotw_confirmation))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_data_source))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_operation))); - result.append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time) + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time) + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_qsl_band) + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.html_qsl_manual_confirmation) + , GeneralVariables.getStringFromResource(R.string.html_qsl_lotw_confirmation) + , GeneralVariables.getStringFromResource(R.string.html_qsl_data_source) + , GeneralVariables.getStringFromResource(R.string.html_operation)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); int order = 0; while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - result.append(""); - - result.append(""); - - result.append("\n"); - - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - - - result.append(""); - - result.append(String.format("\n" - , cursor.getString(cursor.getColumnIndex("ID")) - , GeneralVariables.getStringFromResource(R.string.html_delete))); - result.append("\n"); + HtmlContext.tableRowBegin(result, true, order % 2 != 0); + HtmlContext.tableCell(result + , cursor.getString(cursor.getColumnIndex("startTime")) + , cursor.getString(cursor.getColumnIndex("finishTime")) + , cursor.getString(cursor.getColumnIndex("callsign")) + , cursor.getString(cursor.getColumnIndex("mode")) + , cursor.getString(cursor.getColumnIndex("grid")) + , cursor.getString(cursor.getColumnIndex("band")) + , cursor.getString(cursor.getColumnIndex("band_i")) + "Hz" + , (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) + ? "" : "×" + , (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) + ? "" : "×" + , (cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1) + ? GeneralVariables.getStringFromResource(R.string.html_qsl_import_data_from_external) + : GeneralVariables.getStringFromResource(R.string.html_qsl_native_data) + , String.format("%s" + , cursor.getString(cursor.getColumnIndex("ID")) + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); order++; } - result.append("
%s%s%s%s%s%s%s%s%s%s%s
"); - result.append(cursor.getString(cursor.getColumnIndex("startTime"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("finishTime"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("callsign"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("mode"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("grid"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("band"))); - result.append(""); - result.append(cursor.getString(cursor.getColumnIndex("band_i"))); - result.append("Hz"); - if (cursor.getInt(cursor.getColumnIndex("isQSL")) == 1) { - result.append(""); - } else { - result.append("×"); - } - result.append(""); - if (cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1) { - result.append(""); - } else { - result.append("×"); - } - result.append(""); - if (cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1) { - result.append(GeneralVariables.getStringFromResource(R.string.html_qsl_import_data_from_external)); - } else { - result.append(GeneralVariables.getStringFromResource(R.string.html_qsl_native_data)); - } - result.append("%s
"); + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); return result.toString(); } @@ -458,14 +441,14 @@ public class LogHttpServer extends NanoHTTPD { ""); result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_callsign_grid_total) , GeneralVariables.callsignAndGrids.size())); - //result.append("\n"); - result.append("
\n"); - result.append(""); + HtmlContext.tableBegin(result, true, 1, false); + HtmlContext.tableRowBegin(result); result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_callsign))); result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_qsl_grid))); - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + result.append(GeneralVariables.getCallsignAndGridToHTML()); - result.append("
%s%s

\n"); + HtmlContext.tableEnd(result).append("
\n"); return result.toString(); } @@ -482,266 +465,160 @@ public class LogHttpServer extends NanoHTTPD { "function myrefresh(){\n" + "window.location.reload();\n" + "}\n" + - "setTimeout('myrefresh()',5000); //指定5秒刷新一次,5000处可自定义设置,1000为1秒\n" + + "setTimeout('myrefresh()',5000);\n " +////指定5秒刷新一次,5000处可自定义设置,1000为1秒\n" + ""); - result.append("\n"); - //result.append("
\n"); - result.append(""); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_variable))); - result.append(String.format("", GeneralVariables.getStringFromResource(R.string.html_value))); - result.append("\n"); - result.append(""); - result.append("\n"); - result.append("\n", UtcTimer.getTimeStr(mainViewModel.timerSec.getValue()))); - result.append("\n"); + HtmlContext.tableBegin(result, true, 5, false).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_variable) + , GeneralVariables.getStringFromResource(R.string.html_value)).append("\n"); + + HtmlContext.tableKeyRow(result, false, "UTC" + , UtcTimer.getTimeStr(mainViewModel.timerSec.getValue())); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_my_callsign) + , GeneralVariables.myCallsign); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_my_grid) + , GeneralVariables.getMyMaidenheadGrid()); + + HtmlContext.tableKeyRow(result, true//消息最大缓存条数 + , GeneralVariables.getStringFromResource(R.string.html_max_message_cache) + , String.format("%d", GeneralVariables.MESSAGE_COUNT)); + + HtmlContext.tableKeyRow(result, false//音量大小 + , GeneralVariables.getStringFromResource(R.string.signal_strength) + , String.format("%.0f%%\n", GeneralVariables.volumePercent * 100f)); - result.append(""); - result.append("\n"); - result.append("\n", GeneralVariables.myCallsign)); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_audio_bits) + , GeneralVariables.audioOutput32Bit ? + GeneralVariables.getStringFromResource(R.string.audio32_bit) + : GeneralVariables.getStringFromResource(R.string.audio16_bit)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_audio_rate) + , String.format("%dHz", GeneralVariables.audioSampleRate)); + + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_decodes_in_this_cycle) + , String.format("%d", mainViewModel.currentDecodeCount)); + + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.decode_mode_text) + , GeneralVariables.deepDecodeMode + ? GeneralVariables.getStringFromResource(R.string.deep_mode) + : GeneralVariables.getStringFromResource(R.string.fast_mode)); - result.append(""); - result.append("\n"); - result.append("\n", GeneralVariables.getMyMaidenheadGrid())); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_total_number_of_decodes) + , String.format("%d", mainViewModel.ft8Messages.size())); - - result.append(""); - result.append("\n"); - result.append("\n", GeneralVariables.MESSAGE_COUNT));//消息最大缓存条数 - result.append("\n"); - - result.append(""); - result.append("\n"); - result.append("\n", GeneralVariables.volumePercent * 100f));//音量大小 - result.append("\n"); - - - result.append(""); - result.append("\n"); - result.append("\n", mainViewModel.currentDecodeCount)); - result.append("\n"); - - result.append(""); - result.append("\n"); - result.append("\n", mainViewModel.ft8Messages.size())); - result.append("\n"); - - result.append(""); - result.append("\n"); - result.append("\n", - Boolean.TRUE.equals(mainViewModel.mutableIsRecording.getValue()) + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_in_recording_state) + , Boolean.TRUE.equals(mainViewModel.mutableIsRecording.getValue()) ? GeneralVariables.getStringFromResource(R.string.html_recording) - : GeneralVariables.getStringFromResource(R.string.html_no_recording))); - result.append("\n"); + : GeneralVariables.getStringFromResource(R.string.html_no_recording)); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_time_consuming_for_this_decoding) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_average_delay_time_of_this_cycle) + , String.format(GeneralVariables.getStringFromResource(R.string.html_seconds) + , mainViewModel.mutableTimerOffset.getValue())); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_sound_frequency) + , String.format("%.0fHz", GeneralVariables.getBaseFrequency())); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_transmission_delay_time) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.transmitDelay)); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_launch_supervision) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.launchSupervision)); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_automatic_program_run_time) + , String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) + , GeneralVariables.launchSupervisionCount())); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_no_reply_limit) + , GeneralVariables.noReplyLimit); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_no_reply_count) + , GeneralVariables.noReplyCount); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.follow_cq) + , String.valueOf(GeneralVariables.autoFollowCQ)); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.auto_call_follow) + , String.valueOf(GeneralVariables.autoCallFollow)); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_target_callsign) + , (mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue() != null) + ? mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue().callsign : ""); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_sequential) + , mainViewModel.ft8TransmitSignal.sequential); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.synFrequency) + , String.valueOf(GeneralVariables.synFrequency)); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.tran_delay) + , GeneralVariables.transmitDelay); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.ptt_delay) + , GeneralVariables.pttDelay); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.rig_name) + , RigNameList.getInstance( + GeneralVariables.getMainContext()).getRigNameByIndex(GeneralVariables.modelNo).modelName); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_mark_message) + , String.format("%s", mainViewModel.markMessage + ? GeneralVariables.getStringFromResource(R.string.html_marking_message) + : GeneralVariables.getStringFromResource(R.string.html_do_not_mark_message))); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_operation_mode) + , ControlMode.getControlModeStr(GeneralVariables.controlMode)); - result.append(""); - result.append("\n"); - result.append("\n\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_civ_address) + , String.format("0x%2X", GeneralVariables.civAddress)); - result.append(""); - result.append("\n"); - result.append("\n\n"); - - - result.append(""); - result.append("\n"); - result.append("\n\n"); - - result.append(""); - result.append("\n"); - result.append(String.format("" - , ControlMode.getControlModeStr(GeneralVariables.controlMode))); - result.append("\n"); - - result.append(""); - result.append("\n"); - result.append(String.format("", GeneralVariables.civAddress)); - result.append("\n"); - - result.append(">"); - result.append("\n"); - result.append(String.format("", GeneralVariables.baudRate)); - result.append("\n"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_baud_rate) + , GeneralVariables.baudRate); result.append(""); result.append("\n"); - result.append(""); - result.append("\n"); - - result.append(">"); - result.append("\n"); - - result.append(""); - result.append("\n"); - - result.append(""); - result.append("\n"); - if (GeneralVariables.controlMode == ControlMode.VOX) { - result.append(""); - } else { - result.append(String.format("" - , ConnectMode.getModeStr(GeneralVariables.connectMode))); - } - result.append("\n"); - - - result.append(""); - result.append("\n"); - result.append(String.format("", GeneralVariables.baudRate)); - result.append("\n"); - - - result.append(">"); - result.append("\n"); - result.append(""); - result.append("\n"); - - - result.append(""); - result.append("\n"); - result.append(""); result.append("\n"); - result.append(">"); - result.append("\n"); - result.append(""); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_instruction_set) + , (mainViewModel.baseRig != null) ? mainViewModel.baseRig.getName() : "-"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_connect_mode) + , (GeneralVariables.controlMode == ControlMode.VOX) ? "-" + : ConnectMode.getModeStr(GeneralVariables.connectMode)); - result.append(""); - result.append("\n"); - result.append(""); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_baud_rate) + , GeneralVariables.baudRate); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band) + , GeneralVariables.getBandString()); - result.append(">"); - result.append("\n"); - result.append(""); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_radio_frequency) + , (mainViewModel.baseRig != null) + ? BaseRigOperation.getFrequencyStr(mainViewModel.baseRig.getFreq()) : "-"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.html_flex_max_rf_power) + , String.format("%d W", GeneralVariables.flexMaxRfPower)); - result.append(""); - result.append("\n"); - result.append(""); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.html_atu_tune_power) + , String.format("%d W", GeneralVariables.flexMaxTunePower)); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.be_excluded_callsigns) + , GeneralVariables.getExcludeCallsigns()); - result.append(">"); - result.append("\n"); - result.append(""); - result.append("\n"); + HtmlContext.tableKeyRow(result, true + , GeneralVariables.getStringFromResource(R.string.config_save_swl) + , String.valueOf(GeneralVariables.saveSWLMessage)); - result.append("
%s%s
"); - result.append("UTC"); - result.append(""); - result.append(String.format("%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_my_callsign)); - result.append(""); - result.append(String.format("%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_my_grid)); - result.append(""); - result.append(String.format("%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_max_message_cache)); - result.append(""); - result.append(String.format("%d
"); - result.append(GeneralVariables.getStringFromResource(R.string.signal_strength)); - result.append(""); - result.append(String.format("%.0f%%
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_decodes_in_this_cycle)); - result.append(""); - result.append(String.format("%d
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_total_number_of_decodes)); - result.append(""); - result.append(String.format("%d
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_in_recording_state)); - result.append(""); - result.append(String.format("%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_time_consuming_for_this_decoding)); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , mainViewModel.ft8SignalListener.decodeTimeSec.getValue())); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_average_delay_time_of_this_cycle)); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_seconds) - , mainViewModel.mutableTimerOffset.getValue())); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_sound_frequency)); - result.append(""); - result.append(String.format("%.0fHz", GeneralVariables.getBaseFrequency())); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_transmission_delay_time)); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.transmitDelay)); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_launch_supervision)); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.launchSupervision)); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_automatic_program_run_time)); - result.append(""); - result.append(String.format(GeneralVariables.getStringFromResource(R.string.html_milliseconds) - , GeneralVariables.launchSupervisionCount())); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_no_reply_limit)); - result.append(""); - result.append(GeneralVariables.noReplyLimit); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_no_reply_count)); - result.append(""); - result.append(GeneralVariables.noReplyCount); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.follow_cq)); - result.append(""); - result.append(GeneralVariables.autoFollowCQ); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.auto_call_follow)); - result.append(""); - result.append(GeneralVariables.autoCallFollow); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_target_callsign)); - result.append(""); - if (mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue() != null) { - result.append(mainViewModel.ft8TransmitSignal.mutableToCallsign.getValue().callsign); - } else { - result.append("-"); - } - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_sequential)); - result.append(""); - result.append(mainViewModel.ft8TransmitSignal.sequential); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.synFrequency)); - result.append(""); - result.append(GeneralVariables.synFrequency); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.tran_delay)); - result.append(""); - result.append(GeneralVariables.transmitDelay); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.ptt_delay)); - result.append(""); - result.append(GeneralVariables.pttDelay); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.rig_name)); - result.append(""); - result.append(RigNameList.getInstance( - GeneralVariables.getMainContext()).getRigNameByIndex(GeneralVariables.modelNo).modelName); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_mark_message)); - result.append(""); - result.append(String.format("%s", mainViewModel.markMessage - ? GeneralVariables.getStringFromResource(R.string.html_marking_message) - : GeneralVariables.getStringFromResource(R.string.html_do_not_mark_message))); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_operation_mode)); - result.append("%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_civ_address)); - result.append("0x%2X
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_baud_rate)); - result.append("%d
"); result.append(GeneralVariables.getStringFromResource(R.string.html_available_serial_ports)); result.append(""); if (mainViewModel.mutableSerialPorts != null) { if (mainViewModel.mutableSerialPorts.getValue().size() == 0) { @@ -749,123 +626,57 @@ public class LogHttpServer extends NanoHTTPD { } } for (CableSerialPort.SerialPort serialPort : Objects.requireNonNull(mainViewModel.mutableSerialPorts.getValue())) { - result.append(serialPort.information() + "
\n"); - } - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_instruction_set)); - result.append(""); - if (mainViewModel.baseRig != null) { - result.append(mainViewModel.baseRig.getName()); - } else { - result.append("-"); - } - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_connect_mode)); - result.append("-%s
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_baud_rate)); - result.append("%d
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)); - result.append(""); - result.append(GeneralVariables.getBandString()); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_radio_frequency)); - result.append(""); - if (mainViewModel.baseRig != null) { - result.append(BaseRigOperation.getFrequencyStr(mainViewModel.baseRig.getFreq())); - } else { - result.append("-"); + result.append(serialPort.information()).append("
\n"); } result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_flex_max_rf_power)); - result.append(""); - result.append(String.format("%d W", GeneralVariables.flexMaxRfPower)); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.html_atu_tune_power)); - result.append(""); - result.append(String.format("%d W", GeneralVariables.flexMaxTunePower)); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.be_excluded_callsigns)); - result.append(""); - result.append(GeneralVariables.getExcludeCallsigns()); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.config_save_swl)); - result.append(""); - result.append(GeneralVariables.saveSWLMessage); - result.append("
"); - result.append(GeneralVariables.getStringFromResource(R.string.config_save_swl_qso)); - result.append(""); - result.append(GeneralVariables.saveSWL_QSO); - result.append("

"); + HtmlContext.tableKeyRow(result, false + , GeneralVariables.getStringFromResource(R.string.config_save_swl_qso) + , String.valueOf(GeneralVariables.saveSWL_QSO)); + HtmlContext.tableEnd(result).append("
\n"); - result.append("\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); result.append(String.format("\n" , String.format(GeneralVariables.getStringFromResource(R.string.html_successful_callsign) , GeneralVariables.getBandString()))); @@ -875,13 +686,13 @@ public class LogHttpServer extends NanoHTTPD { result.append(GeneralVariables.QSL_Callsign_list.get(i)); result.append(", "); if (((i + 1) % 10) == 0) { - result.append("
%s
"); + result.append("
\n"); } } - result.append("

\n"); + result.append("\n"); + HtmlContext.tableEnd(result).append("
\n"); - - result.append("\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); result.append(String.format("\n" , GeneralVariables.getStringFromResource(R.string.html_tracking_callsign))); @@ -890,19 +701,20 @@ public class LogHttpServer extends NanoHTTPD { result.append(GeneralVariables.followCallsign.get(i)); result.append(", "); if (((i + 1) % 10) == 0) { - result.append("
%s
"); + result.append("
\n"); } } - result.append("
\n"); + result.append("\n"); + HtmlContext.tableEnd(result).append("\n"); - result.append("\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); result.append(String.format("\n" , GeneralVariables.getStringFromResource(R.string.html_tracking_qso_information))); result.append("
%s
"); result.append(GeneralVariables.qslRecordList.toHTML()); - result.append("
\n"); - // result.append("\n"); + result.append("\n"); + HtmlContext.tableEnd(result).append("\n"); return result.toString(); } @@ -920,40 +732,26 @@ public class LogHttpServer extends NanoHTTPD { "}\n" + "setTimeout('myrefresh()',5000); //指定5秒刷新一次,5000处可自定义设置,1000为1秒\n" + ""); - //result.append("\n"); - result.append("
\n"); - result.append(""); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_callsign))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_hash_value))); - result.append("\n"); + HtmlContext.tableBegin(result, true, 3, false).append("\n"); + HtmlContext.tableRowBegin(result); + //表头 + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_hash_value)); + + HtmlContext.tableRowEnd(result).append("\n"); + int order = 0; for (Map.Entry entry : Ft8Message.hashList.entrySet()) { - if ((order / 3) % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - result.append(String.format("", entry.getValue())); - result.append(String.format("", entry.getKey())); - result.append("\n"); + HtmlContext.tableRowBegin(result, false, (order / 3) % 2 != 0); + + HtmlContext.tableCell(result, entry.getValue()); + HtmlContext.tableCell(result, String.format(" 0x%x ", entry.getKey())); + HtmlContext.tableRowEnd(result).append("\n"); + order++; } -// for (HashStruct hash : Ft8Message.hashList) { -// if ((order / 3) % 2 == 0) { -// result.append(""); -// } else { -// result.append(""); -// } -// result.append(String.format("", hash.callsign)); -// result.append(String.format("", hash.hashCode)); -// result.append("\n"); -// order++; -// } - - result.append("
%s%s
%s 0x%x
%s0x%x
"); + HtmlContext.tableEnd(result).append("\n"); return result.toString(); } @@ -977,11 +775,11 @@ public class LogHttpServer extends NanoHTTPD { .replace(".", "_") .replace(">", "_")); } - if (start_date.length()>0){ - fileName.append(String.format("_%s",start_date)); + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); } - if (end_date.length()>0){ - fileName.append(String.format("_%s",end_date)); + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); } fileName.append(".").append(exportFile); @@ -998,18 +796,18 @@ public class LogHttpServer extends NanoHTTPD { } String whereStr = String.format("%%%s%%", callsign); cursor = mainViewModel.databaseOpr.getDb().rawQuery( - "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + - dateSql.toString() + - " order by ID " - , new String[]{whereStr, whereStr}); + "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + + dateSql + + " order by ID " + , new String[]{whereStr, whereStr}); - StringBuilder result=new StringBuilder(); + StringBuilder result = new StringBuilder(); String formatStr; - if (exportFile.equalsIgnoreCase("CSV")){ - formatStr="%s,%.3f,Rx,%s,%d,%.1f,%d,%s\n"; - }else { - formatStr="%s %12.3f Rx %s %6d %4.1f %4d %s\n"; + if (exportFile.equalsIgnoreCase("CSV")) { + formatStr = "%s,%.3f,Rx,%s,%d,%.1f,%d,%s\n"; + } else { + formatStr = "%s %12.3f Rx %s %6d %4.1f %4d %s\n"; } while (cursor.moveToNext()) { @@ -1024,15 +822,14 @@ public class LogHttpServer extends NanoHTTPD { long band = cursor.getLong(cursor.getColumnIndex("BAND")); result.append(String.format(formatStr - ,utcTime,(band/1000f/1000f),protocol,dB,dt,freq,String.format("%s %s %s",callTo,callFrom,extra))); + , utcTime, (band / 1000f / 1000f), protocol, dB, dt, freq, String.format("%s %s %s", callTo, callFrom, extra))); } cursor.close(); - response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain", result.toString()); response.addHeader("Content-Disposition" - , String.format("attachment;filename=%s",fileName.toString())); + , String.format("attachment;filename=%s", fileName)); return response; } @@ -1083,10 +880,14 @@ public class LogHttpServer extends NanoHTTPD { return exportSWLMessage(exportFile, callsign, startDate, endDate); } + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append(""); + + result.append(String.format("%s" + " , %s
" - , callsign, startDate, endDate,GeneralVariables.getStringFromResource(R.string.html_export_csv) - , callsign, startDate, endDate,GeneralVariables.getStringFromResource(R.string.html_export_text))); + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_csv) + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_text))); Cursor cursor; StringBuilder dateSql = new StringBuilder(); @@ -1101,7 +902,7 @@ public class LogHttpServer extends NanoHTTPD { //计算总的记录数 cursor = mainViewModel.databaseOpr.getDb().rawQuery( "select count(*) as rc from SWLMessages " + - "where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?))" + dateSql.toString() + "where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?))" + dateSql , new String[]{whereStr, whereStr}); cursor.moveToFirst(); int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); @@ -1112,19 +913,20 @@ public class LogHttpServer extends NanoHTTPD { result.append(String.format("

%s , %s" + "" +//页码及页大小 "
\n%s " +//呼号 - "
\n%s " +//起始时间 - "
\n%s " +//结束时间 - "  
\n" + "  
\n" + + "
\n%s " +//起始时间 + " \n%s 
" //结束时间 + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) , GeneralVariables.getStringFromResource(R.string.html_message_page_size) , pageSize , GeneralVariables.getStringFromResource(R.string.html_callsign) , callsign + , GeneralVariables.getStringFromResource(R.string.html_message_query) , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) , startDate , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) - , endDate - , GeneralVariables.getStringFromResource(R.string.html_message_query))); + , endDate)); //定位页,第一页、上一页、下一页,最后一页 @@ -1138,40 +940,34 @@ public class LogHttpServer extends NanoHTTPD { , pageIndex , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate , pageCount, pageSize, callsign, startDate, endDate)); - + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result).append("\n"); cursor = mainViewModel.databaseOpr.getDb().rawQuery( String.format( "select * from SWLMessages where ((CALL_TO LIKE ?)OR(CALL_FROM LIKE ?)) " + - dateSql.toString() + + dateSql + " order by ID LIMIT(%d),%d " , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); - result.append("\n"); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.message))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band))); - result.append("\n"); + //result.append("
No.Protocoli3.n3UTCdBDt%s%s%s
\n"); + HtmlContext.tableBegin(result, false, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result, "No."); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_protocol)); + HtmlContext.tableCellHeader(result, "i3.n3", "UTC", "dB", "Δt"); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_qsl_freq)); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.message)); + HtmlContext.tableCellHeader(result, GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + + int order = 0; while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } + HtmlContext.tableRowBegin(result, true, order % 2 != 0); - //int id = cursor.getInt(cursor.getColumnIndex("ID")); int i3 = cursor.getInt(cursor.getColumnIndex("I3")); int n3 = cursor.getInt(cursor.getColumnIndex("N3")); String utcTime = cursor.getString(cursor.getColumnIndex("UTC")); @@ -1184,35 +980,30 @@ public class LogHttpServer extends NanoHTTPD { String extra = cursor.getString(cursor.getColumnIndex("EXTRAL")); long band = cursor.getLong(cursor.getColumnIndex("BAND")); - //UtcTimer.getDatetimeStr(utcTime) - result.append(String.format("" + - "" +//protocol - "" + - "" - , order + 1 + pageSize * (pageIndex - 1) - , protocol - , Ft8Message.getCommandInfoByI3N3(i3, n3) - , utcTime - , dB, dt, freq, String.format("" + - "%s  " + - "%s  %s" - , pageSize, callTo.replace("<", "") - .replace(">", "") - , callTo.replace("<", "<") - .replace(">", ">") - , pageSize, callFrom.replace("<", "") - .replace(">", "") - , callFrom.replace("<", "<") - .replace(">", ">"), extra) + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, protocol, Ft8Message.getCommandInfoByI3N3(i3, n3)); + HtmlContext.tableCell(result, utcTime); + HtmlContext.tableCell(result, String.format("%d", dB)); + HtmlContext.tableCell(result, String.format("%.1f", dt)); + HtmlContext.tableCell(result, String.format("%dHz", freq)); + HtmlContext.tableCell(result, String.format("" + + "%s  " + + "%s  %s", pageSize, callTo.replace("<", "") + .replace(">", "") + , callTo.replace("<", "<") + .replace(">", ">") + , pageSize, callFrom.replace("<", "") + .replace(">", "") + , callFrom.replace("<", "<") + .replace(">", ">"), extra)); + HtmlContext.tableCell(result, BaseRigOperation.getFrequencyStr(band)).append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); - , BaseRigOperation.getFrequencyStr(band))); - //result.append(UtcTimer.getDatetimeStr(utcTime)); - result.append("\n"); order++; } cursor.close(); - result.append("
%d%s%s%s%d%.1f%dHz%s" + - "%s

"); + HtmlContext.tableEnd(result).append("
\n"); + //result.append("
"); return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); @@ -1221,7 +1012,14 @@ public class LogHttpServer extends NanoHTTPD { } - + /** + * 把swo的QSO日志导出到文件 + * @param exportFile 文件名 + * @param callsign 呼号 + * @param start_date 起始日期 + * @param end_date 结束日期 + * @return 数据 + */ @SuppressLint("Range") private Response exportSWLQSOMessage(String exportFile, String callsign, String start_date, String end_date) { Response response; @@ -1241,11 +1039,11 @@ public class LogHttpServer extends NanoHTTPD { .replace(".", "_") .replace(">", "_")); } - if (start_date.length()>0){ - fileName.append(String.format("_%s",start_date)); + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); } - if (end_date.length()>0){ - fileName.append(String.format("_%s",end_date)); + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); } fileName.append(".").append(exportFile); @@ -1265,21 +1063,24 @@ public class LogHttpServer extends NanoHTTPD { cursor = mainViewModel.databaseOpr.getDb().rawQuery( String.format( "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - dateSql.toString() + - " order by ID "),new String[]{whereStr, whereStr}); + dateSql + + " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" - , downQSLTable(cursor,true)); + , downQSLTable(cursor, true)); response.addHeader("Content-Disposition" - , String.format("attachment;filename=%s",fileName.toString())); + , String.format("attachment;filename=%s", fileName)); return response; } - - + /** + * 查询SWL日志 + * @param session 会话 + * @return html + */ @SuppressLint({"DefaultLocale", "Range"}) private Response getSWLQsoMessages(IHTTPSession session) { int pageSize = 100; @@ -1320,8 +1121,12 @@ public class LogHttpServer extends NanoHTTPD { return exportSWLQSOMessage(exportFile, callsign, startDate, endDate); } + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + result.append(""); + result.append(String.format("%s" - , callsign, startDate, endDate,GeneralVariables.getStringFromResource(R.string.html_export_adi))); + , callsign, startDate, endDate, GeneralVariables.getStringFromResource(R.string.html_export_adi))); Cursor cursor; StringBuilder dateSql = new StringBuilder(); @@ -1336,7 +1141,7 @@ public class LogHttpServer extends NanoHTTPD { //计算总的记录数 cursor = mainViewModel.databaseOpr.getDb().rawQuery( "select count(*) as rc from SWLQSOTable " + - "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql.toString() + "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql , new String[]{whereStr, whereStr}); cursor.moveToFirst(); int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); @@ -1347,19 +1152,20 @@ public class LogHttpServer extends NanoHTTPD { result.append(String.format("%s , %s" + "" +//页码及页大小 "
\n%s " +//呼号 - "
\n%s " +//起始时间 - "
\n%s " +//结束时间 - "  
\n" + "  
\n" + + "
\n%s " +//起始时间 + " \n%s 
" //结束时间 + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) , GeneralVariables.getStringFromResource(R.string.html_message_page_size) , pageSize , GeneralVariables.getStringFromResource(R.string.html_callsign) , callsign + , GeneralVariables.getStringFromResource(R.string.html_message_query) , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) , startDate , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) - , endDate - , GeneralVariables.getStringFromResource(R.string.html_message_query))); + , endDate)); //定位页,第一页、上一页、下一页,最后一页 @@ -1368,48 +1174,48 @@ public class LogHttpServer extends NanoHTTPD { "" + ">>" + "  >|
\n" + , 1, pageSize, callsign, startDate, endDate , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate , pageIndex , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate , pageCount, pageSize, callsign, startDate, endDate)); - + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result).append("\n"); cursor = mainViewModel.databaseOpr.getDb().rawQuery( String.format( "select * from SWLQSOTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + - dateSql.toString() + - " order by ID LIMIT(%d),%d " + dateSql + + " order by qso_date desc,time_on desc LIMIT(%d),%d " , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); - result.append("\n"); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append("\n"); + HtmlContext.tableBegin(result, false, true).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result, "No." + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"call" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode)//"mode" + , GeneralVariables.getStringFromResource(R.string.html_rst_sent)//"rst_sent" + , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd)//"rst_rcvd" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" + , GeneralVariables.getStringFromResource(R.string.html_qsl_band)//"band" + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq)//"freq" + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") + .append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); int order = 0; while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); - //int id = cursor.getInt(cursor.getColumnIndex("ID")); String call = cursor.getString(cursor.getColumnIndex("call")); String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); String mode = cursor.getString(cursor.getColumnIndex("mode")); @@ -1426,58 +1232,350 @@ public class LogHttpServer extends NanoHTTPD { String comment = cursor.getString(cursor.getColumnIndex("comment")); - //UtcTimer.getDatetimeStr(utcTime) - result.append(String.format("" + - ""+//call - "" +//gridsquare - "" +//mode - "" +//rst_sent - "" +//rst_rcvd - "" +//qso_date - "" +//time_on - "" +//qso_date_off - "" +//time_off - "" +//band - "" +//freq - ""+//station_callsign - "" +//my_gridsquare - "" //comment - - , order + 1 + pageSize * (pageIndex - 1) - - ,pageSize - ,call.replace("<", "") + //生成数据表的一行 + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, String.format("%s" + , pageSize, call.replace("<", "") .replace(">", "") , call.replace("<", "<") - .replace(">", ">") - - ,gridsquare==null?"":gridsquare - ,mode,rst_sent,rst_rcvd - ,qso_date,time_on,qso_date_off,time_off - ,band,freq - - ,pageSize - ,station_callsign.replace("<", "") + .replace(">", ">"))); + HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare); + HtmlContext.tableCell(result, mode, rst_sent, rst_rcvd, qso_date, time_on, qso_date_off, time_off); + HtmlContext.tableCell(result, band, freq); + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , station_callsign.replace("<", "") .replace(">", "") , station_callsign.replace("<", "<") - .replace(">", ">") + .replace(">", ">"))); + HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare + , comment).append("\n"); - ,my_gridsquare==null?"":my_gridsquare - ,comment )); - //result.append(UtcTimer.getDatetimeStr(utcTime)); - result.append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); order++; } cursor.close(); - result.append("
No.callgridsquaremoderst_sentrst_rcvdqso_datetime_onqso_date_offtime_offbandfreqstation_callsignmy_gridsquarecomment
%d%s%s%s%s%s%s%s%s%s%s%s%s%s%s

"); - + HtmlContext.tableEnd(result).append("
\n"); return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); - //return result.toString(); - } + /** + * 把QSO日志导出到文件 + * @param exportFile 文件名 + * @param callsign 呼号 + * @param start_date 起始日期 + * @param end_date 结束日期 + * @return 数据 + */ + @SuppressLint("Range") + private Response exportQSOLogs(String exportFile, String callsign, String start_date, String end_date, String extWhere) { + Response response; + StringBuilder fileName = new StringBuilder(); + fileName.append("qso_log"); + if (callsign.length() > 0) { + fileName.append("_"); + fileName.append(callsign.replace("/", "_") + .replace("\\", "_") + .replace(":", "_") + .replace("?", "_") + .replace("*", "_") + .replace("|", "_") + .replace("\"", "_") + .replace("'", "_") + .replace("<", "_") + .replace(".", "_") + .replace(">", "_")); + } + if (start_date.length() > 0) { + fileName.append(String.format("_%s", start_date)); + } + if (end_date.length() > 0) { + fileName.append(String.format("_%s", end_date)); + } + fileName.append(".").append(exportFile); + + + Cursor cursor; + String whereStr = String.format("%%%s%%", callsign); + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + extWhere + + " order by qso_date desc,time_on desc "), new String[]{whereStr, whereStr}); + + + response = newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "text/plain" + , downQSLTable(cursor, false)); + response.addHeader("Content-Disposition" + , String.format("attachment;filename=%s", fileName)); + + return response; + } + + /** + * 查询QSO日志 + * @param session 会话 + * @return html + */ + @SuppressLint({"DefaultLocale", "Range"}) + private Response getQsoLogs(IHTTPSession session) { + int pageSize = 100; + String callsign = ""; + + + StringBuilder result = new StringBuilder(); + String startDate = ""; + String endDate = ""; + String exportFile = ""; + String qIsQSL = ""; + String qIsImported = ""; + + //读取查询的参数 + Map pars = session.getParms(); + int pageIndex = 1; + if (pars.get("page") != null) { + pageIndex = Integer.parseInt(Objects.requireNonNull(pars.get("page"))); + } + if (pars.get("pageSize") != null) { + pageSize = Integer.parseInt(Objects.requireNonNull(pars.get("pageSize"))); + } + if (pars.get("callsign") != null) { + callsign = Objects.requireNonNull(pars.get("callsign")); + } + if (pars.get("start_date") != null) { + startDate = Objects.requireNonNull(pars.get("start_date")); + } + if (pars.get("end_date") != null) { + endDate = Objects.requireNonNull(pars.get("end_date")); + } + String whereStr = String.format("%%%s%%", callsign); + + if (pars.get("exportFile") != null) { + exportFile = Objects.requireNonNull(pars.get("exportFile")); + } + if (pars.get("QSL") != null) { + qIsQSL = Objects.requireNonNull(pars.get("QSL")); + } + if (pars.get("Imported") != null) { + qIsImported = Objects.requireNonNull(pars.get("Imported")); + } + + result.append("
\n"); + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result).append(""); + result.append(String.format("%s\n" + , callsign, startDate, endDate, qIsQSL, qIsImported + , GeneralVariables.getStringFromResource(R.string.html_export_adi))); + + Cursor cursor; + StringBuilder dateSql = new StringBuilder(); + if (!startDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)>=\"%s\") " + , startDate.replace("-", ""))); + } + if (!endDate.equals("")) { + dateSql.append(String.format(" AND (SUBSTR(qso_date_off,1,8)<=\"%s\") " + , endDate.replace("-", ""))); + } + if (!qIsQSL.equals("")) { + dateSql.append(String.format(" AND ((isQSl = %s) %s (isLotW_QSL = %s))" + , qIsQSL, qIsQSL.equals("1") ? "OR" : "AND", qIsQSL)); + } + if (!qIsImported.equals("")) { + dateSql.append(String.format(" AND (isLotW_import = %s)", qIsImported)); + } + + + //导出到文件中 + if (exportFile.equalsIgnoreCase("ADI")) { + return exportQSOLogs(exportFile, callsign, startDate, endDate, dateSql.toString()); + } + + //计算总的记录数 + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + "select count(*) as rc from QSLTable " + + "where (([call] LIKE ?)OR(station_callsign LIKE ?))" + dateSql + , new String[]{whereStr, whereStr}); + cursor.moveToFirst(); + int pageCount = Math.round(((float) cursor.getInt(cursor.getColumnIndex("rc")) / pageSize) + 0.5f); + if (pageIndex > pageCount) pageIndex = pageCount; + cursor.close(); + + //查询、每页消息数设定 + result.append(""); + HtmlContext.tableRowEnd(result); + HtmlContext.tableRowBegin(result).append("\n"); + + result.append(String.format("%s , %s" + + "" +//页码及页大小 + "  
\n" + + "
\n%s " +//呼号 + "\n%s " +//起始时间 + "\n%s \n" +//结束时间 + " \n" + + " \n" + + "
\n" + + + "
\n" + + " QSL" + + "
\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
" + + "
\n" + + "
\n" + + "
\n" + + " " + GeneralVariables.getStringFromResource(R.string.html_qso_source) + "" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
" + + "
" + + "
" + + "\n" + + " "); + HtmlContext.tableRowEnd(result); + HtmlContext.tableEnd(result); + result.append(""); + //"
" + + + , String.format(GeneralVariables.getStringFromResource(R.string.html_message_page_count), pageCount) + , GeneralVariables.getStringFromResource(R.string.html_message_page_size) + , pageSize + , GeneralVariables.getStringFromResource(R.string.html_message_query) + + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , callsign + , GeneralVariables.getStringFromResource(R.string.html_start_date_swl_message) + , startDate + , GeneralVariables.getStringFromResource(R.string.html_end_date_swl_message) + , endDate + + , qIsQSL.equals("") ? "checked=\"true\"" : "" + , qIsQSL.equals("0") ? "checked=\"true\"" : "" + , qIsQSL.equals("1") ? "checked=\"true\"" : "" + + + , qIsImported.equals("") ? "checked=\"true\"" : "" + , qIsImported.equals("0") ? "checked=\"true\"" : "" + , qIsImported.equals("1") ? "checked=\"true\"" : "" + )); + + + //定位页,第一页、上一页、下一页,最后一页 + result.append(String.format("|<" + + "  <<" + + "" + + ">>\n" + + "  >|\n" + + , 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageIndex - 1 == 0 ? 1 : pageIndex - 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageIndex + , pageIndex == pageCount ? pageCount : pageIndex + 1, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported + , pageCount, pageSize, callsign, startDate, endDate, qIsQSL, qIsImported)); + result.append("
\n" + + + cursor = mainViewModel.databaseOpr.getDb().rawQuery( + String.format( + "select * from QSLTable where (([call] LIKE ?)OR(station_callsign LIKE ?)) " + + dateSql + + " order by qso_date desc,time_on desc LIMIT(%d),%d " + , (pageIndex - 1) * pageSize, pageSize), new String[]{whereStr, whereStr}); + + HtmlContext.tableBegin(result, false, true).append("\n"); + + //表头 + HtmlContext.tableRowBegin(result).append("\n"); + HtmlContext.tableCellHeader(result, "No.", "QSL" + , GeneralVariables.getStringFromResource(R.string.html_qso_source) + , GeneralVariables.getStringFromResource(R.string.html_callsign) + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid) + , GeneralVariables.getStringFromResource(R.string.html_qsl_mode) + , GeneralVariables.getStringFromResource(R.string.html_rst_sent) + , GeneralVariables.getStringFromResource(R.string.html_rst_rcvd) + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_day)//"qso date" + , GeneralVariables.getStringFromResource(R.string.html_qsl_start_time)//"time_on" + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_date)//qso date off + , GeneralVariables.getStringFromResource(R.string.html_qsl_end_time)//"time_off" + , GeneralVariables.getStringFromResource(R.string.html_qsl_band) + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.html_callsign)//"station_callsign" + , GeneralVariables.getStringFromResource(R.string.html_qsl_grid)//"my_gridsquare" + , GeneralVariables.getStringFromResource(R.string.html_comment))//"comment") + .append("\n"); + HtmlContext.tableRowEnd(result).append("\n"); + + //表内容 + int order = 0; + while (cursor.moveToNext()) { + HtmlContext.tableRowBegin(result, true, order % 2 != 0).append("\n"); + + String call = cursor.getString(cursor.getColumnIndex("call")); + boolean isQSL = cursor.getInt(cursor.getColumnIndex("isQSL")) == 1; + boolean isLotW_Import = cursor.getInt(cursor.getColumnIndex("isLotW_import")) == 1; + boolean isLotW_QSL = cursor.getInt(cursor.getColumnIndex("isLotW_QSL")) == 1; + String gridsquare = cursor.getString(cursor.getColumnIndex("gridsquare")); + String mode = cursor.getString(cursor.getColumnIndex("mode")); + String rst_sent = cursor.getString(cursor.getColumnIndex("rst_sent")); + String rst_rcvd = cursor.getString(cursor.getColumnIndex("rst_rcvd")); + String qso_date = cursor.getString(cursor.getColumnIndex("qso_date")); + String time_on = cursor.getString(cursor.getColumnIndex("time_on")); + String qso_date_off = cursor.getString(cursor.getColumnIndex("qso_date_off")); + String time_off = cursor.getString(cursor.getColumnIndex("time_off")); + String band = cursor.getString(cursor.getColumnIndex("band")); + String freq = cursor.getString(cursor.getColumnIndex("freq")); + String station_callsign = cursor.getString(cursor.getColumnIndex("station_callsign")); + String my_gridsquare = cursor.getString(cursor.getColumnIndex("my_gridsquare")); + String comment = cursor.getString(cursor.getColumnIndex("comment")); + + + HtmlContext.tableCell(result, String.format("%d", order + 1 + pageSize * (pageIndex - 1))); + HtmlContext.tableCell(result, (isQSL || isLotW_QSL) ? "" : ""); + HtmlContext.tableCell(result, isLotW_Import ? + String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_qso_external)) + : String.format("%s" + , GeneralVariables.getStringFromResource(R.string.html_qso_raw)));//是否是导入的 + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , call.replace("<", "") + .replace(">", "") + , call.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, gridsquare == null ? "" : gridsquare, mode, rst_sent, rst_rcvd + , qso_date, time_on, qso_date_off, time_off, band, freq); + HtmlContext.tableCell(result, String.format("%s" + , pageSize + , station_callsign.replace("<", "") + .replace(">", "") + , station_callsign.replace("<", "<") + .replace(">", ">"))); + HtmlContext.tableCell(result, my_gridsquare == null ? "" : my_gridsquare + , comment).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + + order++; + } + cursor.close(); + HtmlContext.tableEnd(result).append("
\n"); + + return newFixedLengthResponse(HtmlContext.HTML_STRING(result.toString())); + } /** * 获取全部通联日志 @@ -1487,7 +1585,7 @@ public class LogHttpServer extends NanoHTTPD { private String showAllQSL() { Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( "select * from QSLTable order by ID DESC ", null); - return HtmlContext.ListTableContext(cursor); + return HtmlContext.ListTableContext(cursor, true); } /** @@ -1500,7 +1598,7 @@ public class LogHttpServer extends NanoHTTPD { Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery( "select * from QSLTable WHERE SUBSTR(qso_date,1,?)=? \n" + "order by ID DESC ", new String[]{String.valueOf(month.length()), month}); - return HtmlContext.ListTableContext(cursor); + return HtmlContext.ListTableContext(cursor, true); } /** @@ -1516,32 +1614,25 @@ public class LogHttpServer extends NanoHTTPD { "}\n" + "setTimeout('myrefresh()',5000); //指定5秒刷新一次,5000处可自定义设置,1000为1秒\n" + ""); - result.append("\n"); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_qsl_freq))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.message))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band))); - result.append("\n"); + HtmlContext.tableBegin(result, false, true).append("\n"); + + HtmlContext.tableRowBegin(result); + HtmlContext.tableCellHeader(result, "UTC", "dB", "Δt" + , GeneralVariables.getStringFromResource(R.string.html_qsl_freq) + , GeneralVariables.getStringFromResource(R.string.message) + , GeneralVariables.getStringFromResource(R.string.html_carrier_frequency_band)); + HtmlContext.tableRowEnd(result).append("\n"); + int order = 0; if (mainViewModel.currentMessages != null) { for (Ft8Message message : mainViewModel.currentMessages) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - result.append(message.toHtml()); - result.append(""); + HtmlContext.tableRowBegin(result, true, order % 2 != 0) + .append("\n").append(message.toHtml()); + HtmlContext.tableRowEnd(result).append("\n"); order++; } } - result.append("
UTCdBDt%s%s%s

"); + HtmlContext.tableEnd(result).append("
\n"); return result.toString(); } @@ -1552,18 +1643,23 @@ public class LogHttpServer extends NanoHTTPD { */ private String showImportLog() { StringBuilder result = new StringBuilder(); - result.append("\n"); - result.append(String.format("" + HtmlContext.tableBegin(result, false, 0, true).append("\n"); + HtmlContext.tableRowBegin(result, false, true); + HtmlContext.tableCell(result, String.format("%s%s%s" , GeneralVariables.getStringFromResource(R.string.html_please_select) , GeneralVariables.getStringFromResource(R.string.html_adi_format) , GeneralVariables.getStringFromResource(R.string.html_file_in_other_format))); - result.append(""); - result.append("
" + - "%s%s%s


\n" + - " \n" + + " \n" + " \n" + - "
"); + " "); + HtmlContext.tableRowEnd(result).append("\n"); + HtmlContext.tableEnd(result).append("\n"); return result.toString(); } @@ -1571,21 +1667,23 @@ public class LogHttpServer extends NanoHTTPD { private String showQSLTable() { StringBuilder result = new StringBuilder(); - result.append("\n"); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_time))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_total))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_operation))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_operation))); - result.append(String.format("" - , GeneralVariables.getStringFromResource(R.string.html_operation))); + HtmlContext.tableBegin(result, false, 1, true).append("\n"); + HtmlContext.tableRowBegin(result).append("\n"); + + HtmlContext.tableCellHeader(result + , GeneralVariables.getStringFromResource(R.string.html_time) + , GeneralVariables.getStringFromResource(R.string.html_total) + , GeneralVariables.getStringFromResource(R.string.html_operation) + , GeneralVariables.getStringFromResource(R.string.html_operation) + , GeneralVariables.getStringFromResource(R.string.html_operation) + ).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select count(*) as b from QSLTable" , null); cursor.moveToFirst(); + result.append(String.format("" , GeneralVariables.getStringFromResource(R.string.html_all_logs))); result.append(String.format("", cursor.getString(cursor.getColumnIndex("b")))); @@ -1598,53 +1696,52 @@ public class LogHttpServer extends NanoHTTPD { "WHERE SUBSTR(qso_date,1,8)=?", new String[]{UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime())}); cursor.moveToFirst(); - result.append(""); - - result.append(String.format("" + HtmlContext.tableRowBegin(result, true, true).append("\n"); + HtmlContext.tableCell(result, String.format("%s" , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) , GeneralVariables.getStringFromResource(R.string.html_today_log))); - result.append(String.format("" - , cursor.getString(cursor.getColumnIndex("b")))); - result.append(String.format("" + HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); + HtmlContext.tableCell(result, String.format("%s" , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) , GeneralVariables.getStringFromResource(R.string.html_download_all))); - result.append(String.format("" + HtmlContext.tableCell(result, String.format("%s" , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); - - result.append(String.format("" + HtmlContext.tableCell(result, String.format("%s" , UtcTimer.getYYYYMMDD(UtcTimer.getSystemTime()) - , GeneralVariables.getStringFromResource(R.string.html_delete))); + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); + cursor.close(); int order = 1; - cursor = mainViewModel.databaseOpr.getDb().rawQuery("select SUBSTR(qso_date,1,6) as a,count(*) as b from QSLTable\n" + - "group by SUBSTR(qso_date,1,6)", null); + cursor = mainViewModel.databaseOpr.getDb() + .rawQuery("select SUBSTR(qso_date,1,6) as a,count(*) as b from QSLTable\n" + + "group by SUBSTR(qso_date,1,6)", null); while (cursor.moveToNext()) { - if (order % 2 == 0) { - result.append(""); - } else { - result.append(""); - } - result.append(String.format("" + HtmlContext.tableRowBegin(result, true, order % 2 == 0); + + HtmlContext.tableCell(result, String.format("%s" , cursor.getString(cursor.getColumnIndex("a")) , cursor.getString(cursor.getColumnIndex("a")))); - result.append(String.format("" - , cursor.getString(cursor.getColumnIndex("b")))); - result.append(String.format("" + + HtmlContext.tableCell(result, cursor.getString(cursor.getColumnIndex("b"))); + HtmlContext.tableCell(result, String.format("%s" , cursor.getString(cursor.getColumnIndex("a")) , GeneralVariables.getStringFromResource(R.string.html_download_all))); - result.append(String.format("" + HtmlContext.tableCell(result, String.format("%s" , cursor.getString(cursor.getColumnIndex("a")) , GeneralVariables.getStringFromResource(R.string.html_download_unconfirmed))); - - result.append(String.format("" + HtmlContext.tableCell(result, String.format("%s" , cursor.getString(cursor.getColumnIndex("a")) - , GeneralVariables.getStringFromResource(R.string.html_delete))); + , GeneralVariables.getStringFromResource(R.string.html_delete))).append("\n"); + + HtmlContext.tableRowEnd(result).append("\n"); order++; } - result.append("
%s%s%s%s%s
%s%s
%s%s%s%s%s
%s%s%s%s%s
"); - //cursor.close(); + HtmlContext.tableEnd(result).append("\n"); + cursor.close(); return result.toString(); } @@ -1660,17 +1757,17 @@ public class LogHttpServer extends NanoHTTPD { , new String[]{String.valueOf(month.length()), month}); } - return downQSLTable(cursor,false); + return downQSLTable(cursor, false); } /** * 下载全部日志 * - * @return + * @return String */ private String downAllQSl() { Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable", null); - return downQSLTable(cursor,false); + return downQSLTable(cursor, false); } /** @@ -1679,8 +1776,7 @@ public class LogHttpServer extends NanoHTTPD { * @return 日志内容 */ @SuppressLint({"Range", "DefaultLocale"}) - private String downQSLTable(Cursor cursor,boolean isSWL) { - //Cursor cursor = mainViewModel.databaseOpr.getDb().rawQuery("select * from QSLTable", null); + private String downQSLTable(Cursor cursor, boolean isSWL) { StringBuilder logStr = new StringBuilder(); logStr.append("FT8CN ADIF Export\n"); @@ -1699,76 +1795,76 @@ public class LogHttpServer extends NanoHTTPD { } else { logStr.append("N "); } - }else { + } else { logStr.append("Y "); } - if (cursor.getString(cursor.getColumnIndex("gridsquare"))!=null) { + if (cursor.getString(cursor.getColumnIndex("gridsquare")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("gridsquare")).length() , cursor.getString(cursor.getColumnIndex("gridsquare")))); } - if (cursor.getString(cursor.getColumnIndex("mode"))!=null) { + if (cursor.getString(cursor.getColumnIndex("mode")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("mode")).length() , cursor.getString(cursor.getColumnIndex("mode")))); } - if (cursor.getString(cursor.getColumnIndex("rst_sent"))!=null) { + if (cursor.getString(cursor.getColumnIndex("rst_sent")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("rst_sent")).length() , cursor.getString(cursor.getColumnIndex("rst_sent")))); } - if (cursor.getString(cursor.getColumnIndex("rst_rcvd"))!=null) { + if (cursor.getString(cursor.getColumnIndex("rst_rcvd")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("rst_rcvd")).length() , cursor.getString(cursor.getColumnIndex("rst_rcvd")))); } - if (cursor.getString(cursor.getColumnIndex("qso_date"))!=null) { + if (cursor.getString(cursor.getColumnIndex("qso_date")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("qso_date")).length() , cursor.getString(cursor.getColumnIndex("qso_date")))); } - if (cursor.getString(cursor.getColumnIndex("time_on"))!=null) { + if (cursor.getString(cursor.getColumnIndex("time_on")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("time_on")).length() , cursor.getString(cursor.getColumnIndex("time_on")))); } - if (cursor.getString(cursor.getColumnIndex("qso_date_off"))!=null) { + if (cursor.getString(cursor.getColumnIndex("qso_date_off")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("qso_date_off")).length() , cursor.getString(cursor.getColumnIndex("qso_date_off")))); } - if (cursor.getString(cursor.getColumnIndex("time_off"))!=null) { + if (cursor.getString(cursor.getColumnIndex("time_off")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("time_off")).length() , cursor.getString(cursor.getColumnIndex("time_off")))); } - if (cursor.getString(cursor.getColumnIndex("band"))!=null) { + if (cursor.getString(cursor.getColumnIndex("band")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("band")).length() , cursor.getString(cursor.getColumnIndex("band")))); } - if (cursor.getString(cursor.getColumnIndex("freq"))!=null) { + if (cursor.getString(cursor.getColumnIndex("freq")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("freq")).length() , cursor.getString(cursor.getColumnIndex("freq")))); } - if (cursor.getString(cursor.getColumnIndex("station_callsign"))!=null) { + if (cursor.getString(cursor.getColumnIndex("station_callsign")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("station_callsign")).length() , cursor.getString(cursor.getColumnIndex("station_callsign")))); } - if (cursor.getString(cursor.getColumnIndex("my_gridsquare"))!=null) { + if (cursor.getString(cursor.getColumnIndex("my_gridsquare")) != null) { logStr.append(String.format("%s " , cursor.getString(cursor.getColumnIndex("my_gridsquare")).length() , cursor.getString(cursor.getColumnIndex("my_gridsquare")))); @@ -1783,7 +1879,6 @@ public class LogHttpServer extends NanoHTTPD { , comment)); } - //Log.e(TAG, "getQSLTable: " + logStr.toString()); cursor.close(); return logStr.toString(); } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComPacketTypes.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IComWifiRig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java old mode 100755 new mode 100644 index cbd022b..d0f1242 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomAudioUdp.java @@ -47,48 +47,10 @@ public class IcomAudioUdp extends IcomUdpBase { x = 1.0f; else if (x < -1.0) x = -1.0f; - temp[i] = (short) (0.5 + (x * 32767.0)); + temp[i] = (short) (x * 32767.0); } doTXAudioRunnable.audioData=temp; doTXThreadPool.execute(doTXAudioRunnable); -// new Thread(new Runnable() { -// @Override -// public void run() { -// final int partialLen = IComPacketTypes.TX_BUFFER_SIZE * 2;//数据包的长度 -// //要转换一下到BYTE,小端模式 -// -// //byte[] data = new byte[audioData.length * 2 + partialLen * 4];//多出一点空声音放在前后各20ms*2共80ms -// //先播放,是给出空的声音,for i 循环,做了一个判断,是给前面的空声音,for j循环,做得判断,是让后面发送空声音 -// byte[] audioPacket = new byte[partialLen]; -// for (int i = 0; i < (audioData.length / IComPacketTypes.TX_BUFFER_SIZE) + 8; i++) {//多出6个周期,前面3个,后面3个多 -// if (!isPttOn) break; -// long now = System.currentTimeMillis() - 1;//获取当前时间 -// -// sendTrackedPacket(IComPacketTypes.AudioPacket.getTxAudioPacket(audioPacket -// , (short) 0, localId, remoteId, innerSeq)); -// innerSeq++; -// -// Arrays.fill(audioPacket,(byte)0x00); -// if (i>=3) {//让前两个空数据发送出去 -// for (int j = 0; j < IComPacketTypes.TX_BUFFER_SIZE; j++) { -// if ((i-3) * IComPacketTypes.TX_BUFFER_SIZE + j < audioData.length) { -// System.arraycopy(IComPacketTypes.shortToBigEndian((short) -// (audioData[(i-3) * IComPacketTypes.TX_BUFFER_SIZE + j] -// * GeneralVariables.volumePercent)) -// , 0, audioPacket, j * 2, 2); -// } -// } -// } -// while (isPttOn) { -// if (System.currentTimeMillis() - now >= 21) {//20毫秒一个周期 -// break; -// } -// } -// } -// Log.e(TAG, "run: 音频发送完毕!!" ); -// Thread.currentThread().interrupt(); -// } -// }).start(); } private static class DoTXAudioRunnable implements Runnable{ IcomAudioUdp icomAudioUdp; diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomCivUdp.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomControlUdp.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomSeqBuffer.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpBase.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/icom/IcomUdpClient.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/HashTable.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogCallsignAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogFileImport.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java old mode 100755 new mode 100644 index 7e39539..8e170f8 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/log/LogQSLAdapter.java @@ -111,10 +111,15 @@ public class LogQSLAdapter extends RecyclerView.Adapter1000) clearBufferData(); + //return;//说明数据还没接收完。 + }else { + if (s.indexOf("\r")>0){//说明接到结束的数据了,并且不是第一个字符是; + buffer.append(s.substring(0,s.indexOf("\r"))); + } + //开始分析数据 + Yaesu3Command yaesu3Command = Yaesu3Command.getCommand(buffer.toString()); + clearBufferData();//清一下缓存 + //要把剩下的数据放到缓存里 + buffer.append(s.substring(s.indexOf("\r")+1)); + + if (yaesu3Command == null) { + return; + } + String cmd=yaesu3Command.getCommandID(); + if (cmd.equalsIgnoreCase("FA")) {//频率 + long tempFreq=Yaesu3Command.getFrequency(yaesu3Command); + if (tempFreq!=0) {//如果tempFreq==0,说明频率不正常 + setFreq(Yaesu3Command.getFrequency(yaesu3Command)); + } + }else if (cmd.equalsIgnoreCase("RM")){//meter + if (Yaesu3Command.is590MeterSWR(yaesu3Command)) { + swr = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + if (Yaesu3Command.is590MeterALC(yaesu3Command)) { + alc = Yaesu3Command.get590ALCOrSWR(yaesu3Command); + } + showAlert(); + } + + } + + } + private void showAlert() { + if (swr >= KenwoodTK90RigConstant.ts_590_swr_alert_max) { + if (!swrAlert) { + swrAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.swr_high_alert)); + } + } else { + swrAlert = false; + } + if (alc > KenwoodTK90RigConstant.ts_590_alc_alert_max) {//网络模式下不警告ALC + if (!alcMaxAlert) { + alcMaxAlert = true; + ToastMessage.show(GeneralVariables.getStringFromResource(R.string.alc_high_alert)); + } + } else { + alcMaxAlert = false; + } + + } + @Override + public void readFreqFromRig() { + if (getConnector() != null) { + clearBufferData();//清空一下缓存 + getConnector().sendData(KenwoodTK90RigConstant.setTS590ReadOperationFreq()); + } + } + + @Override + public String getName() { + return "KENWOOD TS-2000"; + } + + public KenwoodTS2000Rig() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (getConnector()!=null){ + getConnector().sendData(KenwoodTK90RigConstant.setTS590VFOMode()); + } + } + },START_QUERY_FREQ_DELAY-500); + readFreqTimer.schedule(readTask(), START_QUERY_FREQ_DELAY,QUERY_FREQ_TIMEOUT); + } +} diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java old mode 100755 new mode 100644 index 6905c42..fb59880 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/KenwoodTS590Rig.java @@ -176,7 +176,7 @@ public class KenwoodTS590Rig extends BaseRig { @Override public String getName() { - return "KENWOOD TS-480/590/2000"; + return "KENWOOD TS-480/590"; } public KenwoodTS590Rig() { diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnConnectReceiveData.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/OnRigStateChanged.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/XieGuRig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Command.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu2Rig.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38Rig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu38_450Rig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu39Rig.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/Yaesu3Command.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java old mode 100755 new mode 100644 index 338b384..1f7244e --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/rigs/YaesuDX10Rig.java @@ -17,7 +17,7 @@ import java.util.TimerTask; * 3代的指令,不同电台还有不同,频率长度981,991是9位,其它的长度是8位 */ public class YaesuDX10Rig extends BaseRig { - private static final String TAG = "Yaesu3Rig"; + private static final String TAG = "YaesuDX10Rig"; private final StringBuilder buffer = new StringBuilder(); private int swr = 0; private int alc = 0; diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java old mode 100755 new mode 100644 index 8574a44..be22673 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CdcAcmSerialDriver.java @@ -305,6 +305,8 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { new int[] { UsbId.VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL, }); + + supportedDevices.put(UsbId.VENDOR_ATMEL, new int[] { UsbId.ATMEL_LUFA_CDC_DEMO_APP, @@ -321,7 +323,8 @@ public class CdcAcmSerialDriver implements UsbSerialDriver { new int[] { UsbId.ST_CDC, UsbId.ST_CDC2, - UsbId.ST_CDC3 + UsbId.ST_CDC3, + UsbId.CDC_WOLF_PID }); supportedDevices.put(UsbId.VENDOR_RASPBERRY_PI, new int[] { diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Ch34xSerialDriver.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/CommonUsbSerialPort.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/Cp21xxSerialDriver.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/FtdiSerialDriver.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProbeTable.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/ProlificSerialDriver.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/SerialTimeoutException.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java old mode 100755 new mode 100644 index b9db984..73ab2a2 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbId.java @@ -42,6 +42,8 @@ public final class UsbId { public static final int VENDOR_VAN_OOIJEN_TECH = 0x16c0; public static final int VAN_OOIJEN_TECH_TEENSYDUINO_SERIAL = 0x0483; + public static final int CDC_WOLF_PID= 0xF001;// ST CDC WOLF RIG + public static final int VENDOR_LEAFLABS = 0x1eaf; public static final int LEAFLABS_MAPLE = 0x0004; diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialDriver.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/UsbSerialProber.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/MonotonicClock.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/serialport/util/SerialInputOutputManager.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/spectrum/SpectrumListener.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/OnUtcTimer.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/timer/UtcTimer.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BandsSpinnerAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/BauRateSpinnerAdapter.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index b2c7223..ddfb0f9 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/CallingListAdapter.java @@ -9,6 +9,7 @@ package com.bg7yoz.ft8cn.ui; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Paint; +import android.opengl.Visibility; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.View; @@ -169,6 +170,7 @@ public class CallingListAdapter extends RecyclerView.Adapter>() { + @Override + public void onChanged(ArrayList messages) { callingListAdapter.notifyDataSetChanged(); //当列表下部稍微多出一些,自动上移 if (callListRecyclerView.computeVerticalScrollRange() @@ -115,7 +124,6 @@ public class CallingListFragment extends Fragment { } }); - //观察UTC时间 mainViewModel.timerSec.observe(getViewLifecycleOwner(), new Observer() { @Override diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ClearCacheDataDialog.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ColumnarView.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index 0e7d215..9e69fa1 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ConfigFragment.java @@ -17,6 +17,10 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.CompoundButton; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; + import com.bg7yoz.ft8cn.FAQActivity; import com.bg7yoz.ft8cn.Ft8Message; import com.bg7yoz.ft8cn.GeneralVariables; @@ -34,10 +38,6 @@ import com.bg7yoz.ft8cn.timer.UtcTimer; import java.io.IOException; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.Observer; - /** * A simple {@link Fragment} subclass. * create an instance of this fragment. @@ -280,6 +280,15 @@ public class ConfigFragment extends Fragment { //设置电台名称,参数列表 setRigNameSpinner(); + //设置解码模式 + setDecodeMode(); + + //设置音频输出的位数 + setAudioOutputBitsMode(); + + //设置音频输出采样率 + setAudioOutputRateMode(); + //设置控制模式 VOX CAT setControlMode(); @@ -762,6 +771,80 @@ public class ConfigFragment extends Fragment { } + private void setDecodeMode() { + binding.decodeModeRadioGroup.clearCheck(); + binding.fastDecodeRadioButton.setChecked(!GeneralVariables.deepDecodeMode); + binding.deepDecodeRadioButton.setChecked(GeneralVariables.deepDecodeMode); + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.decodeModeRadioGroup.getCheckedRadioButtonId(); + GeneralVariables.deepDecodeMode= buttonId ==binding.deepDecodeRadioButton.getId(); + writeConfig("deepMode", GeneralVariables.deepDecodeMode? "1" : "0"); + } + }; + + binding.fastDecodeRadioButton.setOnClickListener(listener); + binding.deepDecodeRadioButton.setOnClickListener(listener); + + } + + + /** + * 设置音频输出的位数 + */ + private void setAudioOutputBitsMode() { + //binding.controlModeRadioGroup.setOnCheckedChangeListener(null); + binding.audioBitsRadioGroup.clearCheck(); + binding.audio32BitsRadioButton.setChecked(GeneralVariables.audioOutput32Bit); + binding.audio16BitsRadioButton.setChecked(!GeneralVariables.audioOutput32Bit); + + + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + int buttonId = binding.audioBitsRadioGroup.getCheckedRadioButtonId(); + GeneralVariables.audioOutput32Bit= buttonId ==binding.audio32BitsRadioButton.getId(); + writeConfig("audioBits", GeneralVariables.audioOutput32Bit? "1" : "0"); + } + }; + + binding.audio32BitsRadioButton.setOnClickListener(listener); + binding.audio16BitsRadioButton.setOnClickListener(listener); + + } + + /** + * 输出音频的采样率设置 + */ + private void setAudioOutputRateMode() { + binding.audioRateRadioGroup.clearCheck(); + binding.audio12kRadioButton.setChecked(GeneralVariables.audioSampleRate==12000); + binding.audio24kRadioButton.setChecked(GeneralVariables.audioSampleRate==24000); + binding.audio48kRadioButton.setChecked(GeneralVariables.audioSampleRate==48000); + + + + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View view) { + if (binding.audio12kRadioButton.isChecked()) GeneralVariables.audioSampleRate=12000; + if (binding.audio24kRadioButton.isChecked()) GeneralVariables.audioSampleRate=24000; + if (binding.audio48kRadioButton.isChecked()) GeneralVariables.audioSampleRate=48000; + writeConfig("audioRate", String.valueOf(GeneralVariables.audioSampleRate)); + } + }; + + binding.audio12kRadioButton.setOnClickListener(listener); + binding.audio24kRadioButton.setOnClickListener(listener); + binding.audio48kRadioButton.setOnClickListener(listener); + + } + + + /** * 设置控制模式VOX CAT */ @@ -897,66 +980,54 @@ public class ConfigFragment extends Fragment { binding.callsignHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "callsign.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "callsign_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.callsign_help) + , true).show(); } }); //梅登海德网格的帮助 binding.maidenGridImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "maidenhead.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "maidenhead_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.maidenhead_help) + , true).show(); } }); //发射频率的帮助 binding.frequencyImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "frequency.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "frequency_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.frequency_help) + , true).show(); } }); //延迟发射帮助 binding.transDelayImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "transDelay.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "transDelay_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.transDelay_help) + , true).show(); } }); //时间偏移帮助 binding.timeOffsetImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "timeoffset.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "timeoffset_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.timeoffset_help) + , true).show(); } }); //PTT延时帮助 binding.pttDelayImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "pttdelay.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "pttdelay_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.pttdelay_help) + , true).show(); } }); //设置ABOUT @@ -970,110 +1041,110 @@ public class ConfigFragment extends Fragment { binding.operationHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "operationBand.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "operationBand_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.operationBand_help) + , true).show(); } }); //设置操作模式 binding.controlModeHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "controlMode.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "controlMode_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.controlMode_help) + , true).show(); } }); //设置CI-V地址和波特率帮助 binding.baudRateHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "civ_help.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "civ_help_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.civ_help) + , true).show(); } }); //电台型号列表 binding.rigNameHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "rig_model_help.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "rig_model_help_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.rig_model_help) + , true).show(); } }); //发射监管 binding.launchSupervisionImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "launch_supervision_help.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "launch_supervision_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.launch_supervision_help) + , true).show(); } }); //无回应次数 binding.noResponseCountButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "no_response_help.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "no_response_help_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.no_response_help) + , true).show(); } }); //自动呼叫 binding.autoFollowCountButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "auto_follow_help.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "auto_follow_help_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.auto_follow_help) + , true).show(); } }); //连接模式 binding.connectModeHelpImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "connectMode.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "connectMode_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.connectMode_help) + , true).show(); } }); //排除选项 binding.excludedHelpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "excludeCallsign.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "excludeCallsign_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.excludeCallsign_help) + , true).show(); } }); binding.swlHelpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "swlMode.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "swlMode_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.swlMode_help) + , true).show(); + } + }); + + //解码模式 + binding.decodeModeHelpButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(),requireActivity() + ,GeneralVariables.getStringFromResource(R.string.deep_mode_help) + ,true).show(); + } + }); + + //音频输出帮助 + binding.audioOutputImageButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.audio_output_help) + , true).show(); } }); @@ -1081,11 +1152,9 @@ public class ConfigFragment extends Fragment { binding.clearCacheHelpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (GeneralVariables.isChina) { - new HelpDialog(requireContext(), requireActivity(), "clear_cache_data.txt", true).show(); - } else { - new HelpDialog(requireContext(), requireActivity(), "clear_cache_data_en.txt", true).show(); - } + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.clear_cache_data_help) + , true).show(); } }); binding.clearFollowButton.setOnClickListener(new View.OnClickListener() { diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FilterDialog.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FlexMeterRulerView.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FreqDialog.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/FunctionOrderSpinnerAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/HelpDialog.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LaunchSupervisionSpinnerAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java old mode 100755 new mode 100644 index e27c71e..ad1ab12 --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LogFragment.java @@ -1,6 +1,7 @@ package com.bg7yoz.ft8cn.ui; /** * 通联纪录的主界面。 + * * @author BGY70Z * @date 2023-03-20 */ @@ -65,7 +66,7 @@ public class LogFragment extends Fragment { private LogCallsignAdapter logCallsignAdapter; private LogQSLAdapter logQSLAdapter; - private boolean loading=false;//防止滑动触发多次查询 + private boolean loading = false;//防止滑动触发多次查询 private int lastItemPosition; @@ -113,12 +114,12 @@ public class LogFragment extends Fragment { }); binding.inputMycallEdit.setText(mainViewModel.queryKey); - queryByCallsign(mainViewModel.queryKey,0); + queryByCallsign(mainViewModel.queryKey, 0); mainViewModel.mutableQueryFilter.observe(getViewLifecycleOwner(), new Observer() { @Override public void onChanged(Integer integer) { - queryByCallsign(mainViewModel.queryKey,0); + queryByCallsign(mainViewModel.queryKey, 0); } }); @@ -137,7 +138,7 @@ public class LogFragment extends Fragment { @Override public void afterTextChanged(Editable editable) { mainViewModel.queryKey = editable.toString(); - queryByCallsign(mainViewModel.queryKey,0); + queryByCallsign(mainViewModel.queryKey, 0); } }); @@ -153,10 +154,16 @@ public class LogFragment extends Fragment { binding.exportImageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - new HelpDialog(requireContext(), requireActivity() - , String.format(GeneralVariables.getStringFromResource(R.string.export_info) - , getLocalIp(), LogHttpServer.DEFAULT_PORT) - , false).show(); + if (getLocalIp()==null) { + new HelpDialog(requireContext(), requireActivity() + , GeneralVariables.getStringFromResource(R.string.export_null) + ,false).show(); + }else { + new HelpDialog(requireContext(), requireActivity() + , String.format(GeneralVariables.getStringFromResource(R.string.export_info) + , getLocalIp(), LogHttpServer.DEFAULT_PORT) + , false).show(); + } } }); @@ -166,7 +173,7 @@ public class LogFragment extends Fragment { public void onClick(View view) { mainViewModel.logListShowCallsign = !mainViewModel.logListShowCallsign; setShowStyle(); - queryByCallsign(binding.inputMycallEdit.getText().toString(),0);//偏移量0,就是重新查询 + queryByCallsign(binding.inputMycallEdit.getText().toString(), 0);//偏移量0,就是重新查询 } }); @@ -176,7 +183,7 @@ public class LogFragment extends Fragment { public void onClick(View view) { Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); intent.putExtra("qslAll", mainViewModel.queryKey); - intent.putExtra("queryFilter",mainViewModel.queryFilter); + intent.putExtra("queryFilter", mainViewModel.queryFilter); startActivity(intent); } }); @@ -209,6 +216,8 @@ public class LogFragment extends Fragment { break; case 3: Intent intent = new Intent(requireContext(), GridTrackerMainActivity.class); + //ArrayList records=new ArrayList<>(); + //records.add(logQSLAdapter.getRecord(position)); intent.putExtra("qslList", logQSLAdapter.getRecord(position)); startActivity(intent); break; @@ -222,6 +231,7 @@ public class LogFragment extends Fragment { return super.onContextItemSelected(item); } + private boolean itemIsOnScreen(View view) { if (view != null) { int width = view.getWidth(); @@ -232,15 +242,20 @@ public class LogFragment extends Fragment { return false; } - private void loadQueryData(RecyclerView recyclerView){ - if ((!loading)) { - if (mainViewModel.logListShowCallsign){ - queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); - }else { - queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); + private void loadQueryData(RecyclerView recyclerView) { +// if ((!loading)&&(recyclerView.computeVerticalScrollRange() +// - recyclerView.computeVerticalScrollExtent() +// - recyclerView.computeVerticalScrollOffset() < 100)) { + if ((!loading)) { + //ToastMessage.show("查询"); + if (mainViewModel.logListShowCallsign) { + queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); + } else { + queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); } } } + /** * 设置列表滑动动作 */ @@ -252,13 +267,13 @@ public class LogFragment extends Fragment { super.onScrollStateChanged(recyclerView, newState); int itemCount; - if (mainViewModel.logListShowCallsign){ - itemCount=logCallsignAdapter.getItemCount(); - }else { - itemCount=logQSLAdapter.getItemCount(); + if (mainViewModel.logListShowCallsign) { + itemCount = logCallsignAdapter.getItemCount(); + } else { + itemCount = logQSLAdapter.getItemCount(); } if (newState == SCROLL_STATE_IDLE && - lastItemPosition == itemCount){ + lastItemPosition == itemCount) { loadQueryData(recyclerView); } @@ -268,14 +283,28 @@ public class LogFragment extends Fragment { public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); - if (layoutManager instanceof LinearLayoutManager){ + if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager manager = (LinearLayoutManager) layoutManager; int firstVisibleItem = manager.findFirstVisibleItemPosition(); int l = manager.findLastCompletelyVisibleItemPosition(); - lastItemPosition = firstVisibleItem+(l-firstVisibleItem)+1; + lastItemPosition = firstVisibleItem + (l - firstVisibleItem) + 1; } +// if (dy>0){//列表向上移动 +// loadQueryData(recyclerView); +// } + //当列表上滑,接近底部时,开始查询 +// if ((!loading)&&(dy>0)&&(recyclerView.computeVerticalScrollRange() +// - recyclerView.computeVerticalScrollExtent() +// - recyclerView.computeVerticalScrollOffset() < 100)) { +// ToastMessage.show("查询"); +// if (mainViewModel.logListShowCallsign){ +// queryByCallsign(mainViewModel.queryKey, logCallsignAdapter.getItemCount()); +// }else { +// queryByCallsign(mainViewModel.queryKey, logQSLAdapter.getItemCount()); +// } +// } } }); @@ -425,15 +454,15 @@ public class LogFragment extends Fragment { * * @param callsign 呼号 */ - private void queryByCallsign(String callsign,int offset) { - loading=true;//开始读数据 + private void queryByCallsign(String callsign, int offset) { + loading = true;//开始读数据 //分两种查询 if (mainViewModel.logListShowCallsign) { - if (offset==0) {//说明是新增记录 + if (offset == 0) {//说明是新增记录 logCallsignAdapter.clearRecords();//清空记录 } - mainViewModel.databaseOpr.getQSLCallsignsByCallsign(false,offset,callsign, mainViewModel.queryFilter + mainViewModel.databaseOpr.getQSLCallsignsByCallsign(false, offset, callsign, mainViewModel.queryFilter , new OnQueryQSLCallsign() { @Override public void afterQuery(ArrayList records) { @@ -441,16 +470,16 @@ public class LogFragment extends Fragment { @Override public void run() { logCallsignAdapter.setQSLCallsignList(records); - loading=false; + loading = false; } }); } }); } else { - if (offset==0){//说明是新增记录 + if (offset == 0) {//说明是新增记录 logQSLAdapter.clearRecords(); } - mainViewModel.databaseOpr.getQSLRecordByCallsign(false,offset,callsign, mainViewModel.queryFilter + mainViewModel.databaseOpr.getQSLRecordByCallsign(false, offset, callsign, mainViewModel.queryFilter , new OnQueryQSLRecordCallsign() { @Override public void afterQuery(ArrayList records) { @@ -458,7 +487,7 @@ public class LogFragment extends Fragment { @Override public void run() { logQSLAdapter.setQSLList(records); - loading=false; + loading = false; } }); } diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/LoginIcomRadioDialog.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/NoReplyLimitSpinnerAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/PttDelaySpinnerAdapter.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/QRZ_Fragment.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/RulerFrequencyView.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectBluetoothDialog.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SelectFlexRadioDialog.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumFragment.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java old mode 100755 new mode 100644 index 84ea2b3..6dbd23c --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/SpectrumView.java @@ -72,8 +72,6 @@ public class SpectrumView extends ConstraintLayout { mainViewModel.currentMessages=null; - - //原始频谱开关 controlDeNoiseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -105,7 +103,7 @@ public class SpectrumView extends ConstraintLayout { mainViewModel.mutableIsDecoding.observe(fragment.getViewLifecycleOwner(), new Observer() { @Override public void onChanged(Boolean aBoolean) { - waterfallView.setDrawMessage(!aBoolean);//false说明解码完毕 + waterfallView.setDrawMessage(!aBoolean);//aBoolean==false说明解码完毕 } }); @@ -121,7 +119,6 @@ public class SpectrumView extends ConstraintLayout { columnarView.setTouch_x(Math.round(motionEvent.getX())); - if (!mainViewModel.ft8TransmitSignal.isSynFrequency() && (waterfallView.getFreq_hz() > 0) && (motionEvent.getAction() == ACTION_UP) @@ -132,7 +129,6 @@ public class SpectrumView extends ConstraintLayout { mainViewModel.ft8TransmitSignal.setBaseFrequency( (float) waterfallView.getFreq_hz()); - rulerFrequencyView.setFreq(waterfallView.getFreq_hz()); fragment.requireActivity().runOnUiThread(new Runnable() { diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/ToastMessage.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/UtcOffsetSpinnerAdapter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/UtcOffsetSpinnerAdapter.java old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 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 old mode 100755 new mode 100644 index 93b243d..a0fc9ed --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/ui/WaterfallView.java @@ -1,6 +1,7 @@ package com.bg7yoz.ft8cn.ui; /** * 瀑布图自定义控件。 + * * @author BGY70Z * @date 2023-03-20 */ @@ -19,12 +20,17 @@ import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.bg7yoz.ft8cn.Ft8Message; import com.bg7yoz.ft8cn.timer.UtcTimer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.ListIterator; public class WaterfallView extends View { private int blockHeight = 2;//色块高度 @@ -60,6 +66,8 @@ public class WaterfallView extends View { public WaterfallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } + ArrayList messages= new ArrayList<>(); + /** * 把dp值转换为像素点 @@ -97,7 +105,7 @@ public class WaterfallView extends View { fontPaint.setDither(true); fontPaint.setTextAlign(Paint.Align.LEFT); - // messagePaint = new Paint(); + // messagePaint = new Paint(); messagePaint.setTextSize(dpToPixel(11)); messagePaint.setColor(0xff00ffff); messagePaint.setAntiAlias(true); @@ -176,7 +184,10 @@ public class WaterfallView extends View { invalidate(); } - public void setWaveData(int[] data, int sequential, List messages) { + public void setWaveData(int[] data, int sequential, List msgs) { + if (drawMessage&& msgs!=null){//把需要画的消息复制出来防止多线程访问冲突 + messages=new ArrayList<>(msgs); + } if (data == null) { return; @@ -232,19 +243,7 @@ public class WaterfallView extends View { fontPaint.setTextAlign(Paint.Align.LEFT); for (Ft8Message msg : messages) { -// if (GeneralVariables.checkQSLCallsign(msg.getCallsignFrom())) {//如果在数据库中,划线 -// messagePaint.setStrikeThruText(true); -// messagePaint.setUnderlineText(true); -// //messagePaint.setFlags(messagePaint.getFlags() | Paint.STRIKE_THRU_TEXT_FLAG); -// } else {//如果不在数据库中,去掉划线 -// messagePaint.setStrikeThruText(false); -// messagePaint.setUnderlineText(false); -// //messagePaint.setFlags(messagePaint.getFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); -// } - - if (msg.inMyCall()) {//与我有关 - //messagePaint.setColor(0xffFF0202); messagePaint.setColor(0xffffb2b2); } else if (msg.checkIsCQ()) {//CQ messagePaint.setColor(0xffeeee00); @@ -252,18 +251,20 @@ public class WaterfallView extends View { messagePaint.setColor(0xff00ffff); } Path path = new Path(); + path.moveTo(msg.freq_hz * freq_width, pathStart); path.lineTo(msg.freq_hz * freq_width, pathEnd); - _canvas.drawTextOnPath(msg.getMessageText(), path + _canvas.drawTextOnPath(msg.getMessageText(true), path , 0, 0, messagePaintBack);//消息背景 - _canvas.drawTextOnPath(msg.getMessageText(), path + _canvas.drawTextOnPath(msg.getMessageText(true), path , 0, 0, messagePaint);//消息 } } + } public void setTouch_x(int touch_x) { 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 old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/MicRecorder.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnAudioRecorded.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnGetVoiceDataDone.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnHamRecord.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/OnVoiceMonitorChanged.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveAccess.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveConstants.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileReader.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java old mode 100755 new mode 100644 index ade3750..dcc46bd --- a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java +++ b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WaveFileWriter.java @@ -87,7 +87,7 @@ public class WaveFileWriter { } public void close() { - // TODO document why this method is empty + } private void writeString(String str, int len) { diff --git a/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java b/ft8CN/app/src/main/java/com/bg7yoz/ft8cn/wave/WriteWavHeader.java old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/anim/click_button.xml b/ft8CN/app/src/main/res/anim/click_button.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/anim/view_blink.xml b/ft8CN/app/src/main/res/anim/view_blink.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/color/imagebutton_click_selector.xml b/ft8CN/app/src/main/res/color/imagebutton_click_selector.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/color/switch_color_style.xml b/ft8CN/app/src/main/res/color/switch_color_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/baseline_location_on_24.xml b/ft8CN/app/src/main/res/drawable-night/baseline_location_on_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48 2.xml b/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48 2.xml deleted file mode 100755 index 46b3d83..0000000 --- a/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48.xml b/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24 2.xml b/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24 2.xml deleted file mode 100755 index 32ebe76..0000000 --- a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24.xml b/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24 2.xml b/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24 2.xml deleted file mode 100755 index a893e20..0000000 --- a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24.xml b/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/cqzone.png b/ft8CN/app/src/main/res/drawable-night/cqzone.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/cqzone_b 2.png b/ft8CN/app/src/main/res/drawable-night/cqzone_b 2.png deleted file mode 100755 index b4a4dfe..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/cqzone_b 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/cqzone_b.png b/ft8CN/app/src/main/res/drawable-night/cqzone_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/dxcc 2.png b/ft8CN/app/src/main/res/drawable-night/dxcc 2.png deleted file mode 100755 index 6aa14e8..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/dxcc 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/dxcc.png b/ft8CN/app/src/main/res/drawable-night/dxcc.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/dxcc_b 2.png b/ft8CN/app/src/main/res/drawable-night/dxcc_b 2.png deleted file mode 100755 index f436b64..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/dxcc_b 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/dxcc_b.png b/ft8CN/app/src/main/res/drawable-night/dxcc_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/guohe_logo 2.png b/ft8CN/app/src/main/res/drawable-night/guohe_logo 2.png deleted file mode 100755 index a80382f..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/guohe_logo 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/guohe_logo.png b/ft8CN/app/src/main/res/drawable-night/guohe_logo.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24 2.xml deleted file mode 100755 index 84527a7..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24 2.xml deleted file mode 100755 index f753515..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off 2.xml deleted file mode 100755 index f926e9a..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off 2.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48 2.xml deleted file mode 100755 index 76faa78..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32 2.xml deleted file mode 100755 index a880088..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24 2.xml deleted file mode 100755 index a99f4b1..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_info_32.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_info_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_insert_chart_outlined_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_insert_chart_outlined_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_link_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_link_24 2.xml deleted file mode 100755 index 57f63f7..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_link_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_link_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_link_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24 2.xml deleted file mode 100755 index a8087d4..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_mic_off_48.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_mic_off_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32 2.xml deleted file mode 100755 index 0241208..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_pause_circle_outline_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_pause_circle_outline_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24 2.xml deleted file mode 100755 index a534888..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48 2.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48 2.xml deleted file mode 100755 index 461a25c..0000000 --- a/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48 2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/ic_baseline_settings_bluetooth_24.xml b/ft8CN/app/src/main/res/drawable-night/ic_baseline_settings_bluetooth_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/itu 2.png b/ft8CN/app/src/main/res/drawable-night/itu 2.png deleted file mode 100755 index d76a030..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/itu 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/itu.png b/ft8CN/app/src/main/res/drawable-night/itu.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/itu_b 2.png b/ft8CN/app/src/main/res/drawable-night/itu_b 2.png deleted file mode 100755 index 1bce90b..0000000 Binary files a/ft8CN/app/src/main/res/drawable-night/itu_b 2.png and /dev/null differ diff --git a/ft8CN/app/src/main/res/drawable-night/itu_b.png b/ft8CN/app/src/main/res/drawable-night/itu_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable-night/spinner_style 2.xml b/ft8CN/app/src/main/res/drawable-night/spinner_style 2.xml deleted file mode 100755 index f3e99f0..0000000 --- a/ft8CN/app/src/main/res/drawable-night/spinner_style 2.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ft8CN/app/src/main/res/drawable-night/spinner_style.xml b/ft8CN/app/src/main/res/drawable-night/spinner_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/baseline_location_on_24.xml b/ft8CN/app/src/main/res/drawable/baseline_location_on_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/calling_list_cell_0_style.xml b/ft8CN/app/src/main/res/drawable/calling_list_cell_0_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/calling_list_cell_1_style.xml b/ft8CN/app/src/main/res/drawable/calling_list_cell_1_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/calling_list_cell_2_style.xml b/ft8CN/app/src/main/res/drawable/calling_list_cell_2_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/calling_list_cell_3_style.xml b/ft8CN/app/src/main/res/drawable/calling_list_cell_3_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/calling_list_cell_style.xml b/ft8CN/app/src/main/res/drawable/calling_list_cell_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/clear_calling_list_image_48.xml b/ft8CN/app/src/main/res/drawable/clear_calling_list_image_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_down_24.xml b/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_down_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_up_24.xml b/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_up_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/count_detail_item_0_style.xml b/ft8CN/app/src/main/res/drawable/count_detail_item_0_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/count_detail_item_1_style.xml b/ft8CN/app/src/main/res/drawable/count_detail_item_1_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/cq.png b/ft8CN/app/src/main/res/drawable/cq.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/cqzone.png b/ft8CN/app/src/main/res/drawable/cqzone.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/cqzone_b.png b/ft8CN/app/src/main/res/drawable/cqzone_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/debug_message_style.xml b/ft8CN/app/src/main/res/drawable/debug_message_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/dxcc.png b/ft8CN/app/src/main/res/drawable/dxcc.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/dxcc_b.png b/ft8CN/app/src/main/res/drawable/dxcc_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/editor_layout_background_style.xml b/ft8CN/app/src/main/res/drawable/editor_layout_background_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/editor_layout_style.xml b/ft8CN/app/src/main/res/drawable/editor_layout_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/editor_style.xml b/ft8CN/app/src/main/res/drawable/editor_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/error_editor_style.xml b/ft8CN/app/src/main/res/drawable/error_editor_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/flex_config_style.xml b/ft8CN/app/src/main/res/drawable/flex_config_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/flex_icon.png b/ft8CN/app/src/main/res/drawable/flex_icon.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/flex_meter_ruler_style.xml b/ft8CN/app/src/main/res/drawable/flex_meter_ruler_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/flex_meter_style.xml b/ft8CN/app/src/main/res/drawable/flex_meter_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/flexradio.png b/ft8CN/app/src/main/res/drawable/flexradio.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/float_button_style.xml b/ft8CN/app/src/main/res/drawable/float_button_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ft8cn_icon.png b/ft8CN/app/src/main/res/drawable/ft8cn_icon.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/guohe_logo.png b/ft8CN/app/src/main/res/drawable/guohe_logo.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/help_dialog_style.xml b/ft8CN/app/src/main/res/drawable/help_dialog_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_ind_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_ind_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_campaign_transmitting_red_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_campaign_transmitting_red_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_cancel_schedule_send_off.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_cancel_schedule_send_off.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_check_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_check_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_check_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_check_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_chevron_left_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_chevron_left_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_chevron_right_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_chevron_right_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_close_32.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_close_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_cq_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_cq_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_cq_qso_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_cq_qso_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_freq_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_freq_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_fullscreen_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_fullscreen_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_fullscreen_exit_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_fullscreen_exit_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_grid_tracker_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_grid_tracker_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_headset_mic_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_headset_mic_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_help_outline_32.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_help_outline_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_history_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_history_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_info_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_info_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_info_32.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_info_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_info_white_32.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_info_white_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_insert_chart_outlined_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_insert_chart_outlined_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_library_add_check_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_library_add_check_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_link_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_link_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_list_alt_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_list_alt_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_mic_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_mic_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_mic_off_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_mic_off_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_mic_red_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_mic_red_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_nav_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_nav_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_not_listed_location_32.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_not_listed_location_32.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_pause_circle_outline_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_pause_circle_outline_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_pause_disable_circle_outline_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_pause_disable_circle_outline_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_save_alt_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_save_alt_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_send_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_send_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_send_red_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_send_red_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_send_white_48.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_send_white_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_settings_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_settings_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_settings_bluetooth_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_settings_bluetooth_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_signal_cellular_4_bar_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_signal_cellular_4_bar_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_tracker_close_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_tracker_close_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_tracker_settings_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_tracker_settings_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_visibility_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_visibility_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_volume_up_24.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_volume_up_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_baseline_wifi_tethering_16.xml b/ft8CN/app/src/main/res/drawable/ic_baseline_wifi_tethering_16.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_launcher_background.xml b/ft8CN/app/src/main/res/drawable/ic_launcher_background.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_launcher_foreground.xml b/ft8CN/app/src/main/res/drawable/ic_launcher_foreground.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/ic_nav_spectrum_24.xml b/ft8CN/app/src/main/res/drawable/ic_nav_spectrum_24.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/icon_background_style.xml b/ft8CN/app/src/main/res/drawable/icon_background_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/imagebutton_selected_style.xml b/ft8CN/app/src/main/res/drawable/imagebutton_selected_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/imagebutton_style.xml b/ft8CN/app/src/main/res/drawable/imagebutton_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/imagebutton_transparent_style.xml b/ft8CN/app/src/main/res/drawable/imagebutton_transparent_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/is_weak_signal_24.xml b/ft8CN/app/src/main/res/drawable/is_weak_signal_24.xml new file mode 100644 index 0000000..1872318 --- /dev/null +++ b/ft8CN/app/src/main/res/drawable/is_weak_signal_24.xml @@ -0,0 +1,6 @@ + + + diff --git a/ft8CN/app/src/main/res/drawable/itu.png b/ft8CN/app/src/main/res/drawable/itu.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/itu_b.png b/ft8CN/app/src/main/res/drawable/itu_b.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/log_callsign_editor_style.xml b/ft8CN/app/src/main/res/drawable/log_callsign_editor_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/log_item_delete_icon.xml b/ft8CN/app/src/main/res/drawable/log_item_delete_icon.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/logo_background_style.xml b/ft8CN/app/src/main/res/drawable/logo_background_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/nav_calling_list_image.xml b/ft8CN/app/src/main/res/drawable/nav_calling_list_image.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/nav_my_calling_image.xml b/ft8CN/app/src/main/res/drawable/nav_my_calling_image.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/newyear.png b/ft8CN/app/src/main/res/drawable/newyear.png old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/run_decode_image_48.xml b/ft8CN/app/src/main/res/drawable/run_decode_image_48.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/select_bluetooth_dialog_style.xml b/ft8CN/app/src/main/res/drawable/select_bluetooth_dialog_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/select_serial_port_item_style.xml b/ft8CN/app/src/main/res/drawable/select_serial_port_item_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/select_serial_port_layout_style.xml b/ft8CN/app/src/main/res/drawable/select_serial_port_layout_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/speaker.xml b/ft8CN/app/src/main/res/drawable/speaker.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/spectrum_text_back_ground.xml b/ft8CN/app/src/main/res/drawable/spectrum_text_back_ground.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/spinner_style.xml b/ft8CN/app/src/main/res/drawable/spinner_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_calling_list_background.xml b/ft8CN/app/src/main/res/drawable/tracker_calling_list_background.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_config_bar_style.xml b/ft8CN/app/src/main/res/drawable/tracker_config_bar_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_grid_info_win_style.xml b/ft8CN/app/src/main/res/drawable/tracker_grid_info_win_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_new_cq_info_win_style.xml b/ft8CN/app/src/main/res/drawable/tracker_new_cq_info_win_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_qsl_sample_style.xml b/ft8CN/app/src/main/res/drawable/tracker_qsl_sample_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_qso_sample_style.xml b/ft8CN/app/src/main/res/drawable/tracker_qso_sample_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_qsx_sample_style.xml b/ft8CN/app/src/main/res/drawable/tracker_qsx_sample_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_sample_cq_bar_style.xml b/ft8CN/app/src/main/res/drawable/tracker_sample_cq_bar_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/tracker_tips_radio_style.xml b/ft8CN/app/src/main/res/drawable/tracker_tips_radio_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/drawable/welcom_layout_style.xml b/ft8CN/app/src/main/res/drawable/welcom_layout_style.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/fragment_calling_list.xml b/ft8CN/app/src/main/res/layout-land/fragment_calling_list.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/fragment_flex_radio_info.xml b/ft8CN/app/src/main/res/layout-land/fragment_flex_radio_info.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/fragment_my_calling.xml b/ft8CN/app/src/main/res/layout-land/fragment_my_calling.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/log_callsign_holder_item.xml b/ft8CN/app/src/main/res/layout-land/log_callsign_holder_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/log_qsl_holder_item.xml b/ft8CN/app/src/main/res/layout-land/log_qsl_holder_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout-land/main_activity.xml b/ft8CN/app/src/main/res/layout-land/main_activity.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/activity_faqactivity.xml b/ft8CN/app/src/main/res/layout/activity_faqactivity.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/activity_grid_tracker_main.xml b/ft8CN/app/src/main/res/layout/activity_grid_tracker_main.xml old mode 100755 new mode 100644 index d0d2789..1cf90e9 --- a/ft8CN/app/src/main/res/layout/activity_grid_tracker_main.xml +++ b/ft8CN/app/src/main/res/layout/activity_grid_tracker_main.xml @@ -69,6 +69,29 @@ android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> + + + + + + + \ No newline at end of file diff --git a/ft8CN/app/src/main/res/layout/clear_cache_dialog_layout.xml b/ft8CN/app/src/main/res/layout/clear_cache_dialog_layout.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/count_detail_item.xml b/ft8CN/app/src/main/res/layout/count_detail_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/count_info_bar_item.xml b/ft8CN/app/src/main/res/layout/count_info_bar_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/count_info_none_item.xml b/ft8CN/app/src/main/res/layout/count_info_none_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/count_info_pie_item.xml b/ft8CN/app/src/main/res/layout/count_info_pie_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/filter_dialog_layout.xml b/ft8CN/app/src/main/res/layout/filter_dialog_layout.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/flex_device_list_item.xml b/ft8CN/app/src/main/res/layout/flex_device_list_item.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/fragment_calling_list.xml b/ft8CN/app/src/main/res/layout/fragment_calling_list.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/layout/fragment_config.xml b/ft8CN/app/src/main/res/layout/fragment_config.xml old mode 100755 new mode 100644 index 4c9d64c..46c4210 --- a/ft8CN/app/src/main/res/layout/fragment_config.xml +++ b/ft8CN/app/src/main/res/layout/fragment_config.xml @@ -345,6 +345,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -880,6 +1044,77 @@ + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/decodeModeLayout"> - + app:layout_constraintTop_toBottomOf="@+id/audioOutputLayout"> Κλήση Αρχείο επαφών Ρυθμίσεις - - - \'Εναρξη αποκωδικοποίησης dB Δt @@ -173,7 +170,9 @@ Χρόνος έναρξης : %s Χρόνος τερματισμού : %s Rst που ελήφθη : %s + Rst που ελήφθη Rst που στάλθηξε : %s + Rst που στάλθηξε Μπάντα : %s Συχν : %s Mode : %s @@ -212,7 +211,9 @@ Λειτουργία Διαγραφή Χρόνος έναρξης QSO + Ημερομηνία έναρξης QSO Χρόνος τερματισμού QSO + Ημερομηνία λήξης QSO Mode Grid Μπάντα @@ -427,7 +428,51 @@ Export to ADIF file Total number of QSO decoded QSOs decoded - + Audio output + 16-bit int + 32-bit float + The number of bits in audio output + 12kHz + 24kHz + 48kHz + Sample rate + The sample rate of the audio output + Bit depth + callsign_en.txt + maidenhead_en.txt + frequency_en.txt + transDelay_en.txt + timeoffset_en.txt + pttdelay_en.txt + operationBand_en.txt + controlMode_en.txt + civ_help_en.txt + rig_model_help_en.txt + launch_supervision_en.txt + no_response_help_en.txt + auto_follow_help_en.txt + connectMode_en.txt + excludeCallsign_en.txt + swlMode_en.txt + audio_output_help_en.txt + clear_cache_data_en.txt + Query the logs + All + Data sources + Unconfirmed + Confirmed + Local logs + External logs + External + Local log + Πρωτόκολλο + Comment + Λειτουργία αποκωδικοποίησης + Γρήγορη λειτουργία + Λειτουργία πολλαπλής αποκωδικοποίησης + Παρακαλώ χρησιμοποιήστε τον web browser των άλλων συσκευών για εξαγωγή στο παρασκήνιο στο ίδιο LAN που είναι αυτή η συσκευή.\nPlease connect to a valid Wi-Fi + decode_help_en.txt + \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-es/strings.xml b/ft8CN/app/src/main/res/values-es/strings.xml old mode 100755 new mode 100644 index 80aac66..9b6ec62 --- a/ft8CN/app/src/main/res/values-es/strings.xml +++ b/ft8CN/app/src/main/res/values-es/strings.xml @@ -5,9 +5,6 @@ Calling QSO Logs Ajustes - - - Empezar a decodificar dB Δt @@ -173,7 +170,9 @@ Hora de inicio : %s Hora de finalización: %s Rst recibido : %s + Rst recibido Rst enviado: %s + Rst enviado Banda : %s Freq : %s Modo : %s @@ -212,7 +211,9 @@ Operación Borrar QSO hora de inicio + Fecha de inicio del qso QSO hora de finalización + Fecha de finalización del qso Modo Grid Banda @@ -427,7 +428,50 @@ Export to ADIF file Total number of QSO decoded QSOs decoded - + Audio output + 16-bit int + 32-bit float + The number of bits in audio output + 12kHz + 24kHz + 48kHz + Sample rate + The sample rate of the audio output + Bit depth + callsign_en.txt + maidenhead_en.txt + frequency_en.txt + transDelay_en.txt + timeoffset_en.txt + pttdelay_en.txt + operationBand_en.txt + controlMode_en.txt + civ_help_en.txt + rig_model_help_en.txt + launch_supervision_en.txt + no_response_help_en.txt + auto_follow_help_en.txt + connectMode_en.txt + excludeCallsign_en.txt + swlMode_en.txt + audio_output_help_en.txt + clear_cache_data_en.txt + Query the logs + All + Data sources + Unconfirmed + Confirmed + Local logs + External logs + External + Local log + Acuerdo + Comment + Modo de decodificación + Modo rápido + Modo de decodificación múltiple + Utilice el navegador web de otros dispositivos para exportar desde el fondo en la misma LAN que este dispositivo.\nPlease connect to a valid Wi-Fi + decode_help_en.txt \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-ja/strings.xml b/ft8CN/app/src/main/res/values-ja/strings.xml old mode 100755 new mode 100644 index e88d0f3..0600511 --- a/ft8CN/app/src/main/res/values-ja/strings.xml +++ b/ft8CN/app/src/main/res/values-ja/strings.xml @@ -13,12 +13,6 @@ 設定 - - - - - - デコ—ドを開始 dB @@ -288,7 +282,7 @@ - %.0fキロメートル + %.0f km @@ -323,8 +317,10 @@ 終了時間:%s 自局RST:%s + 自局RST 相手局RST:%s + 相手局RST バンド:%s @@ -391,8 +387,10 @@ 削除 交信開始時間 + 交信開始日 交信終了時間 + 交信終了日 モード @@ -713,6 +711,50 @@ ADIFにエクスポート デコードされたQSO総件数 デコードされたQSO + 音声出力 + 16-bit int + 32-bit float + 音声出力中のビット数 + 12kHz + 24kHz + 48kHz + サンプルレート + 音声出力のサンプルレート + ビット深度 + callsign_en.txt + maidenhead_en.txt + frequency_en.txt + transDelay_en.txt + timeoffset_en.txt + pttdelay_en.txt + operationBand_en.txt + controlMode_en.txt + civ_help_en.txt + rig_model_help_en.txt + launch_supervision_en.txt + no_response_help_en.txt + auto_follow_help_en.txt + connectMode_en.txt + excludeCallsign_en.txt + swlMode_en.txt + audio_output_help_en.txt + clear_cache_data_en.txt + ログを検索 + 全部 + データソース + 未確認 + 確認済 + ローカルログ + 外部ログ + 外部 + ローカル + プロトコル + コメント + デコードモード + 高速 + ディープ + このデバイスと同じLANに接続し、ほかのデバイスでウェブブラウザーを利用してエクスポートしてください。\nブラウザーのアドレスバーに次の内容を入力してください:\n有効なWiFiに接続してください。 + decode_help_en.txt \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-night/colors.xml b/ft8CN/app/src/main/res/values-night/colors.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values-night/strings.xml b/ft8CN/app/src/main/res/values-night/strings.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values-night/themes.xml b/ft8CN/app/src/main/res/values-night/themes.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values-zh-rCN/strings.xml b/ft8CN/app/src/main/res/values-zh-rCN/strings.xml old mode 100755 new mode 100644 index fd650ae..0c20174 --- a/ft8CN/app/src/main/res/values-zh-rCN/strings.xml +++ b/ft8CN/app/src/main/res/values-zh-rCN/strings.xml @@ -6,8 +6,6 @@ 通联记录 设置 - - 开始解码 dB Δt @@ -172,7 +170,9 @@ 起始时间:%s 结束时间:%s 接收报告:%s + 接收报告 发送报告:%s + 发送报告 波长:%s 频率:%s 模式:%s @@ -209,6 +209,7 @@ 操作 删除 通联开始时间 + 通联开始日期 通联结束时间 模式 网格 @@ -425,6 +426,50 @@ 导出到ADIF文件 守听到的QSO数 SWL记录 + 音频输出 + 16位整型 + 32位浮点 + 音频输出位数 + 12kHz + 24kHz + 48kHz + 采样频率 + 音频输出采样率 + 位深 + callsign.txt + maidenhead.txt + frequency.txt + transDelay.txt + timeoffset.txt + pttdelay.txt + operationBand.txt + controlMode.txt + civ_help.txt + rig_model_help.txt + launch_supervision_help.txt + no_response_help.txt + auto_follow_help.txt + connectMode.txt + excludeCallsign.txt + swlMode.txt + audio_output_help.txt + clear_cache_data.txt + 查询日志 + 全部 + 数据来源 + 未确认 + 已确认 + 本地日志 + 外部日志 + 外部日志 + 本地日志 + 协议 + 备注 + 解码模式 + 快速解码 + 多次解码 + 请在与本机相同的局域网下,用其它设备的网页浏览器从后台导出。\n在浏览器的地址栏中输入如下内容:\n无法获取合适的IP地址,请连接到一个有效的Wifi。 + decode_help.txt \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-zh-rHK/strings.xml b/ft8CN/app/src/main/res/values-zh-rHK/strings.xml old mode 100755 new mode 100644 index d8cebe0..d4a9220 --- a/ft8CN/app/src/main/res/values-zh-rHK/strings.xml +++ b/ft8CN/app/src/main/res/values-zh-rHK/strings.xml @@ -6,8 +6,6 @@ 通聯記錄 設置 - - 開始解碼 dB Δt @@ -172,7 +170,9 @@ 起始時間:%s 結束時間:%s 接收報告:%s + 接收報告 發送報告:%s + 發送報告 頻段:%s 頻率:%s 模式:%s @@ -209,7 +209,9 @@ 操作 刪除 通聯開始時間 + 通聯開始日期 通聯結束時間 + 通聯結束日期 模式 網格 頻段 @@ -425,6 +427,50 @@ 導出到ADIF文件 收聽到的QSO數 SWL記錄 + 音頻輸出 + 16位整型 + 32位浮點 + 音頻輸出位數 + 12kHz + 24kHz + 48kHz + 采樣頻率 + 音頻輸出采樣率 + 位深 + callsign.txt + maidenhead.txt + frequency.txt + transDelay.txt + timeoffset.txt + pttdelay.txt + operationBand.txt + controlMode.txt + civ_help.txt + rig_model_help.txt + launch_supervision_help.txt + no_response_help.txt + auto_follow_help.txt + connectMode.txt + excludeCallsign.txt + swlMode.txt + audio_output_help.txt + clear_cache_data.txt + 查詢日誌 + 全部 + 數據來源 + 未確認 + 已確認 + 本地日誌 + 外部日誌 + 外部日誌 + 本地日誌 + 協定 + 備註 + 解碼模式 + 快速解碼 + 多次解碼 + 請在與本機相同的局域網下,用其它設備的網頁瀏覽器從後台導出。\n在瀏覽器的地址欄中輸入如下內容:\n無法獲取合適的IP地址,請連接到一個有效的Wifi。 + decode_help.txt \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-zh-rMO/strings.xml b/ft8CN/app/src/main/res/values-zh-rMO/strings.xml old mode 100755 new mode 100644 index 7f8e931..1d36dab --- a/ft8CN/app/src/main/res/values-zh-rMO/strings.xml +++ b/ft8CN/app/src/main/res/values-zh-rMO/strings.xml @@ -6,8 +6,6 @@ 通聯記錄 設置 - - 開始解碼 dB Δt @@ -172,7 +170,9 @@ 起始時間:%s 結束時間:%s 接收報告:%s + 接收報告 發送報告:%s + 發送報告 頻段:%s 頻率:%s 模式:%s @@ -209,7 +209,9 @@ 操作 刪除 通聯開始時間 + 通聯開始日期 通聯結束時間 + 通聯結束日期 模式 網格 頻段 @@ -425,5 +427,50 @@ 導出到ADIF文件 收聽到的QSO數 SWL記錄 + 音頻輸出 + 16位整型 + 32位浮點 + 音頻輸出位數 + 12kHz + 24kHz + 48kHz + 采樣頻率 + 音頻輸出采樣率 + 位深 + callsign.txt + maidenhead.txt + frequency.txt + transDelay.txt + timeoffset.txt + pttdelay.txt + operationBand.txt + controlMode.txt + civ_help.txt + rig_model_help.txt + launch_supervision_help.txt + no_response_help.txt + auto_follow_help.txt + connectMode.txt + excludeCallsign.txt + swlMode.txt + audio_output_help.txt + clear_cache_data.txt + 查詢日誌 + 全部 + 數據來源 + 未確認 + 已確認 + 本地日誌 + 外部日誌 + 外部日誌 + 本地日誌 + 協定 + 備註 + 解碼模式 + 快速解碼 + 多次解碼 + 請在與本機相同的局域網下,用其它設備的網頁瀏覽器從後台導出。\n在瀏覽器的地址欄中輸入如下內容:\n無法獲取合適的IP地址,請連接到一個有效的Wifi。 + decode_help.txt + \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values-zh-rTW/strings.xml b/ft8CN/app/src/main/res/values-zh-rTW/strings.xml old mode 100755 new mode 100644 index 114d409..b052a6b --- a/ft8CN/app/src/main/res/values-zh-rTW/strings.xml +++ b/ft8CN/app/src/main/res/values-zh-rTW/strings.xml @@ -6,8 +6,6 @@ 通聯記錄 設置 - - 開始解碼 dB Δt @@ -172,7 +170,9 @@ 起始時間:%s 結束時間:%s 接收報告:%s + 接收報告 發送報告:%s + 發送報告 頻段:%s 頻率:%s 模式:%s @@ -209,7 +209,9 @@ 操作 刪除 通聯開始時間 + 通聯開始日期 通聯結束時間 + 通聯結束日期 模式 網格 頻段 @@ -425,6 +427,50 @@ 導出到ADIF文件 收聽到的QSO數 SWL記錄 + 音頻輸出 + 16位整型 + 32位浮點 + 音頻輸出位數 + 12kHz + 24kHz + 48kHz + 采樣頻率 + 音頻輸出采樣率 + 位深 + callsign.txt + maidenhead.txt + frequency.txt + transDelay.txt + timeoffset.txt + pttdelay.txt + operationBand.txt + controlMode.txt + civ_help.txt + rig_model_help.txt + launch_supervision_help.txt + no_response_help.txt + auto_follow_help.txt + connectMode.txt + excludeCallsign.txt + swlMode.txt + audio_output_help.txt + clear_cache_data.txt + 查詢日誌 + 全部 + 數據來源 + 未確認 + 已確認 + 本地日誌 + 外部日誌 + 外部日誌 + 本地日誌 + 協定 + 備註 + 解碼模式 + 快速解碼 + 多次解碼 + 請在與本機相同的局域網下,用其它設備的網頁瀏覽器從後台導出。\n在瀏覽器的地址欄中輸入如下內容:\n無法獲取合適的IP地址,請連接到一個有效的Wifi。 + decode_help.txt \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values/colors.xml b/ft8CN/app/src/main/res/values/colors.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values/float_button_ids.xml b/ft8CN/app/src/main/res/values/float_button_ids.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values/strings.xml b/ft8CN/app/src/main/res/values/strings.xml old mode 100755 new mode 100644 index dfa5a02..8671633 --- a/ft8CN/app/src/main/res/values/strings.xml +++ b/ft8CN/app/src/main/res/values/strings.xml @@ -173,7 +173,9 @@ Start time : %s End time : %s Rst received : %s + Rst received Rst send : %s + Rst send Band : %s Freq : %s Mode : %s @@ -212,7 +214,9 @@ Operation Delete QSO start time + QSO start date QSO end time + QSO end date Mode Grid Band @@ -357,7 +361,6 @@ Warning ! SWR is too high . Signal output strength %.0f %% Signal output strength - Distance QTH of callsign Update time @@ -395,7 +398,6 @@ Band \t Total CQ Modifier Please input modifer - Start ATU Maximum Tx power to %d W ATU Tune power %d W @@ -429,6 +431,50 @@ Export to ADIF file Total number of QSO decoded QSOs decoded + Audio output + 16-bit int + 32-bit float + Audio - bit depth + 12kHz + 24kHz + 48kHz + Sample rate + Audio - sample rate + Bit depth + callsign_en.txt + maidenhead_en.txt + frequency_en.txt + transDelay_en.txt + timeoffset_en.txt + pttdelay_en.txt + operationBand_en.txt + controlMode_en.txt + civ_help_en.txt + rig_model_help_en.txt + launch_supervision_en.txt + no_response_help_en.txt + auto_follow_help_en.txt + connectMode_en.txt + excludeCallsign_en.txt + swlMode_en.txt + audio_output_help_en.txt + decode_help_en.txt + clear_cache_data_en.txt + Query the logs + All + Data sources + Unconfirmed + Confirmed + Local logs + External logs + External + Local log + Protocol + Comment + Decode mode + Fast decode + Deep decode + Export from WebUI via web browser on another device under the same LAN \n Please connect to a valid Wi-Fi \ No newline at end of file diff --git a/ft8CN/app/src/main/res/values/styles.xml b/ft8CN/app/src/main/res/values/styles.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/values/themes.xml b/ft8CN/app/src/main/res/values/themes.xml old mode 100755 new mode 100644 diff --git a/ft8CN/app/src/main/res/xml/device_filter.xml b/ft8CN/app/src/main/res/xml/device_filter.xml old mode 100755 new mode 100644 index 265ed83..dd21225 --- a/ft8CN/app/src/main/res/xml/device_filter.xml +++ b/ft8CN/app/src/main/res/xml/device_filter.xml @@ -39,4 +39,5 @@ + diff --git a/ft8CN/app/src/test/java/com/bg7yoz/ft8cn/ExampleUnitTest.java b/ft8CN/app/src/test/java/com/bg7yoz/ft8cn/ExampleUnitTest.java deleted file mode 100755 index 1ec875a..0000000 --- a/ft8CN/app/src/test/java/com/bg7yoz/ft8cn/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.bg7yoz.ft8cn; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/ft8CN/build.gradle b/ft8CN/build.gradle old mode 100755 new mode 100644 index a13cd5e..5908a23 --- a/ft8CN/build.gradle +++ b/ft8CN/build.gradle @@ -1,10 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.4.1' apply false - id 'com.android.library' version '7.4.1' apply false + id 'com.android.application' version '7.1.2' apply false + id 'com.android.library' version '7.1.2' apply false } ext { - + var3 = 'G:\\coding\\ft8CN\\ft8CN.jks' + var = var3 + var1 = '/Users/mymac/Desktop/coding/ft8CN/ft8CN.jks' + var2 = var } task clean(type: Delete) { delete rootProject.buildDir diff --git a/ft8CN/ft8cn_icon.png b/ft8CN/ft8cn_icon.png old mode 100755 new mode 100644 diff --git a/ft8CN/gradle.properties b/ft8CN/gradle.properties old mode 100755 new mode 100644 diff --git a/ft8CN/gradle/wrapper/gradle-wrapper.jar b/ft8CN/gradle/wrapper/gradle-wrapper.jar old mode 100755 new mode 100644 diff --git a/ft8CN/gradle/wrapper/gradle-wrapper.properties b/ft8CN/gradle/wrapper/gradle-wrapper.properties old mode 100755 new mode 100644 index 515d43a..e0c50f2 --- a/ft8CN/gradle/wrapper/gradle-wrapper.properties +++ b/ft8CN/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Mon Mar 20 22:46:09 CST 2023 +#Sat Jul 08 16:47:34 CST 2023 distributionBase=GRADLE_USER_HOME distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip distributionPath=wrapper/dists diff --git a/ft8CN/gradlew b/ft8CN/gradlew old mode 100755 new mode 100644 diff --git a/ft8CN/gradlew.bat b/ft8CN/gradlew.bat old mode 100755 new mode 100644 diff --git a/ft8CN/settings.gradle b/ft8CN/settings.gradle old mode 100755 new mode 100644