- Added QSO log importing WebUI
- Fixed map crash when importing large amount of logs (10k+)
- Optimized database structure to improve log importing speed and stability
- Fixed typo in translation
- Added support for UA3REO Wolf SDR
- Added support for GUOHE PMR-171
pull/66/head
wangg 2023-08-15 01:18:25 -04:00
rodzic d6e50da2e0
commit 824b4bdd00
482 zmienionych plików z 167529 dodań i 0 usunięć

15
ft8cn/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
ft8cn/.idea/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

Wyświetl plik

@ -0,0 +1 @@
Ft8CN

Wyświetl plik

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

Wyświetl plik

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

Wyświetl plik

@ -0,0 +1,13 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DanglingJavadoc" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IgnoreResultOfCall" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportAllNonLibraryCalls" value="false" />
<option name="callCheckString" value="java.io.File,.*,java.io.InputStream,read|skip|available|markSupported,java.io.Reader,read|skip|ready|markSupported,java.lang.AbstractStringBuilder,capacity|codePointAt|codePointBefore|codePointCount|indexOf|lastIndexOf|offsetByCodePoints|substring|subSequence,java.lang.Boolean,.*,java.lang.Byte,.*,java.lang.Character,.*,java.lang.Double,.*,java.lang.Float,.*,java.lang.Integer,.*,java.lang.Long,.*,java.lang.Math,.*,java.lang.Object,equals|hashCode|toString,java.lang.Short,.*,java.lang.StrictMath,.*,java.lang.String,.*,java.lang.Thread,interrupted,java.math.BigDecimal,.*,java.math.BigInteger,.*,java.net.InetAddress,.*,java.net.URI,.*,java.nio.channels.AsynchronousChannelGroup,.*,java.util.Arrays,.*,java.util.Collections,(?!addAll).*,java.util.List,of,java.util.Map,of|ofEntries|entry,java.util.Set,of,java.util.UUID,.*,java.util.concurrent.CountDownLatch,await|getCount,java.util.concurrent.ExecutorService,awaitTermination|isShutdown|isTerminated,java.util.concurrent.ForkJoinPool,awaitQuiescence,java.util.concurrent.Semaphore,tryAcquire|availablePermits|isFair|hasQueuedThreads|getQueueLength|getQueuedThreads,java.util.concurrent.locks.Condition,await|awaitNanos|awaitUntil,java.util.concurrent.locks.Lock,tryLock|newCondition,java.util.regex.Matcher,pattern|toMatchResult|start|end|group|groupCount|matches|find|lookingAt|quoteReplacement|replaceAll|replaceFirst|regionStart|regionEnd|hasTransparentBounds|hasAnchoringBounds|hitEnd|requireEnd,java.util.regex.Pattern,.*,java.util.stream.BaseStream,.*,java.util.stream.DoubleStream,.*,java.util.stream.IntStream,.*,java.util.stream.LongStream,.*,java.util.stream.Stream,.*" />
</inspection_tool>
<inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ADDITIONAL_TAGS" value="date" />
</inspection_tool>
</profile>
</component>

Wyświetl plik

@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/calling_list_cell_1_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/calling_list_cell_3_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/clear_calling_list_image_48.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_down_24.xml" value="0.1365" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/config_ic_baseline_keyboard_arrow_up_24.xml" value="0.1365" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/debug_message_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_assignment_ind_24.xml" value="0.179" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_cancel_schedule_send_off.xml" value="0.1785" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_check_48.xml" value="0.141" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_close_32.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_headset_mic_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_info_32.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_insert_chart_outlined_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_list_alt_24.xml" value="0.177" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_mic_off_48.xml" value="0.1655" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_not_listed_location_32.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_pause_circle_outline_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_play_circle_outline_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_save_alt_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_send_white_48.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/ic_baseline_settings_bluetooth_24.xml" value="0.1675" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/imagebutton_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/logo_background_style.xml" value="0.136" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/select_bluetooth_dialog_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-night/spinner_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-v24/imagebutton_selected_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable-v24/imagebutton_style.xml" value="0.114" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/calling_list_cell_0_style.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/calling_list_cell_1_style.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/calling_list_cell_2_style.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/calling_list_cell_3_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/calling_list_cell_style.xml" value="0.214" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/clear_calling_list_image_48.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_down_24.xml" value="0.1365" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/config_ic_baseline_keyboard_arrow_up_24.xml" value="0.1365" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/count_detail_item_1_style.xml" value="0.1725" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/count_detail_item_style.xml" value="0.1725" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/debug_message_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/editor_layout_background_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/editor_layout_style.xml" value="0.131" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/editor_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/help_dialog_style.xml" value="0.1305" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_assignment_ind_24.xml" value="0.179" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_cancel_schedule_send_off.xml" value="0.1045" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_cancel_schedule_send_off_48.xml" value="0.1235" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_check_24.xml" value="0.177" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_check_48.xml" value="0.1435" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_close_32.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_help_outline_32.xml" value="0.1305" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_history_24.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_info_32.xml" value="0.1245" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_insert_chart_outlined_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml" value="0.1475" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_library_add_check_24.xml" value="0.177" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_list_alt_24.xml" value="0.177" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_location_on_24.xml" value="0.1305" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_location_on_48.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_mic_48.xml" value="0.1655" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_mic_off_48.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_mic_red_48.xml" value="0.1655" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_not_listed_location_32.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_pause_circle_outline_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_pause_disable_circle_outline_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_play_circle_outline_24.xml" value="0.1665" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_save_alt_24.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_send_48.xml" value="0.1235" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_send_red_48.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_send_white_48.xml" value="0.155" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_settings_24.xml" value="0.1675" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_settings_bluetooth_24.xml" value="0.1675" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_baseline_wifi_tethering_16.xml" value="0.1355" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_launcher_background.xml" value="0.2375" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/ic_nav_spectrum_24.xml" value="0.179" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/icon_background_style.xml" value="0.148" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/imagebutton_selected_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/imagebutton_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/imagebutton_transparent_style.xml" value="0.2055" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/input_Layout_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/log_callsign_editor_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/log_item_delete_icon.xml" value="0.1905" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/logo_background_style.xml" value="0.124" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/nav_calling_list_image.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/nav_my_calling_image.xml" value="0.195" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/reset_cq_image.xml" value="0.181" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/run_decode_image.xml" value="0.1425" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/run_decode_image_48.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/select_bluetooth_dialog_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/select_serIal_port_background_layout_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/select_serial_port_item_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/selector_for_utc_offset_spinner.xml" value="0.135" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/speaker.xml" value="0.175" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/spinner_editor_style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/spinner_style.xml" value="0.119" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/swithc_theme.style.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/vd_vector.xml" value="0.121" />
<entry key="..\:/coding/ft8CN/app/src/main/res/drawable/welcom_layout_style.xml" value="0.124" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-land/fragment_calling_list.xml" value="0.20989583333333334" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-land/fragment_my_calling.xml" value="0.3828571428571429" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-land/log_callsign_holder_item.xml" value="0.5142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-land/log_qsl_holder_item.xml" value="0.42857142857142855" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-land/main_activity.xml" value="0.23324514991181658" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-sw600dp/fragment_my_calling.xml" value="0.15812841530054644" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-v23/call_list_holder_item.xml" value="0.42857142857142855" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-v23/count_info_item.xml" value="0.15572916666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout-v23/launch_supervision_spinner_item.xml" value="0.2760416666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/activity_blue_tooth_list.xml" value="0.3903985507246377" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/activity_faqactivity.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/bau_rate_spinner_item.xml" value="0.23596014492753623" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/bluetooth_device_list_item.xml" value="0.15572916666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/bluetooth_device_list_tem.xml" value="0.3903985507246377" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/call_list_holder_item.xml" value="0.7142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/count_detail_item.xml" value="0.4166666666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/count_info_bar_item.xml" value="0.15572916666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/count_info_item.xml" value="0.48231511254019294" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/count_info_none_item.xml" value="0.4" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/count_info_pie_item.xml" value="0.3233695652173913" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/filter_dialog_layout.xml" value="0.3078125" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_calling_list.xml" value="0.5142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_config.xml" value="0.3828571428571429" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_count.xml" value="0.20742753623188406" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_log.xml" value="0.5142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_my_calling.xml" value="0.18857142857142858" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_qrz.xml" value="0.1" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/fragment_spectrum.xml" value="0.25407608695652173" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/function_order_spinner_item.xml" value="0.5142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/help_dialog_layout.xml" value="0.4" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/initializeation_data_dialog_layout.xml" value="0.18659420289855072" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/launch_supervision_spinner_item.xml" value="0.2760416666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/log_callsign_holder_item.xml" value="0.42857142857142855" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/log_qsl_holder_item.xml" value="0.6285714285714287" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/main_activity.xml" value="0.4" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/main_fragment.xml" value="0.24864130434782608" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/no_reply_limit_spinner_item.xml" value="0.38768115942028986" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/operation_band_spinner_item.xml" value="0.21014492753623187" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/ptt_delay_spinner_item.xml" value="0.2760416666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/rig_name_spinner_item.xml" value="0.72" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/select_bluetooth_dialog_layout.xml" value="0.2857142857142857" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/select_serial_port_list_view_item.xml" value="0.291213768115942" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/serial_port_dialog_layout.xml" value="0.264" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/serial_port_spinner_item.xml" value="0.2760416666666667" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/spectrum_layout.xml" value="0.2857142857142857" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/transmit_list_holder_item.xml" value="0.5142857142857143" />
<entry key="..\:/coding/ft8CN/app/src/main/res/layout/utc_time_offset_spinner_item.xml" value="0.2857142857142857" />
<entry key="..\:/coding/ft8CN/app/src/main/res/menu/bottom_nav_menu.xml" value="0.2755208333333333" />
<entry key="..\:/coding/ft8CN/app/src/main/res/menu/calling_list_menu.xml" value="0.2857142857142857" />
<entry key="..\:/coding/ft8CN/app/src/main/res/xml/imagebutton_click_selector.xml" value="0.2361111111111111" />
<entry key="app/src/main/res/drawable/calling_list_cell_style.xml" value="0.3095" />
<entry key="app/src/main/res/drawable/editor_layout_background_style.xml" value="0.3455" />
<entry key="app/src/main/res/drawable/editor_style.xml" value="0.3455" />
<entry key="app/src/main/res/drawable/help_dialog_style.xml" value="0.3095" />
<entry key="app/src/main/res/drawable/transmitting_message_layout_style.xml" value="0.3455" />
<entry key="app/src/main/res/layout/activity_faqactivity.xml" value="0.34375" />
<entry key="app/src/main/res/layout/fragment_calling_list.xml" value="0.34375" />
<entry key="app/src/main/res/layout/fragment_config.xml" value="0.34375" />
<entry key="app/src/main/res/layout/fragment_my_calling.xml" value="0.34375" />
<entry key="app/src/main/res/layout/fragment_spectrum.xml" value="0.34375" />
<entry key="app/src/main/res/layout/help_dialog_layout.xml" value="0.34375" />
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="0.027632950990615225" />
</ProjectState>
</option>
</component>
</project>

Wyświetl plik

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="useLiveRendering" value="false" />
</component>
</project>

1
ft8cn/app/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1 @@
/build

Wyświetl plik

@ -0,0 +1,91 @@
import java.text.DateFormat
import java.text.SimpleDateFormat
plugins {
id 'com.android.application'
}
def currentTime = getCurrentTime()
static def getCurrentTime() {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd")
return df.format(Calendar.getInstance(Locale.CHINA).getTime())
}
android {
compileSdk 33
defaultConfig {
applicationId "com.bg7yoz.ft8cn"
minSdk 23
targetSdk 33
versionCode 1
versionName '0.9'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
dataBinding{
enabled true
}
externalNativeBuild {
cmake {
cppFlags '-JENABLE_XOM=false'//XOM
}
}
signingConfig signingConfigs.debug
}
buildTypes {
debug{
buildConfigField("String", "apkBuildTime", "\"${currentTime}\"")
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "apkBuildTime", "\"${currentTime}\"")
// true -
//minifyEnabled true
// true -
//shrinkResources true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// externalNativeBuild {
// cmake {
// path file('src/main/cpp/CMakeLists.txt')
// version '3.18.1'
// }
// }
namespace 'com.bg7yoz.ft8cn'
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.navigation:navigation-fragment:2.4.1'
implementation 'androidx.navigation:navigation-ui:2.4.1'
implementation 'com.google.android.gms:play-services-maps:18.0.2'
implementation 'commons-net:commons-net:3.6'//
implementation 'com.google.guava:guava:31.1-jre'//HashTablekeyHashMap
implementation files('src/libs/MPAndroidChartv_3.1.0.jar')
implementation files('src/libs/nanohttpd-2.2.0.jar')
implementation files('src/libs/osmdroid-android-6.1.14.aar')//
}

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

21
ft8cn/app/proguard-rules.pro vendored 100644
Wyświetl plik

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 因需要录音和文件存储功能,所以需要申请录音和存储权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 存储数据权限 -->
<!-- <uses-permission -->
<!-- android:name="android.permission.WRITE_EXTERNAL_STORAGE" -->
<!-- tools:ignore="ScopedStorage" /> -->
<!-- 允许程序访问CellID或WiFi热点来获取粗略的位置 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@drawable/ft8cn_icon"
android:label="@string/app_name"
android:roundIcon="@drawable/ft8cn_icon"
android:supportsRtl="true"
android:theme="@style/Theme.Ft8CN">
<activity
android:name=".grid_tracker.GridTrackerMainActivity"
android:screenOrientation="sensorLandscape"
android:exported="false">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<activity
android:name=".FAQActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
<service android:name=".bluetooth.BluetoothSerialService" />
</application>
</manifest>

Wyświetl plik

@ -0,0 +1,5 @@
关于音频输出设置
采样位深也称采样精度FT8CN只有16位整型和32位浮点可选。采样位数是表示声音强度量化后的精细程度它的数值越大波动幅度的分辨率也就越高所发出声音的能力越强。
采样率:也称取样频率, 指每秒钟取得声音样本的次数。采样频率越高,声音的质量也就越好,但占的资源也多。

Wyświetl plik

@ -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.

Wyświetl plik

@ -0,0 +1,5 @@
关于自动关注CQ
自动关注CQ,是指当FT8CN解码出的消息中有CQ消息时会把这条消息推送到“呼叫”界面的消息列表方便在“呼叫”界面中对CQ消息做出反应。自动关注的CQ消息该呼号不会被长久保存到关注的呼号数据库中。
关于自动呼叫关注的呼号
目标呼号被设定为关注后当目标呼号有CQ行为时FT8CN会自动对该呼号进行呼叫。
FT8CN会长久保存您关注的呼号您每次使用FT8CN时FT8CN会把曾经关注过的呼号的新消息推送到“呼叫”界面的列表中。如果想取消对某呼号的关注可以通过后台操作在局域网中用浏览器访问FT8CN删除该呼号。

Wyświetl plik

@ -0,0 +1,8 @@
About Auto Track CQ
Auto track CQ means that when an FT8CN decoded message contains a CQ message, the FT8CN will push the decoded message to the message list on "Calling" interface, so that to respond to the CQ message on "Calling" interface. Auto track CQ callsign will not be stored in the callsign database for a long time.
About Auto Call Tracked
After the target callsign is set to track, FT8CN will automatically call the callsign when the target callsign has CQ behavior.
FT8CN will preserve the tracking callsigns for a long time. Every time you use FT8CN, FT8CN will push the new message of the tracking callsigns to the list of the "Calling" interface. If you want to unfollow a callsign, you can delete the callsign through background operations (accessing FT8CN with a browser on the lan).

Wyświetl plik

@ -0,0 +1,39 @@
*:1810000:160m
*:1840000:160m
*:1908000:160m
:3531000:80m
:3567000:80m
*:3573000:80m
:3585000:80m
:5126000:60m
*:5357000:60m
:5362000:60m
:7056000:40m
:7041000:40m
:7071000:40m
*:7074000:40m
:7080000:40m
:10131000:30m
:10133000:30m
*:10136000:30m
:10143000:30m
:14071000:20m
*:14074000:20m
*:14090000:20m
:18095000:17m
*:18100000:17m
*:21074000:15m
:21091000:15m
:24911000:12m
*:24915000:12m
*:28074000:10m
:28095000:10m
*:40680000:8m
:50310000:6m
*:50313000:6m
*:50323000:6m
:70100000:4m
*:70154000:4m
*:144174000:2m
:144460000:2m
*:432174000:70cm

Wyświetl plik

@ -0,0 +1,5 @@
请输入您的呼号。
呼号是您用本APP进行FT8呼叫的基本要求。您如果在中国境内请使用符合《中华人民共和国无线电管理条例》的合法呼号禁止一切非法发射
关于修饰符
CQ后面可能跟有三个十进制数字或一到四个字母的修饰符一般是000-999或A-Z或AA-ZZ或AAA-ZZZ或AAAA-ZZZZ。例如CQ POTA callsign grid。

Wyświetl plik

@ -0,0 +1,8 @@
Enter your callsign
An amateur radio callsign is required to use this APP. Please comply with local laws and regulations to obtain your amateur radio license and callsign. Its ILLEGAL to transmit without a valid license.
CQ modifier
CQ may be followed by a modifier with three decimal digits or one to four letters.
Example : CQ POTA *callsign* *gridsquare*

Wyświetl plik

@ -0,0 +1,5 @@
关于CI-V地址和波特率
CI-V地址是指电台的CI-V地址。CI-VCommunications Interface V是控制端与电台之间的一种通讯协议。
波特率电台与APP之间的数据传输速率。
按照CI-V的要求必须要有彼此的通讯地址来区分因为控制线路上可能有不止一台设备接收端和发送端一般来说每个型号的设备在出厂时都有默认的地址不同型号的电台默认地址不一定相同。控制端的默认地址一般是0xE0,广播地址是0x00。以IC-705为例它的默认地址是0xA4。
本APP已经查找到部分型号电台的默认地址以及该型号电台最大可支持的波特率可以通过选择“电台型号”自动设置地址和波特率。

Wyświetl plik

@ -0,0 +1,6 @@
About CIV Address and Baud Rate
CI-V address: The CI-V address of the radio station equipment (RIG). CI-V (Communications Interface V) is a communication protocol between the control end and the RIG.
Baud Rate: The data transfer rate between the RIG and the APP.
Since there may be more than one device on the control line, according to the requirements of CI-V, the receiver and the sender must be distinguished by each other's communication address. Generally speaking, each model of device has a default address when it leaves the factory, and the default addresses of different models of RIG are not necessarily the same. In generalthe default address of the control end is 0xE0, and the broadcast address is 0x00. In the case of IC-705, its default address is 0xA4.
This APP has found the default addresses and the maximum supported baud rates of these models of RIG, you can automatically set the address and baud rate by selecting "RIG".

Wyświetl plik

@ -0,0 +1,9 @@
关于清空缓存
FT8CN在数据库中保存有您关注的呼号、通联过程中的消息可以通过后台查看。可以通过清空缓存操作彻底删除这些保存的临时数据。
清空缓存不会删除QSO数据。
清空“关注的呼号”,关注的呼号是在平时通联是您关注的呼号,在该呼号出现时,会自动添加的呼叫列表中,方便自动呼叫。清空关注的呼号,是把所有关注的呼号清空。
清空“解码的消息”FT8CN可以保存所有解码的消息这些消息暂时没有在FT8CN界面中显示可以在后台中查看和查询。

Wyświetl plik

@ -0,0 +1,12 @@
Clear Cache Data:
FT8CN keeps a list of followed callsign and decoded messages in the cache file. You can permanently delete those using empty cache option.
Note that clearing cache won't delete any QSO log.
List of followed callsign:
This list contains all your followed callsigns. When a followed callsign shows up, it will be added to the calling message for quick-call or auto-call.
Note that clearing the list of tracked callsign will remove all saved favorite callsigns.
Clean "cached QSO decoding":
FT8CN can save all decoded messages. You review them from Web UI. (Not on main UI yet)

Wyświetl plik

@ -0,0 +1,7 @@
连接方式是指FT8CN控制电台的连接方式。
FT8CN与电台之间有三种连接方式有线连接USB转串口、蓝牙连接、网络连接。对于有线连接和蓝牙连接FT8CN都是通过串口指令对电台的操作进行控制PTT开/关、频率设置、模式设置等)。而网络连接是使用电台的网络协议对电台进行控制,包括收发的音频也是通过网络进行传输。
目前FT8CN已经支持ICOM、FlexRadio网络协议ICOM已经完全支持音频信号的收发FlexRadio目前仅支持音频的接收。
注:
1.只有手机在蓝牙开启状态才能进入蓝牙连接模式。
2.蓝牙串口与蓝牙音频不同蓝牙音频只能用于音频的收发无法控制电台。具有蓝牙串口的电台才能被FT8CN控制。
3.在ICOM使用网络连接时传输过程对路由器性能要求很高低性能的路由器在发射音频时会有严重的丢包情况建议使用电台的AP或手机的AP来连接。

Wyświetl plik

@ -0,0 +1,6 @@
Connect mode refers to the way FT8CN controls the RIG.
There are two connect modes between FT8CN and RIG: wired connection (USB to serial port) and wireless connection (Bluetooth serial port). FT8CN controls the operation of the RIG through serial port commands (PTT on/off, frequency settings, mode settings, etc.). Both USB and bluetooth use serial port commands to control the RIG.
Note:
1. Only when bluetooth is enabled can the phone enter bluetooth connection mode.
2. Bluetooth serial port is different from bluetooth audio. Bluetooth audio can only be used for audio transmission and reception, it can not control the RIG. Only RIG with bluetooth serial ports can be controlled by FT8CN.

Wyświetl plik

@ -0,0 +1,14 @@
控制方式是指FT8CN发射信号时对电台的控制方式。
VOX:声控模式启动发射。
CAT:通过串口用指令对电台进行控制不同型号的电台指令集并不一定相同。多数的ICOM电台使用的指令集为CI-V多数ICOM型号电台之间指令兼容度比较高。YAESU电台串口指令大体上分为3~4类指令集同类指令集的电台彼此兼容。
RTS:通过串口的RTS电路的开闭来控制电台的PTT。部分老电台如IC706无法通过串口指令控制PTT发射需要通过打开/关闭RTS来控制PTT。选择此项目不代表串口指令不发挥作用除了控制PTT用RTS外其它对电台的控制功能还是使用串口指令的选择RTS也需要设置电台的地址和波特率以方便APP对电台的控制。
DTR:与RTS类似通过串口的DTR电路的开闭来控制电台的PTT。
一般来说即使不同型号的电台指令集存在不同同一厂商或同一系列的电台大部分的指令还是相同的。FT8CN本着最大兼容性考虑尽可能使用少的串口指令。
使用的串口指令主要是读取和设置频率、读取和设置模式USB上边带、打开PTT按键、关闭PTT按键。
我的手机没有串口现在很多电台是在USB口中映射出串口的如果您的电台具备此功能手机端使用USB的OTG转接线接入电台即可。
以IC-705电台为例705的USB会映射出2个串口同时还会虚拟出声卡。当手机通过USB线连接705电台后手机就可以通过映射出的串口控制电台同时利用电台虚拟出的声卡采集和发射音频。
CAT模式应当注意
1.USB口是区分主从关系的电台的USB端口是从模式手机默认情况下也是从模式。如果用手机控制电台就要把手机转为主模式一般可以通过OTG转接口来实现。
2.电台处于发射状态时可能会有电流对USB线产生干扰造成手机与电台的通讯中断无法控制电台常用的解决办法在USB线上增加磁环最佳解决方案使用USB屏蔽器价格在40左右。
3.使用部分OTG或USB拓展坞可能会使电台的底噪增加请选择合适的转接口。

Wyświetl plik

@ -0,0 +1,16 @@
Control Mode refers to the control mode of the RIG when FT8CN transmits signals.
VOXSignal transmission is initiated by voice control.
CATIt refers to the control of RIG with instructions through a serial port. The instruction sets of different RIG models are not always the same. The instruction set used by most ICOM RIGS is CI-V, and the compatibility between most ICOM RIGS is relatively high. YAESU radio serial port instructions are generally divided into three or four instruction sets. RIGS with the same instruction set are compatible with each other.
RTS: The PTT of the RIG is controlled by opening and closing the RTS circuit of the serial port。Some old RIGS, such as IC706, cannot control PTT transmission through serial port instructions and they need to control PTT by turning on/off RTS. Selecting this item does not mean that the serial port instruction does not work. In addition to using RTS to control PTT, other control functions of the RIG still require the instructions of serial port. Choosing RTS also requires setting the RIG address and baud rate to facilitate the app's control.
DTR: Similar to RTS, the RIG's PTT is controlled by the opening and closing the DTR circuit of the serial port.
In general, even if the instruction sets of different RIG models are different, most of the instructions of the same manufacturer or the same series of RIGs are still the same. FT8CN uses as few serial instructions as possible for maximum compatibility.
The serial port instructions used mainly include reading and setting the frequency, reading and setting mode (USB upper sideband), turning on and off the PTT button.
What should I do if my phone doesn't have a serial port?
Now many RIGs are mapped out of the serial port in the USB port, if your RIG has this function, the mobile phone can use the USB OTG adapter cable to access the RIG.
Taking IC-705 RIG as an example, the USB of the 705 will map out 2 serial ports, at the same time, the virtual sound card of the Rig is used to collect and transmit audio. When the mobile phone is connected to the 705 RIG through the USB cable, it can control the RIG through the mapped serial port, at the same time it can use the sound card virtually to collect and transmit audio.
Notes for CAT mode:
1. The USB port can distinguish the master-slave relationship, the USB port of the RIG is in slave mode, and the phone is in slave mode by default. If you use a mobile phone to control the RIG, you must switch the mobile phone to the master mode, which can generally be achieved through the OTG interface.
2. When the RIG is in the transmitting state, there may be current interference with the USB cable, resulting in the interruption of communication between the mobile phone and the rig and the inability to control the station. A common solution is to add a magnetic ring to the USB line. The best solution is to use a USB shielder.
3. Some OTG or USB docking stations may increase the ground noise of the RIG, please choose the appropriate adapter.

Wyświetl plik

@ -0,0 +1,382 @@
Sov Mil Order of Malta:马耳他索夫米尔教团
Spratly Islands China:中国南沙群岛
Monaco:摩纳哥
Agalega & St. Brandon:阿加莱加和圣布兰登岛
Mauritius:毛里求斯
Rodriguez Island:罗德里格斯岛
Equatorial Guinea:赤道几内亚
Annobon Island:安诺本岛
Fiji:斐济
Conway Reef:康威礁
Rotuma Island:罗图马岛
Kingdom of Eswatini:斯威士兰王国
Tunisia:突尼斯
Vietnam:越南
Guinea:几内亚
Bouvet:布韦岛
Peter 1 Island:彼得一世岛
Azerbaijan:阿塞拜疆
Georgia:格鲁吉亚
Montenegro:黑山
Sri Lanka:斯里兰卡
ITU HQ:国际电信联盟总部
United Nations HQ:联合国总部
Vienna Intl Ctr:维也纳国际中心
Timor - Leste:东帝汶
Israel:以色列
Libya:利比亚
Cyprus:塞浦路斯
Tanzania:坦桑尼亚
Nigeria:尼日利亚
Madagascar:马达加斯加
Mauritania:毛里塔尼亚
Niger:尼日尔
Togo:多哥
Samoa:萨摩亚
Uganda:乌干达
Kenya:肯尼亚
Senegal:塞内加尔
Jamaica:牙买加
Yemen:也门
Lesotho:莱索托
Malawi:马拉维
Algeria:阿尔及利亚
Barbados:巴巴多斯
Maldives:马尔代夫
Guyana:圭亚那
Croatia:克罗地亚
Ghana:加纳
Malta:马耳他
Zambia:赞比亚
Kuwait:科威特
Sierra Leone:塞拉利昂
West Malaysia:西马来西亚
East Malaysia:东马来西亚
Nepal:尼泊尔
Dem. Rep. of the Congo:刚果民主共和国
Burundi:布隆迪
Singapore:新加坡
Rwanda:卢旺达
Trinidad & Tobago:特立尼达和多巴哥
Botswana:博茨瓦纳
Tonga:汤加
Oman:阿曼
Bhutan:不丹
United Arab Emirates:阿拉伯联合酋长国
Qatar:卡塔尔
Bahrain:巴林
Pakistan:巴基斯坦
Scarborough Reef China:中国黄岩岛
Taiwan China:中国台湾
Pratas Island China:中国东沙岛
XinJiang China:中国新疆
XiZang China:中国西藏
BeiJing China:中国北京
HeiLongJiang China:中国黑龙江
JiLin China:中国吉林
LiaoNing China:中国辽宁
TianJin China:中国天津
NeiMengGu China:中国内蒙古
HeBei China:中国河北
ShiJiaZhuang HeBei China:中国河北 石家庄
ShanXi China:中国山西
ShangHai China:中国上海
ShanDong China:中国山东
JiangSu China:中国江苏
SuZhou JiangSu China:中国江苏 苏州
ZheJiang China:中国浙江
JiangXi China:中国江西
FuJian China:中国福建
AnHui China:中国安徽
HeNan China:中国河南
HuBei China:中国湖北
HuNan China:中国湖南
GuangDong China:中国广东
ShenZhen China:中国深圳
GuangXi China:中国广西
HaiNan China:中国海南
SiChuan China:中国四川
ChongQing China:中国重庆
GuiZhou China:中国贵州
YunNan China:中国云南
ShaanXi China:中国陕西
GanSu China:中国甘肃
Qingyang GanSu China:中国甘肃 庆阳
NingXia China:中国宁夏
YinChuan NingXia China:中国宁夏 银川
QingHai China:中国青海
China:中国
Nauru:瑙鲁
Andorra:安道尔
The Gambia:冈比亚
Bahamas:巴哈马群岛
Mozambique:莫桑比克
Chile:智利
San Felix & San Ambrosio:圣菲力斯和圣安布罗西奥岛
Easter Island:复活节岛
Juan Fernandez Islands:胡安费南德斯岛
Antarctica:南极洲
Cuba:古巴
Morocco:摩洛哥
Bolivia:玻利维亚
Portugal:葡萄牙
Madeira Islands:马德拉群岛
Azores:亚速尔群岛
Uruguay:乌拉圭
Sable Island:塞布尔岛
St. Paul Island:圣保罗岛
Angola:安哥拉
Cape Verde:佛得角
Comoros:科摩罗
Fed. Rep. of Germany:德国
Philippines:菲律宾
Eritrea:厄立特里亚
Palestine:巴勒斯坦
North Cook Islands:北库克群岛
South Cook Islands:南库克群岛
Niue:纽埃
Bosnia-Herzegovina:波斯尼亚和黑塞哥维那
Spain:西班牙
Balearic Islands:巴利阿里群岛
Canary Islands:加那利群岛
Ceuta & Melilla:休达和梅利利亚
Ireland:爱尔兰
Armenia:亚美尼亚
Liberia:利比里亚
Iran:伊朗
Moldova:摩尔多瓦
Estonia:爱沙尼亚
Ethiopia:埃塞俄比亚
Belarus:白俄罗斯
Kyrgyzstan:吉尔吉斯斯坦
Tajikistan:塔吉克斯坦
Turkmenistan:土库曼斯坦
France:法国
Guadeloupe:瓜德罗普岛
Mayotte:马约特岛
St. Barthelemy:圣巴特勒米岛
New Caledonia:新喀里多尼亚
Chesterfield Islands:切斯特菲尔德岛
Martinique:马提尼克岛
French Polynesia:法属波利尼西亚
Austral Islands:南方群岛
Clipperton Island:克利珀顿岛
Marquesas Islands:马克萨斯岛
St. Pierre & Miquelon:圣皮埃尔和密克隆岛
Reunion Island:留尼旺岛
St. Martin:圣马丁岛
Glorioso Islands:格洛里厄斯岛
Juan de Nova, Europa:新胡安岛欧罗巴岛,欧洲
Tromelin Island:特罗姆林岛
Crozet Island:克罗泽特岛
Kerguelen Islands:凯尔盖朗岛
Amsterdam & St. Paul Is.:阿姆斯特丹和圣保罗岛
Wallis & Futuna Islands:瓦利斯和富图纳岛
French Guiana:法属圭亚那
England:英格兰
Isle of Man:马恩岛
Northern Ireland:北爱尔兰
Jersey:英国泽西岛
Scotland:苏格兰
Shetland Islands:设得兰群岛
Guernsey:根西岛
Wales:威尔士
Solomon Islands:所罗门群岛
Temotu Province:所罗门群岛泰莫图省
Hungary:匈牙利
Switzerland:瑞士
Liechtenstein:列支敦士登
Ecuador:厄瓜多尔
Galapagos Islands:加拉帕戈斯岛
Haiti:海地
Dominican Republic:多米尼加共和国
Colombia:哥伦比亚
San Andres & Providencia:哥伦比亚圣安德列斯和普罗维登西亚岛
Malpelo Island:马尔佩洛岛
Republic of Korea:韩国
Panama:巴拿马
Honduras:洪都拉斯
Thailand:泰国
Vatican City:梵蒂冈
Saudi Arabia:沙特阿拉伯
Italy:意大利
African Italy:意大利非洲属地
Sardinia:意大利撒丁岛
Sicily:意大利西西里岛
Djibouti:吉布提
Grenada:格林纳达
Guinea-Bissau:几内亚比绍
St. Lucia:圣卢西亚
Dominica:多米尼克
St. Vincent:圣文森特岛
Japan:日本
Minami Torishima:日本南鸟岛
Ogasawara:日本小笠原群岛
Mongolia:蒙古国
Svalbard:挪威斯瓦尔巴岛
Bear Island:挪威熊岛
Jan Mayen:挪威扬马延岛
Jordan:约旦
United States:美国
Guantanamo Bay:关塔那摩湾
Mariana Islands:马里亚纳群岛
Baker & Howland Islands:贝克和豪兰岛
Guam:关岛
Johnston Island:约翰斯顿岛
Midway Island:中途岛
Palmyra & Jarvis Islands:巴尔米拉环礁和贾维斯岛
Hawaii:夏威夷
Kure Island:库雷环礁
American Samoa:美属萨摩亚
Swains Island:斯温斯岛
Wake Island:威克岛
Alaska:美国阿拉斯加
Navassa Island:纳瓦萨岛
US Virgin Islands:美属维京群岛
Puerto Rico:波多黎各
Desecheo Island:德塞切奥岛
Norway:挪威
Argentina:阿根廷
Luxembourg:卢森堡
Lithuania:立陶宛
Bulgaria:保加利亚
Peru:秘鲁
Lebanon:黎巴嫩
Austria:奥地利
Finland:芬兰
Aland Islands:奥兰群岛
Market Reef:马凯特礁
Czech Republic:捷克
Slovak Republic:斯洛伐克
Belgium:比利时
Greenland:格陵兰
Faroe Islands:法罗群岛
Denmark:丹麦
Papua New Guinea:巴布亚新几内亚
Aruba:阿鲁巴
DPR of Korea:北朝鲜
Netherlands:荷兰
Curacao:库拉索岛
Bonaire:博奈尔岛
Saba & St. Eustatius:荷属萨巴和圣尤斯特歇斯岛
Sint Maarten:荷属圣马丁岛
Brazil:巴西
Fernando de Noronha:费尔南多迪诺罗尼亚岛
St. Peter & St. Paul:圣彼得和圣保罗岩
Trindade & Martim Vaz:特林达迪和马丁瓦斯岛
Suriname:苏里南
Franz Josef Land:法兰士约瑟夫地岛
Western Sahara:西撒哈拉
Bangladesh:孟加拉国
Slovenia:斯洛文尼亚
Seychelles:塞舌尔岛
Sao Tome & Principe:圣多美和普林西比岛
Sweden:瑞典
Poland:波兰
Sudan:苏丹
Egypt:埃及
Greece:希腊
Mount Athos:希腊阿索斯圣山
Dodecanese:多德卡尼斯群岛
Crete:克里特岛
Tuvalu:图瓦卢
Western Kiribati:基里巴斯西部
Central Kiribati:基里巴斯中部
Eastern Kiribati:基里巴斯东部
Banaba Island:巴纳巴岛
Somalia:索马里
San Marino:圣马力诺
Palau:帕劳
Asiatic Turkey:土耳其·亚洲
European Turkey:土耳其·欧洲
Iceland:冰岛
Guatemala:危地马拉
Costa Rica:哥斯达黎加
Cocos Island:科科斯岛
Cameroon:喀麦隆
Corsica:科西嘉岛
Central African Republic:中非共和国
Republic of the Congo:刚果共和国
Gabon:加蓬
Chad:乍得共和国
Cote d'Ivoire:科特迪瓦
Benin:贝宁
Mali:马里
European Russia:俄罗斯·欧洲
Kaliningrad:俄罗斯·加里宁格勒
Asiatic Russia:俄罗斯·亚洲
Uzbekistan:乌兹别克斯坦
Kazakhstan:哈萨克斯坦
Ukraine:乌克兰
Antigua & Barbuda:安提瓜和巴布达
Belize:伯利兹
St. Kitts & Nevis:圣基茨和尼维斯
Namibia:纳米比亚
Micronesia:密克罗尼西亚
Marshall Islands:马绍尔群岛
Brunei Darussalam:文莱达鲁萨兰国
Canada:加拿大
Australia:澳大利亚
Heard Island:赫德岛
Macquarie Island:麦格理岛
Cocos (Keeling) Islands:科科斯(基林)群岛
Lord Howe Island:豪勋爵岛
Mellish Reef:梅利什礁
Norfolk Island:诺福克岛
Willis Island:威利斯岛
Christmas Island:圣诞岛
Anguilla:安圭拉岛
Montserrat:蒙特塞拉特岛
British Virgin Islands:英属维京岛
Turks & Caicos Islands:特克斯和凯科斯岛
Pitcairn Island:皮特凯恩岛
Ducie Island:迪西岛
Falkland Islands:福克兰群岛
South Georgia Island:南乔治亚岛
South Shetland Islands:南设得兰群岛
South Orkney Islands:南奥克尼群岛
South Sandwich Islands:南桑维奇群岛
Bermuda:百慕大群岛
Chagos Islands:查戈斯群岛
Hong Kong China:中国香港
India:印度
Andaman & Nicobar Is.:安达曼和尼科巴岛
Lakshadweep Islands:拉克沙威岛
Mexico:墨西哥
Revillagigedo:雷维亚希赫多岛
Burkina Faso:布基纳法索
Cambodia:柬埔寨
Laos:老挝
Macao:中国澳门
Myanmar:缅甸
Afghanistan:阿富汗
Indonesia:印度尼西亚
Iraq:伊拉克
Vanuatu:瓦努阿图
Syria:叙利亚
Latvia:拉脱维亚
Nicaragua:尼加拉瓜
Romania:罗马尼亚
El Salvador:萨尔瓦多
Serbia:塞尔维亚
Venezuela:委内瑞拉
Aves Island:阿维斯岛
Zimbabwe:津巴布韦
North Macedonia:北马其顿
Republic of Kosovo:科索沃共和国
Republic of South Sudan:南苏丹共和国
Albania:阿尔巴尼亚
Gibraltar:直布罗陀
UK Base Areas on Cyprus:英属塞浦路斯基地
St. Helena:圣赫勒拿岛
Ascension Island:阿森松岛
Tristan da Cunha & Gough:特里斯坦和达库尼亚岛
Cayman Islands:开曼群岛
Tokelau Islands:托克劳群岛
New Zealand:新西兰
Chatham Islands:查塔姆群岛
Kermadec Islands:克马德克群岛
N.Z. Subantarctic Is.:新西兰亚南极群岛
Paraguay:巴拉圭
South Africa:南非
Pr. Edward & Marion Is.:爱德华王子和马里恩岛

Wyświetl plik

@ -0,0 +1,382 @@
Sov Mil Order of Malta:马耳他索夫米尔教团
Spratly Islands China:中国南沙群岛
Monaco:摩纳哥
Agalega & St. Brandon:阿加莱加群岛和圣布莱顿岛
Mauritius:毛里求斯
Rodriguez Island:罗德里格斯岛
Equatorial Guinea:赤道几内亚
Annobon Island:安诺本岛
Fiji:斐济
Conway Reef:康威礁
Rotuma Island:罗图马岛
Kingdom of Eswatini:埃斯瓦蒂尼王国
Tunisia:突尼斯
Vietnam:越南
Guinea:几尼
Bouvet:布韦
Peter 1 Island:彼得1岛
Azerbaijan:阿塞拜疆
Georgia:佐治亚州
Montenegro:黑山
Sri Lanka:斯里兰卡
ITU HQ:国际电信联盟总部
United Nations HQ:联合国总部
Vienna Intl Ctr:维也纳国际中心
Timor - Leste:东帝汶
Israel:以色列
Libya:利比亚
Cyprus:塞浦路斯
Tanzania:坦桑尼亚
Nigeria:尼日利亚
Madagascar:马达加斯加
Mauritania:毛里塔尼亚
Niger:尼日尔
Togo:多哥
Samoa:萨摩亚
Uganda:乌干达
Kenya:肯尼亚
Senegal:塞内加尔
Jamaica:牙买加
Yemen:也门
Lesotho:莱索托
Malawi:马拉维
Algeria:阿尔及利亚
Barbados:巴巴多斯
Maldives:马尔代夫
Guyana:圭亚那
Croatia:克罗地亚
Ghana:加纳
Malta:马耳他
Zambia:赞比亚
Kuwait:科威特
Sierra Leone:塞拉利昂
West Malaysia:西马来西亚
East Malaysia:东马来西亚
Nepal:尼泊尔
Dem. Rep. of the Congo:刚果共和国
Burundi:布隆迪
Singapore:新加坡
Rwanda:卢旺达
Trinidad & Tobago:特立尼达和多巴哥
Botswana:博茨瓦纳
Tonga:汤加
Oman:阿曼
Bhutan:不丹
United Arab Emirates:阿拉伯联合酋长国
Qatar:卡塔尔
Bahrain:巴林
Pakistan:巴基斯坦
Scarborough Reef China:中国黄岩岛
Taiwan China:中国台湾
Pratas Island China:中国东沙岛
XinJiang China:中国新疆
XiZang China:中国西藏
BeiJing China:中国北京
HeiLongJiang China:中国黑龙江
JiLin China:中国吉林
LiaoNing China:中国辽宁
TianJin China:中国天津
NeiMengGu China:中国内蒙古
HeBei China:中国河北
ShiJiaZhuang HeBei China:中国河北 石家庄
ShanXi China:中国山西
ShangHai China:中国上海
ShanDong China:中国山东
JiangSu China:中国江苏
SuZhou JiangSu China:中国江苏 苏州
ZheJiang China:中国浙江
JiangXi China:中国江西
FuJian China:中国福建
AnHui China:中国安徽
HeNan China:中国河南
HuBei China:中国湖北
HuNan China:中国湖南
GuangDong China:中国广东
ShenZhen China:中国深圳
GuangXi China:中国广西
HaiNan China:中国海南
SiChuan China:中国四川
ChongQing China:中国重庆
GuiZhou China:中国贵州
YunNan China:中国云南
ShaanXi China:中国陕西
GanSu China:中国甘肃
Qingyang GanSu China:中国甘肃 庆阳
NingXia China:中国宁夏
YinChuan NingXia China:中国宁夏 银川
QingHai China:中国青海
China:中国
Nauru:瑙鲁
Andorra:安道尔
The Gambia:冈比亚
Bahamas:巴哈马群岛
Mozambique:莫桑比克
Chile:智利
San Felix & San Ambrosio:圣菲利克斯和圣安布罗西奥
Easter Island:复活节岛
Juan Fernandez Islands:费尔南德斯群岛
Antarctica:南极洲
Cuba:古巴
Morocco:摩洛哥
Bolivia:玻利维亚
Portugal:葡萄牙
Madeira Islands:马德拉群岛
Azores:亚速尔群岛
Uruguay:乌拉圭
Sable Island:塞布尔岛
St. Paul Island:圣保罗岛
Angola:安哥拉
Cape Verde:佛得角
Comoros:科摩罗
Fed. Rep. of Germany:德国
Philippines:菲律宾
Eritrea:厄立特里亚
Palestine:巴勒斯坦
North Cook Islands:北库克群岛
South Cook Islands:南库克群岛
Niue:纽埃
Bosnia-Herzegovina:波斯尼亚和黑塞哥维那
Spain:西班牙
Balearic Islands:巴利阿里群岛
Canary Islands:加那利群岛
Ceuta & Melilla:休达梅利利亚
Ireland:爱尔兰
Armenia:亚美尼亚
Liberia:利比里亚
Iran:伊朗
Moldova:摩尔多瓦
Estonia:爱沙尼亚
Ethiopia:埃塞俄比亚
Belarus:白俄罗斯
Kyrgyzstan:吉尔吉斯斯坦
Tajikistan:塔吉克斯坦
Turkmenistan:土库曼斯坦
France:法国
Guadeloupe:瓜德罗普岛
Mayotte:马约特
St. Barthelemy:圣巴托洛缪岛
New Caledonia:新喀里多尼亚
Chesterfield Islands:切斯特菲尔德群岛
Martinique:马提尼克岛
French Polynesia:法属波利尼西亚
Austral Islands:澳大利亚群岛
Clipperton Island:克利珀顿岛
Marquesas Islands:马奎萨斯群岛
St. Pierre & Miquelon:圣皮埃尔和密克隆
Reunion Island:留尼旺岛
St. Martin:圣马丁
Glorioso Islands:格洛里厄斯群岛
Juan de Nova, Europa:万诺瓦岛
Tromelin Island:特罗梅林岛
Crozet Island:克罗泽岛
Kerguelen Islands:克格伦群岛
Amsterdam & St. Paul Is.:阿姆斯特丹和圣保罗
Wallis & Futuna Islands:沃利斯和富图纳群岛
French Guiana:法属圭亚那
England:英格兰
Isle of Man:马恩岛
Northern Ireland:北爱尔兰
Jersey:英国泽西岛
Scotland:苏格兰
Shetland Islands:设得兰群岛
Guernsey:根西岛
Wales:威尔士
Solomon Islands:所罗门群岛
Temotu Province:所罗门群岛泰莫图省
Hungary:匈牙利
Switzerland:瑞士
Liechtenstein:列支敦士登
Ecuador:厄瓜多尔
Galapagos Islands:加拉帕戈斯群岛
Haiti:海地
Dominican Republic:多米尼加共和国
Colombia:哥伦比亚
San Andres & Providencia:哥伦比亚圣安德列斯-普罗维登西亚省
Malpelo Island:马尔佩洛岛
Republic of Korea:韩国
Panama:巴拿马
Honduras:洪都拉斯
Thailand:泰国
Vatican City:梵蒂冈
Saudi Arabia:沙特阿拉伯
Italy:意大利
African Italy:非裔意大利
Sardinia:意大利撒丁岛
Sicily:意大利西西里岛
Djibouti:吉布提
Grenada:格林纳达
Guinea-Bissau:几内亚比绍
St. Lucia:圣卢西亚
Dominica:多米尼加
St. Vincent:圣文森特
Japan:日本
Minami Torishima:日本南鸟岛
Ogasawara:日本小笠原群岛
Mongolia:蒙古国
Svalbard:挪威斯瓦尔巴群岛
Bear Island:挪威熊岛
Jan Mayen:挪威扬马延岛
Jordan:约旦
United States:美国
Guantanamo Bay:关塔那摩湾
Mariana Islands:马里亚纳群岛
Baker & Howland Islands:贝克豪兰群岛
Guam:关岛
Johnston Island:约翰斯顿岛
Midway Island:中途岛
Palmyra & Jarvis Islands:帕尔米拉和贾维斯群岛
Hawaii:夏威夷
Kure Island:库尔岛
American Samoa:美属萨摩亚
Swains Island:天鹅岛
Wake Island:威克岛
Alaska:美国阿拉斯加
Navassa Island:纳瓦萨岛
US Virgin Islands:美属维京群岛
Puerto Rico:波多黎各
Desecheo Island:德塞西奥岛
Norway:挪威
Argentina:阿根廷
Luxembourg:卢森堡
Lithuania:立陶宛
Bulgaria:保加利亚
Peru:秘鲁
Lebanon:黎巴嫩
Austria:奥地利
Finland:芬兰
Aland Islands:阿兰群岛
Market Reef:市场暗礁
Czech Republic:捷克
Slovak Republic:斯洛伐克
Belgium:比利时
Greenland:格陵兰
Faroe Islands:法罗群岛
Denmark:丹麦
Papua New Guinea:巴布亚新几内亚
Aruba:阿鲁巴
DPR of Korea:北朝鲜
Netherlands:荷兰
Curacao:库拉索
Bonaire:博内尔
Saba & St. Eustatius:荷属萨巴群岛和圣尤斯特歇斯群岛
Sint Maarten:荷属圣马丁
Brazil:巴西
Fernando de Noronha:费尔南多·德诺伦哈岛
St. Peter & St. Paul:圣彼得和圣保罗岛
Trindade & Martim Vaz:特林达迪和马丁瓦斯群岛
Suriname:苏里南
Franz Josef Land:弗朗兹·约瑟夫群岛
Western Sahara:西撒哈拉
Bangladesh:孟加拉国
Slovenia:斯洛文尼亚
Seychelles:塞舌尔
Sao Tome & Principe:圣多美和普林西比岛
Sweden:瑞典
Poland:波兰
Sudan:苏丹
Egypt:埃及
Greece:希腊
Mount Athos:希腊阿托斯山
Dodecanese:多德卡尼斯群岛
Crete:克里特岛
Tuvalu:图瓦卢
Western Kiribati:基里巴斯西部
Central Kiribati:基里巴斯中部
Eastern Kiribati:基里巴斯东部
Banaba Island:巴纳巴岛
Somalia:索马里
San Marino:圣马力诺
Palau:帕劳
Asiatic Turkey:土耳其·亚洲
European Turkey:土耳其·欧洲
Iceland:冰岛
Guatemala:危地马拉
Costa Rica:哥斯达黎加
Cocos Island:科科斯群岛
Cameroon:喀麦隆
Corsica:科西嘉岛
Central African Republic:中非共和国
Republic of the Congo:刚果共和国
Gabon:加蓬
Chad:乍得共和国
Cote d'Ivoire:科特迪瓦
Benin:贝宁
Mali:马里
European Russia:俄罗斯·欧洲
Kaliningrad:俄罗斯·加里宁格勒
Asiatic Russia:俄罗斯·亚洲
Uzbekistan:乌兹别克斯坦
Kazakhstan:哈萨克斯坦
Ukraine:乌克兰
Antigua & Barbuda:安提瓜和巴布达
Belize:伯利兹
St. Kitts & Nevis:圣基茨和尼维斯
Namibia:纳米比亚
Micronesia:密克罗尼西亚
Marshall Islands:马绍尔群岛
Brunei Darussalam:文莱达鲁萨兰国
Canada:加拿大
Australia:澳大利亚
Heard Island:赫德岛
Macquarie Island:麦格理岛
Cocos (Keeling) Islands:科科斯(基林)群岛
Lord Howe Island:豪勋爵岛
Mellish Reef:梅利什礁
Norfolk Island:诺福克岛
Willis Island:威利斯岛
Christmas Island:圣诞岛
Anguilla:安圭拉
Montserrat:蒙特塞拉特
British Virgin Islands:英属维尔京群岛
Turks & Caicos Islands:特克斯和凯科斯群岛
Pitcairn Island:皮特凯恩岛
Ducie Island:迪西岛
Falkland Islands:福克兰群岛
South Georgia Island:南乔治亚岛
South Shetland Islands:南设得兰群岛
South Orkney Islands:南奥克尼群岛
South Sandwich Islands:南桑威奇群岛
Bermuda:百慕大群岛
Chagos Islands:查戈斯群岛
Hong Kong China:中国香港
India:印度
Andaman & Nicobar Is.:安达曼-尼科巴群岛
Lakshadweep Islands:拉克沙群岛
Mexico:墨西哥
Revillagigedo:雷维拉吉加多
Burkina Faso:布基纳法索
Cambodia:柬埔寨
Laos:老挝
Macao:中国澳门
Myanmar:缅甸
Afghanistan:阿富汗
Indonesia:印度尼西亚
Iraq:伊拉克
Vanuatu:瓦努阿图
Syria:叙利亚
Latvia:拉脱维亚
Nicaragua:尼加拉瓜
Romania:罗马尼亚
El Salvador:萨尔瓦多
Serbia:塞尔维亚
Venezuela:委内瑞拉
Aves Island:艾夫斯岛
Zimbabwe:津巴布韦
North Macedonia:北马其顿
Republic of Kosovo:科索沃共和国
Republic of South Sudan:南苏丹共和国
Albania:阿尔巴尼亚
Gibraltar:直布罗陀
UK Base Areas on Cyprus:英国在塞浦路斯的基地
St. Helena:圣赫勒拿
Ascension Island:阿森松岛
Tristan da Cunha & Gough:特里斯坦·达库尼亚和高夫
Cayman Islands:开曼群岛
Tokelau Islands:托克劳群岛
New Zealand:新西兰
Chatham Islands:查塔姆群岛
Kermadec Islands:克马德克群岛
N.Z. Subantarctic Is.:新西兰亚南极群岛
Paraguay:巴拉圭
South Africa:南非
Pr. Edward & Marion Is.:爱德华王子群岛和马里恩岛

Wyświetl plik

@ -0,0 +1,382 @@
Sov Mil Order of Malta:馬耳他索夫米爾教團
Spratly Islands China:中國南沙群島
Monaco:摩納哥
Agalega & St. Brandon:阿加萊加及聖布蘭登島
Mauritius:毛里求斯
Rodriguez Island:羅德里格斯島
Equatorial Guinea:赤道幾內亞
Annobon Island:安諾本島
Fiji:斐濟
Conway Reef:康威礁
Rotuma Island:羅圖馬島
Kingdom of Eswatini:斯威士蘭王國
Tunisia:突尼斯
Vietnam:越南
Guinea:幾內亞
Bouvet:布韋島
Peter 1 Island:彼得一世島
Azerbaijan:阿塞拜疆
Georgia:格魯吉亞
Montenegro:黑山共和國
Sri Lanka:斯里蘭卡
ITU HQ:國際電信聯盟總部
United Nations HQ:聯合國總部
Vienna Intl Ctr:維也納國際中心
Timor - Leste:東帝汶
Israel:以色列
Libya:利比亞
Cyprus:塞浦路斯
Tanzania:坦桑尼亞
Nigeria:尼日利亞
Madagascar:馬達加斯加
Mauritania:毛里塔尼亞
Niger:尼日爾
Togo:多哥
Samoa:薩摩亞
Uganda:烏幹達
Kenya:肯尼亞
Senegal:塞內加爾
Jamaica:牙買加
Yemen:也門
Lesotho:萊索托
Malawi:馬拉維
Algeria:阿爾及利亞
Barbados:巴巴多斯
Maldives:馬爾代夫
Guyana:圭亞那
Croatia:克羅地亞
Ghana:加納
Malta:馬爾他
Zambia:讚比亞
Kuwait:科威特
Sierra Leone:塞拉利昂
West Malaysia:西馬來西亞
East Malaysia:東馬來西亞
Nepal:尼泊爾
Dem. Rep. of the Congo:剛果民主共和國
Burundi:布隆迪
Singapore:新加坡
Rwanda:盧旺達
Trinidad & Tobago:特立尼達和多巴哥
Botswana:博茨瓦納
Tonga:湯加
Oman:阿曼
Bhutan:不丹
United Arab Emirates:阿拉伯聯合酋長國
Qatar:卡塔爾
Bahrain:巴林
Pakistan:巴基斯坦
Scarborough Reef China:中國黃巖島
Taiwan China:中國台灣
Pratas Island China:中國東沙島
XinJiang China:中國新疆
XiZang China:中國西藏
BeiJing China:中國北京
HeiLongJiang China:中國黑龍江
JiLin China:中國吉林
LiaoNing China:中國遼寧
TianJin China:中國天津
NeiMengGu China:中國內蒙古
HeBei China:中國河北
ShiJiaZhuang HeBei China:中國河北 石家莊
ShanXi China:中國山西
ShangHai China:中國上海
ShanDong China:中國山東
JiangSu China:中國江蘇
SuZhou JiangSu China:中國江蘇 蘇州
ZheJiang China:中國浙江
JiangXi China:中國江西
FuJian China:中國福建
AnHui China:中國安徽
HeNan China:中國河南
HuBei China:中國湖北
HuNan China:中國湖南
GuangDong China:中國廣東
ShenZhen China:中國深圳
GuangXi China:中國廣西
HaiNan China:中國海南
SiChuan China:中國四川
ChongQing China:中國重慶
GuiZhou China:中國貴州
YunNan China:中國雲南
ShaanXi China:中國陜西
GanSu China:中國甘肅
Qingyang GanSu China:中國甘肅 慶陽
NingXia China:中國寧夏
YinChuan NingXia China:中國寧夏 銀川
QingHai China:中國青海
China:中國
Nauru:瑙魯
Andorra:安道爾
The Gambia:岡比亞
Bahamas:巴哈馬島
Mozambique:莫桑比克
Chile:智利
San Felix & San Ambrosio:聖菲利斯和聖安布羅西奧島
Easter Island:復活節島
Juan Fernandez Islands:胡安費南德斯島
Antarctica:南極洲
Cuba:古巴
Morocco:摩洛哥
Bolivia:玻利維亞
Portugal:葡萄牙
Madeira Islands:馬德拉島
Azores:亞速爾島
Uruguay:烏拉圭
Sable Island:塞布爾島
St. Paul Island:聖保羅島
Angola:安哥拉
Cape Verde:佛得角
Comoros:科摩羅
Fed. Rep. of Germany:德國
Philippines:菲律賓
Eritrea:厄立特里亞
Palestine:巴勒斯坦
North Cook Islands:北庫克島
South Cook Islands:南庫克島
Niue:紐埃
Bosnia-Herzegovina:波斯尼亞和黑塞哥維那
Spain:西班牙
Balearic Islands:巴利阿里島
Canary Islands:加那利島
Ceuta & Melilla:休達梅利利亞
Ireland:愛爾蘭
Armenia:亞美尼亞
Liberia:利比里亞
Iran:伊朗
Moldova:摩爾多瓦
Estonia:愛沙尼亞
Ethiopia:埃塞俄比亞
Belarus:白俄羅斯
Kyrgyzstan:吉爾吉斯斯坦
Tajikistan:塔吉克斯坦
Turkmenistan:土庫曼斯坦
France:法國
Guadeloupe:瓜德羅普島
Mayotte:馬約特島
St. Barthelemy:聖巴特勒米島
New Caledonia:新喀里多尼亞
Chesterfield Islands:切斯特菲爾德島
Martinique:馬提尼克島
French Polynesia:法屬波利尼西亞
Austral Islands:南方群島
Clipperton Island:克利珀頓島
Marquesas Islands:馬克薩斯島
St. Pierre & Miquelon:聖皮埃爾和密克隆島
Reunion Island:留尼旺島
St. Martin:聖馬丁島
Glorioso Islands:格洛里厄斯島
Juan de Nova, Europa:新胡安島歐羅巴島,歐洲
Tromelin Island:特羅姆蘭島
Crozet Island:克羅澤島
Kerguelen Islands:凱爾蓋朗島
Amsterdam & St. Paul Is.:阿姆斯特丹和聖保羅島
Wallis & Futuna Islands:瓦利斯和富圖納島
French Guiana:法屬圭亞那
England:英格蘭
Isle of Man:馬恩島
Northern Ireland:北愛爾蘭
Jersey:英國澤西島
Scotland:蘇格蘭
Shetland Islands:設特蘭島
Guernsey:根西島
Wales:威爾士
Solomon Islands:所羅門島
Temotu Province:所羅門島泰莫圖省
Hungary:匈牙利
Switzerland:瑞士
Liechtenstein:列支敦士登
Ecuador:厄瓜多爾
Galapagos Islands:加拉帕戈斯島
Haiti:海地
Dominican Republic:多明尼加共和國
Colombia:哥倫比亞
San Andres & Providencia:哥倫比亞聖安德列斯和普羅維登西亞島
Malpelo Island:馬爾佩洛島
Republic of Korea:韓國
Panama:巴拿馬
Honduras:洪都拉斯
Thailand:泰國
Vatican City:梵蒂岡
Saudi Arabia:沙特阿拉伯
Italy:意大利
African Italy:意大利非洲屬地
Sardinia:意大利撒丁島
Sicily:意大利西西里島
Djibouti:吉布提
Grenada:格林納達
Guinea-Bissau:幾內亞比紹
St. Lucia:聖盧西亞
Dominica:多米尼克
St. Vincent:聖文森特島
Japan:日本
Minami Torishima:日本南鳥島
Ogasawara:日本小笠原島
Mongolia:蒙古國
Svalbard:挪威斯瓦爾巴島
Bear Island:挪威熊島
Jan Mayen:挪威揚馬延島
Jordan:約旦
United States:美國
Guantanamo Bay:關塔那摩灣
Mariana Islands:馬利亞納島
Baker & Howland Islands:貝克和豪蘭島
Guam:關島
Johnston Island:約翰斯頓島
Midway Island:中途島
Palmyra & Jarvis Islands:巴爾米拉環礁和賈維斯島
Hawaii:夏威夷
Kure Island:庫雷環礁
American Samoa:美屬薩摩亞
Swains Island:斯溫斯島
Wake Island:威克島
Alaska:美國阿拉斯加
Navassa Island:納瓦薩島
US Virgin Islands:美屬維京島
Puerto Rico:波多黎各
Desecheo Island:德塞西奧島
Norway:挪威
Argentina:阿根廷
Luxembourg:盧森堡
Lithuania:立陶宛
Bulgaria:保加利亞
Peru:秘魯
Lebanon:黎巴嫩
Austria:奧地利
Finland:芬蘭
Aland Islands:奧蘭島
Market Reef:馬凱特礁
Czech Republic:捷克
Slovak Republic:斯洛伐克
Belgium:比利時
Greenland:格陵蘭
Faroe Islands:法羅島
Denmark:丹麥
Papua New Guinea:巴布亞新幾內亞
Aruba:阿魯巴
DPR of Korea:北朝鮮
Netherlands:荷蘭
Curacao:庫拉索島
Bonaire:博內爾島
Saba & St. Eustatius:荷屬薩巴和聖尤斯特歇斯島
Sint Maarten:荷屬聖馬丁島
Brazil:巴西
Fernando de Noronha:費爾南多迪諾羅尼亞島
St. Peter & St. Paul:聖彼得和聖保羅岩
Trindade & Martim Vaz:特林達迪和馬丁瓦斯島
Suriname:蘇里南
Franz Josef Land:法蘭士約瑟夫地島
Western Sahara:西撒哈拉
Bangladesh:孟加拉國
Slovenia:斯洛文尼亞
Seychelles:塞舌爾
Sao Tome & Principe:聖多美和普林西比島
Sweden:瑞典
Poland:波蘭
Sudan:蘇丹
Egypt:埃及
Greece:希臘
Mount Athos:希臘阿索斯聖山
Dodecanese:多德卡尼斯島
Crete:克里特島
Tuvalu:圖瓦盧
Western Kiribati:基里巴斯西部
Central Kiribati:基里巴斯中部
Eastern Kiribati:基里巴斯東部
Banaba Island:巴納巴島
Somalia:索馬里
San Marino:聖馬力諾
Palau:帕勞
Asiatic Turkey:土耳其·亞洲
European Turkey:土耳其·歐洲
Iceland:冰島
Guatemala:危地馬拉
Costa Rica:哥斯達黎加
Cocos Island:科科斯島
Cameroon:喀麥隆
Corsica:科西嘉島
Central African Republic:中非共和國
Republic of the Congo:剛果共和國
Gabon:加蓬
Chad:乍得共和國
Cote d'Ivoire:科特迪瓦
Benin:貝寧
Mali:馬里
European Russia:俄羅斯·歐洲
Kaliningrad:俄羅斯·加里寧格勒
Asiatic Russia:俄羅斯·亞洲
Uzbekistan:烏茲別克斯坦
Kazakhstan:哈薩克斯坦
Ukraine:烏克蘭
Antigua & Barbuda:安提瓜和巴布達
Belize:伯利茲
St. Kitts & Nevis:聖基茨和尼維斯
Namibia:納米比亞
Micronesia:密克羅尼西亞
Marshall Islands:馬紹爾島
Brunei Darussalam:文萊達魯薩蘭國
Canada:加拿大
Australia:澳洲
Heard Island:赫德島
Macquarie Island:麥格理島
Cocos (Keeling) Islands:科科斯(基林)島
Lord Howe Island:豪勳爵島
Mellish Reef:梅利什礁
Norfolk Island:諾福克島
Willis Island:威利斯島
Christmas Island:聖誕島
Anguilla:安圭拉島
Montserrat:蒙特塞拉特島
British Virgin Islands:英屬維爾京島
Turks & Caicos Islands:特克斯和凱科斯島
Pitcairn Island:皮特凱恩島
Ducie Island:迪西島
Falkland Islands:福克蘭島
South Georgia Island:南喬治亞島
South Shetland Islands:南設特蘭島
South Orkney Islands:南奧克尼島
South Sandwich Islands:南桑威奇島
Bermuda:百慕大島
Chagos Islands:查戈斯島
Hong Kong China:中國香港
India:印度
Andaman & Nicobar Is.:安達曼和尼科巴島
Lakshadweep Islands:拉克沙威島
Mexico:墨西哥
Revillagigedo:雷維亞希赫多島
Burkina Faso:布基納法索
Cambodia:柬埔寨
Laos:老撾
Macao:中國澳門
Myanmar:緬甸
Afghanistan:阿富汗
Indonesia:印尼
Iraq:伊拉克
Vanuatu:瓦努阿圖
Syria:敘利亞
Latvia:拉脫維亞
Nicaragua:尼加拉瓜
Romania:羅馬尼亞
El Salvador:薩爾瓦多
Serbia:塞爾維亞
Venezuela:委內瑞拉
Aves Island:阿維斯島
Zimbabwe:津巴布韋
North Macedonia:北馬其頓
Republic of Kosovo:科索沃共和國
Republic of South Sudan:南蘇丹共和國
Albania:阿爾巴尼亞
Gibraltar:直布羅陀
UK Base Areas on Cyprus:英屬塞浦路斯基地
St. Helena:聖赫勒拿島
Ascension Island:阿森松島
Tristan da Cunha & Gough:特里斯坦和達庫尼亞島
Cayman Islands:開曼島
Tokelau Islands:托克勞島
New Zealand:新西蘭
Chatham Islands:查塔姆島
Kermadec Islands:克馬德克島
N.Z. Subantarctic Is.:新西蘭亞南極島
Paraguay:巴拉圭
South Africa:南非
Pr. Edward & Marion Is.:愛德華王子島和馬里恩島

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,4 @@
关于解码模式
快速解码沿用原来FT8CN的解码对接收到的音频做一次性的解码。
多次解码:在快速解码的基础上,再做多次的解码,增加迭代的次数,并尝试解码频率有叠加的信号。
注:多次解码增加的运算量,总的解码时间会变长,会缩短设备的续航时间。

Wyświetl plik

@ -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.

Wyświetl plik

@ -0,0 +1,3 @@
排除的呼号前缀。是指FT8CN解码消息后不会把带有指定呼号前缀列入到“呼叫”列表中为自动程序排除掉对该呼号的呼叫。
呼号前缀可以是多个,用逗号、或空格分隔。
排除的呼号前缀的动作具有最高优先级高于“关注”、“CQ”、”与我有关“。

Wyświetl plik

@ -0,0 +1,4 @@
Filtering - hide by prefixes
You can enter a list of prefix that separated by comma "," or space " ". FT8CN will loop up callsigns by your predefined prefixes and hide them from the Calling list.
This filter has the highest priority, which will override any entry from "Tracked Callsign", "CQ", and "about me".

Wyświetl plik

@ -0,0 +1,4 @@
发射FT8信号的频率是指在当前波段上发出的声音频率频率范围一般是10-3000赫兹之间。
同频发射:是指声音频率使用您呼叫目标的频率。此时默认频率值无效。
异频发射:是指您的声音频率使用设定的默认频率。在异频发射模式下,您也可以在频谱界面中通过触摸瀑布图修改默认频率。
关于发射频率,为了防止您的发射频率与其他友台的频率重叠,建议在频谱中观察一下当前所接收到的信号的频率情况,然后选择与接收的各信号不重叠的频率。

Wyświetl plik

@ -0,0 +1,7 @@
The frequency of the transmitted FT8 signal refers to the frequency of the sound transmitted in the current band, and the frequency range is generally between 10 and 3000 Hz.
Locked TX=RXlocked transmission: It refers to the frequency of your target call. The default frequency value is not valid at this point.
Tx/Rx Splitsplit transmission: it refers to the default frequency that has been set. In Tx/Rx Split mode, you can also modify the default frequency in the spectrum interface by touching the waterfall chart.
About the TX Frequency.
In order to prevent your TX frequency from overlapping with the frequencies of others, it is recommended to observe the frequency of the currently received signal in the spectrum, and then select a frequency that does not overlap with each received signal.

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,6 @@
About Tx Watchdog
Within a 1-minute QSO with protocols such as JT65, users have about 10 seconds to decide whether to proceed with the next stage of action. The shorter sequences of FT8 and FT4 require faster responses and the software needs a moderate amount of built-in automation in actual use. This is why the automation program is used in FT8CN.
The FT8CN endorses the Joel Taylor philosophy and opposes the use of robots for FT8 operations, with limited use of automated programs.
Therefore, FT8CN should supervise the automatic program.
When no one intervenes after the launch regulatory time limit is exceededthe launch procedure will automatically stop.

Wyświetl plik

@ -0,0 +1,3 @@
关于发射监管
与JT65等协议的1分钟通联时序用户可以有大约10秒钟的时间来决定是否继续下一阶段的动作而FT8和FT4的较短序列需要更快的响应在实际使用中需要软件内置适量的自动化这就是FT8CN中使用自动程序的原因。
FT8CN赞同乔尔·泰勒理念反对使用机器人用于FT8操作只能有限度地使用自动化程序所以FT8CN要对自动程序进行监管当无人干预超过发射监管时间限制后发射程序将自动停止。

Wyświetl plik

@ -0,0 +1,3 @@
请输入您梅登海德网格信息至少4位如果是6位在FT8呼叫时会自动忽略后两位。
如果您给本APP授权定位的权限点击输入框旁边的定位按钮APP会自动计算出您当前所在的网格。
通过您的网格数据APP会自动计算发起呼叫的电台与您的距离。

Wyświetl plik

@ -0,0 +1,3 @@
Please enter Maidenhead grid (at least 4 characters. If it is 6 characters, the last two bits are automatically ignored while FT8 calling.
If you grant the APP the permission to locate, please click the positioning button next to the input box. The APP will automatically calculate the grid you are currently in.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,2 @@
关于无回应次数则中断呼叫
在与对方通联时基于各种原因对方没有回应您的呼叫FT8CN会以当前阶段的消息内容重复呼叫直到对方回应才进入下一阶段的呼叫。当设置无回应的次数时当达到重复呼叫的次数达到设定值时自动程序会停止对该目标的通联转入下一个目标

Wyświetl plik

@ -0,0 +1,3 @@
About call interruption due to no response
When communicating with the other side and it does not respond to your call for various reasons, FT8CN will repeat the call with the message content of the current stage. A call will not proceed to the next stage until the other side responds. After setting no reply counter, when the number of repeated calls reaches the set value, automatic program will stop communicating to the target and move on to the next target.

Wyświetl plik

@ -0,0 +1,3 @@
载波频段,是指电台在发射时所使用的载波频段。载波频段,会记录到每一个通联的日志中。
在CAT模式下APP会自动设置电台的载波频段。
在VOX模式下只用于保存日志。

Wyświetl plik

@ -0,0 +1,4 @@
Band refers to the carrier frequency band used by the RIG while transmitting. Carrier bands are recorded in the log of each connection.
In CAT mode, the APP sets the carrier band of the RIG automatically.
In VOX mode, it is only used to save logs.

Wyświetl plik

@ -0,0 +1,2 @@
PTT延迟是指本电台接收到PTT按下的指令后的响应时间。
当FT8CN发送PTT按下的指令后往往电台会稍有滞后的响应设定此时间是为了电台真正处于发射状态后FT8CN才发送声音防止因PTT滞后造成声音无法完全发送。

Wyświetl plik

@ -0,0 +1 @@
PTT delay refers to the response time after the RIG receives the instructions of PTT. FT8CN sends a PTT pressed command, the response of the RIG tends to lag slightly. The time is set to ensure that the FT8CN sends the sound after the RIG is in the transmitted state, preventing the sound from being unable to send completely due to PTT lag.

Wyświetl plik

@ -0,0 +1,296 @@
免责声明:
FT8CN旨在研究的目的学习如何对FT8信号进行解码、发射等操作不对使用者操作本APP所产生的后果负责。
在中华人民共和国境内使用FT8CN请遵守《中华人民共和国无线电管理条例》等相关规定。
考虑到手机的性能和续航的限制,对信号的处理采用轻量化的运算,未做深度解码等处理。
如有好的建议或问题可以提交到到”有问题要吐槽“。
Disclaimer
FT8CN aims to learn how to decode, transmit FT8 signal for research purposes, which is not responsible for the consequences caused by the user's operation.
Please comply with local laws and regulations when using FT8CN.
Considering the performance and endurance limitations of the mobile phone, the processing of the signal adopts lightweight operations instead of deep decoding and other processing.
Please click "FAQ" if you have good suggestions or questions .
BG7YOZ
2022-07-01
2023-08-07(0.90)
1.增加日志导入时Web界面交互模式。
2.修正当日志数据量过大时,地图崩溃的问题。
3.优化数据库结构,提升日志数据导入、更新速度(更新此版本前,建议备份日志以防不测)。
4.修正部分单词拼写错误。
5.增加电台UA3REO Wolf SDR。
6.增加电台GUOHE(国赫) PMR-171。
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.增加日志可以按条件查询并导出。
3.修改日志查询以时间为降序显示。
4.修正SWL QSO记录重复的问题。
5.针对各主流浏览器优化后台UI。
patch 2
6.修正“通联记录”定位闪退问题。
2023-03-24(0.87)
1.增加查询的通联日志结果在地图中定位显示功能。
2.增加FlexRadio仪表显示和参数设置(目前暂不支持发射)。
3.增加时间自动同步功能服务器是Microsoft NTP
4.增加SWL模式对解码消息以及QSO有保存和导出的功能(SWL的QSO认定标准至少要有双方的报告以及结束语73、RR73、RRR)。
5.丰富后台数据的查询功能。
6.修正后台查询“呼号与网格映射表”中距离计算的错误。
7.针对协谷G90S未来的新固件调整电台型号选项。
8.解决解码消息较多时界面会卡顿的问题。
9.优化日志查询性能。
2023-02-06(0.86)
1.提高日志导入的健壮性,反馈格式错误的日志信息。
2.修正在推算SNR时偶尔会出现数组下标越界造成闪退的问题。
3.修正导入日志后数量计算不准确的问题。
2023-01-28(0.85)
1.增加排除的呼号前缀功能(排除的呼号前缀的动作具有最高优先级,为自动程序排除掉不需要的呼叫)。
2.增加GridTracker中地图的日落日出灰线。
3.增加清空关注呼号列表功能。
4.增加清空缓存的通联QSO消息功能。
5.增加呼叫修饰符功能。例如 CQ POTA xxxxxx xxxx、或CQ DX xxxxxx xxxx。修饰符范围是000-999,A-Z,AA-ZZ,AAA-ZZZ,AAAA-ZZZZ。
2023-01-08(0.84)
1.优化地图的色彩模式。
2.修补因线程不同步,导致提示消息操作闪退的问题。
3.解决部分日志字段表述不标准,造成导入日志失败的问题。
4.优化呼号的哈希表处理。
2023-01-07(0.83)
1.增加自由文本发射功能。
2.修改因Execute-only memory violation错误造成的闪退问题。
3.修改某型号电台设置错误的问题。
4.修改日志导入导出操作,增加导出日志中确认字段,通过导入日志自动更新确认项。
5.排除多个内存泄漏点。
6.解决部分日志QSO中对方网格不准确的问题。
7.解决长期在网格追踪的界面下,会息屏的问题。
2022-12-31(0.8.1)
再见2022愿明天更好
注意!此版本数据库结构有更新,升级前,请导出日志备份,升级后,将无法回退到旧的版本。
注意此版本开始支持ICOM的网络控制功能建议使用电台的WIFI连接手机的AP优先选项或手机连接电台的AP。
注意!不建议使用路由器来连接,如果路由器的性能不够,会造成发射音频是丢包!!!
1.增加ICom系列电台的网络Wifi支持。
2.增加ICom系列电台驻波比、ALC值过高警告功能。
3.增加建伍TS系列电台驻波比、ALC值过高警告功能。
4.增加YAESU各系列电台驻波比、ALC值过高警告功能。
5.增加Elecraft系列电台驻波比、ALC值过高警告功能。
6.增加ICom部分电台在不同的连接方式下自动切换Connectors的Data mode。
7.增加调整信号强度功能用于调节ALC。
8.增加支持3位呼号的呼叫能力。
9.增加支持的电台。
10.增加获取最新版本的入口。https://github.com/N0BOY/FT8CN/releases
11.增加呼号与网格的映射表(升级数据库)。
12.增加地图可视化展示功能类似于GridTracker
13.增加在地图中呼叫的能力。
14.解决部分设备在处理音频数据时,因内存抖动造成程序崩溃的问题。
15.解决部分消息的QTH不准确的问题。
16.修正了在一些情况下,发射条目上有分区图标显示的问题。
17.修正了发射含有3位呼号消息时闪退的问题。
18.优化频谱图,解决低辨率下文字信息显示不正常的问题,
19.更新部分地区的坐标
20.优化消息列表处理策略,减少内存抖动。
21.修正当达到无回应阈值,切换目标呼号后,提示消息没有随之变化的问题。
22.解决部分非标准呼号地理位置无法解析的问题。
23.修正因高版本安卓及高版本ARM64的内存访问机制造成异常崩溃的问题。
2022-11-08(0.79)
1.协谷X6100的操作模式改为U-DIG模式。
2.把音频数据格式从16位整型改为32位浮点模式。
3.修正FFT过程存在内存泄漏的问题。
4.增加Flex-6000系列网络连接模式支持目前仅支持接收不支持发射。
5.增加禁止锁屏休眠。
6.控制历史消息数量暂时设置在3000条以内
7.增加全屏模式。
8.增加快速切换频率。
9.修正部分电台iCOM、协谷因数据传输质量不好造成闪退的问题。
10.修正对小于6位的非标准呼号识别错误的问题。
存在的问题:
1.连接Flex电台只能在同一网段内暂时没有增加输入IP直接连接的方式。
2022-11-18(0.79 Patch 4)
1.解决部分设备解码按钮失效的问题。
2.增加连接Flex电台可以用输入IP的方式直接连接解决跨网段连接的问题。
2022-10-06(0.78)
1.继续优化自动程序逻辑,修正自动呼叫打开时,目标不专一的问题。
2.对日志删除动作,弹出删除确认对话框。
2022-10-01(0.77)
1.修改统计按波段统计大小写被区分的问题。
2.对曾经通联过的呼号,不在当前波段的,以蓝色字体颜色显示。
3.新增电台型号。
2022-09-24(0.76)
1.调整历史通联呼号规则,以波段(波长)区分。
2.修改发射监管会自减的错误。
3.继续解决日志信号报告不准确的问题。
4.继续优化自动程序策略。
2022-09-17(0.75)
1.继续修改通联日志的信号报告问题(信号报告反了、数值不准确)。
2.增加针对安卓12申请蓝牙连接权限。
3.针对某型号电台USB线连接后指令反应不及时的问题启用延时发送指令。
4.YAESU FT450D的操作模式改为USER-U模式。
5.继续优化自动程序,调整自动程序的运行机制,自动记录日志提前。
6.退出应用时如果处于发射状态自动关闭PTT。
7.解决带哈希呼号的消息因过采样造成重复的问题。
8.增加日文、希腊、西班牙UI。
9.修正关注的消息不在同一频段内自动呼叫的错误。
2022-09-09(0.74)
1.增加英文版帮助。
2.呼号查询结果以时间降序显示。
3.ICOM电台操作模式改为USB-D模式。
4.增加对呼号的QRZ查询功能。
5.修正了日志中信号报告值不严谨的问题。
2022-09-03(0.73)
1.修正某些日志起始时间不准确的问题。
2.优化对为通联过的分区标注。
3.基于消息的历史,把没有网格报告的消息也标注出距离。
2022-08-28(0.72)
1.解决自动程序中自己呼叫自己的问题。
2.对通联成功的呼号以通联成功的频率做出区分。
3.丰富了一下后台”跟踪运行信息“内容。
4.重新增加通联记录中呼号查询列表,并调整了显示的内容。
5.解决因数组下标溢出导致闪退的问题。
6.减少权限申请,取消存储权限,保留麦克风、位置权限(可以拒绝)。
7.解决没有麦克风权限造成闪退的问题。
2022-08-27(0.71)
1.优化发射周期PTT打开的时长确保接收消息周期的完整。
2.解决Q900蓝牙发送、接收音频适配的问题真正实现蓝牙控制、音频收发能力。
3.美化在消息的分区标注。
4.新增电台支持。
5.解决有时新增消息后,消息列表不自动上移的问题。
2022-08-22(0.7)
1.加入DXCC分区数据统计。
2.加入ITU分区数据统计。
3.加入CQ分区数据统计。
4.对各频段的距离做统计。
5.对未通联过的DXCC、ITU、CQ分区呼号做标注。
6.解决前缀1位字母2位数字呼号计算不准确的问题。
2022-08-13(0.63)
1.修正了对非标准呼号的认定,解决对部分非标准呼号计算错误的问题。
2.继续优化了一些布局(尤其是横屏)。
3.增加了繁体位置信息。
2022-08-11(0.62)
1.把FT-817/818系列的工作模式由USB改为DIGI模式。
2.把发射消息回显到呼叫栏中。
3.解决部分设备在手动中断发射时,闪退的问题。
4.解决我的呼号为空时,发射闪退的问题。
5.解决某型号电台控制问题。
6.增加英文语言包。
7.优化了布局。
2022-08-06(0.6)
1.重构与电台有关的底层架构,适应多型号电台。
2.完成国赫YAESUKENWOOD部分型号电台的指令集。
3.完成通过蓝牙串口(SPP模式)进行控制功能。
4.实现对蓝牙音频的采集。
5.修改了规则,不能自己呼叫自己。
6.增加了对非标准呼号、复合呼号的支持。
7.增加了发射时,如果采集不到声音,会把发射的消息提交到呼叫列表。
2022-07-17(0.51)
1.在BA2BI的帮助下解决频段波长不正确的问题。
2.修复设置页面载波频段列表内容重复的问题。
3.解决DTR不能发射的问题。
4.增加电台频率变化后,保存电台的频率值,如果通联成功,以电台频率为准。
5.增加对WSPR-2频率的保护功能当电台选择的频率在WSPR-2的范围内禁止发射。
6.解决0.5版日志中对方呼号没有网格信息的问题。
7.解决0.5版对自动关注的CQ目标不自动呼叫的问题。
8.解决了后台无法删除关注的呼号问题。
9.添加发射、监听的进度条。
10.增加日志导入导出的同步功能并自动LoTW确认。
11.增加手工确认。
12.增加电台PTT响应延迟设定。
13.增加在消息列表中向左滑动开始快速呼叫本周期前2.5秒内起作用)。
14.对日志导出中,增加了”今天的日志“。
15.解决无法删除带斜线的呼号问题。
16.通联记录查询添加简单的过滤功能。
2022-07-10(0.5)
此版本属重大更新。完善了自动程序增加日志查询导出功能。到此基本完成一个可以具备通联能力的APP。
此外,还有如下变化:
1.修复瀑布图文字重叠问题。
2.增加电台支持,以及波特率。
3.修复当没有定位权限,启动崩溃问题。
4.增加DTR支持。
5.修复一些随时发现的小错误。
6.增加自动发射的监管。
7.增加自动关注CQ开关。
8.增加自动呼叫关注的呼号开关。
9.增加对消息偏移时间过长做标注。
存在的问题:
1.如果对方是从第2个消息开始呼号我保存的日志中就没有对方的网格其实在消息上下文中存在对方的网格信息。
2.如果自动关注CQ消息并打开自动回复关注开关当有CQ的消息时并不回复。
以上问题将在下一个版本中解决。
2022-07-02(0.44)
1.增加问题收集反馈的入口。
2.修正在设置页面会闪退的BUG。
3.把x5105加到设备列表中。
2022-07-01(0.43)
1.在BG7IKK的帮助下解决部分电台使用RTS控制PTT的问题。
2.BI1NIZ注册了一个项目问题收集反馈和FAQ的账号。
3.在频谱的标尺上加了发射频率的红色标记。
2022-06-30(0.42)
1.BH7ACO帮助解决了协谷X6100的驱动。未解决的问题协谷6100有时会莫名断开解决办法设置SSB模式的指令延迟1秒发送解决得不够理想
2022-06-29(0.41)
1.反馈705、7100、7300控制测试成功。
2.BH2RSJ帮助建立了一个APP测试群群成员在陆续反馈使用的情况提出了一些修改意见。
3.修改了一下启动方式,确保配置参数能按时读入。
4.修改了对电台修改频率会把filter变为FIL2的错误。
2022-06-27
1.增加了电台CAT控制功能目前支持部分ICOM系列电台。目前只对IC-705做测试成功因为手中没有ICOM的其他型号不清楚串口的驱动能否识别并使用。
2.查找到可以支持CI-V指令控制的ICOM电台列表以及各型号电台的默认地址。
2022-06-20
1.增加了帮助功能
2.增加了瀑布图的标记功能
3.对android 10版本以上的深色模式做了一些适配
4.更换了图标BG7YOY设计
致敬:
Steve Franke(K9AN)、Bill Somerville(G4WJS)、Joe Taylor(K1JT)提出FT8和FT4协议FT是Franke和Taylor的首字母并在论文《The FT4 and FT8 Communication Protocols》详细介绍了FT4和FT8的设计初衷和在WSJT-X中的具体实现细节成为完成本APP的根本指南。
Karlis Goba(YL3JG)在代码的具体实现上提供了参考。
鸣谢:
BG7YOY在FT8CN开发阶段为我在无线电基本理论上作出指导并为FT8CN设计了图标。
BG4IGX在我刚刚入门业余无线电时为我在具体实践上作出指导。抖音上您可以搜到很多他的教学视频。
BD7MXN帮助我对部分电台的连接控制做了一些测试并提出改进建议。
BH2RSJ帮助我建立了一个FT8CN测试群为测试和后续改进提出了很多宝贵意见。
BH7ACO帮助解决了某电台的驱动和相关的配置参数。
BG7IKK帮助解决了只支持通过RTS控制PTT发射的电台的测试。
BI1NIZ帮助注册账号用于收集问题反馈和FAQ的功能。
BD3OOX以及石家庄业余无线电俱乐部FT8CN的呼号地区归属数据提取至JTDX石家庄版使呼号定位可以精确到中国的省级。
VR2UPU(BD7MJO)在FT8的开发和使用经验上提供指导并在多语言方面给予帮助。
BA2BI在业余无线电的基础知识和通联的日志处理方面上给予帮助和指导。
BI3QXJ在对某品牌系列电台的指令集上给予专业性的指导。
BG6TQD在对某型号电台的指令集测试上给予帮助。
BG5CSS提供某型号电台用于测试。
BG7YXN提供某型号电台用于测试。
BG7YRB对呼号规则运算提供帮助。
BG8KAH提供设备用于测试。
BA7LVG完成日文的翻译校对工作。
JE6WUD完成日文的翻译校对工作。
BG6RI帮助解决日志的信号报告问题。
SV1EEX完成希腊文、西班牙文UI的翻译工作。
VR2VRC帮助修正历史呼号读取规则。
BA7NQ提供设备用于测试。
BD7MYM对某型号的电台测试给予指导。
NØBOY帮助提供Github源以及翻译工作。
BG5JNT帮助修正非标准呼号的识别问题。
BH3NEK协助对某型号电台进行测试。
BG2ALB协助对某型号电台进行测试。
BG6DRU协助对某型号电台进行测试。
BG7NQF提供某型号电台的隐藏指令对一些设备做兼容性测试。
BH2VSQ协助对某型号电台进行测试。
BG7YBW协助对部分功能惊醒测试。
BH1RNN协助对部分功能进行测试。
BG7BSM协助对一些BUG进行调试。
BH4FTI发现并协助对一些BUG进行调试。
BG8BXMM哥为FT8CN的使用做推广抖音和B站上有很多他的教学视频。
BG7MFQ为FT8CN的使用做推广帮助测试。
BG2EFX提供大数据量的日志用于测试。

Wyświetl plik

@ -0,0 +1,2 @@
关于电台型号根据选择的电台型号FT8CN有针对性的使用可以控制该电台的指令集不同的品牌的电台指令集并不相同请在列表中选择您正在使用的电台。
对于ICOM系列电台本列表中所列的是已知默认CI-V地址和最大支持波特率的各电台型号可以通过选择本列表中的电台自动设置CI-V地址和波特率。如果您使用的电台型号不在本列表中需要您自行设置合适的CI-V地址和波特率。

Wyświetl plik

@ -0,0 +1,4 @@
About Rig Model
Depending on the selected RIG model, the targeted use of FT8CN can control the instruction set of the RIG and the instruction set of different brands of RIGs is not the same. Please select the RIG you are using from the list.
For ICOM series RIGs, this list contains the known default CI-V address and the maximum support for each RIG model. You can select the RIG in this list and the CI-V address and baud rate will be set automatically. If the RIG model you are using is not in this list, you need to set the appropriate CI-V address and baud rate yourself.

Wyświetl plik

@ -0,0 +1,53 @@
ICOM IC-7000,70,19200,0
ICOM IC-705,A4,115200,0
ICOM IC-7100,88,19200,0
ICOM IC-7200,76,19200,0
ICOM IC-7300,94,115200,0
ICOM IC-7400,66,19200,0
ICOM IC-7410,80,19200,0
ICOM IC-746,56,19200,0
ICOM IC-746PRO,66,19200,0
ICOM IC-756PRO,5C,19200,0
ICOM IC-756PRO2,64,19200,0
ICOM IC-756PRO3,6E,19200,0
ICOM IC-7600,7A,19200,0
ICOM IC-7610,98,115200,0
ICOM IC-7700,74,19200,0
ICOM IC-7800,6A,19200,0
ICOM IC-7850,8E,115200,0
ICOM IC-7851,8E,115200,0
ICOM IC-9100,7C,19200,0
ICOM IC-9700,A2,115200,0
ICOM IC-R8600,96,115200,0
ICOM ID-52A,A6,115200,0
ICOM IC-706MKIIG,58,19200,0
XIEGU(协谷) X6100/G90S(U-DIG),70,19200,13
XIEGU(协谷) X6100/G90S(USB),70,19200,9
XIEGU(协谷) X5105,70,19200,9
XIEGU(协谷) X108,70,19200,9
GUOHE(国赫) Q900,00,19200,8
GUOHE(国赫) PMR-171,00,19200,8
YAESU FT-450(D),00,4800,4
YAESU FT-817,00,4800,1
YAESU FT-818,00,4800,1
YAESU FT-857,00,4800,1
YAESU FT-891,00,4800,2
YAESU FT-897(D),00,4800,1
YAESU FT-950,00,4800,3
YAESU FT-2000(D),00,4800,3
YAESU FT-991(A),00,4800,2
YAESU FT-DX10,00,38400,6
YAESU FT-DX101,00,38400,6
YAESU FT-DX Other series,00,4800,3
KENWOOD(建伍) TK-90,00,9600,5
KENWOOD(建伍) TS-480,00,9600,7
KENWOOD(建伍) TS-590,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
FlexRadio 6000 series,00,00,12
FX-4CR,00,115200,7
Qrp Labs QDX,00,9600,7
UA3REO Wolf SDR(DIGU),00,4800,15
UA3REO Wolf SDR(USB),00,4800,16

Wyświetl plik

@ -0,0 +1,7 @@
SWL是英文Shortwave Listener的简称即短波收听爱好者。
SWL模式是仅仅收听其他业余无线电爱好者的通联而不进行无线电发射的模式。
针对SWLFT8CN增加了“保存解码消息”和“保存SWL记录”功能。
保存解码消息就是把FT8CN所有解码的消息都保存下来。
保存SWL记录就是把守听到的其他业余无线电台通联的QSO日志保存下来。在FT8CN中认定成功的SWL QSO标准是有结束语73、RR73、RRR且有双方的信号报告双方的网格报告不是必须项。
保存的“解码消息”和“SWL记录”可以在后台需要在同网段的局域网用浏览器访问做查询以及导出操作。
保存的“解码消息”和“SWL记录”的删除操作在”设置界面“的”清空缓存“中操作。

Wyświetl plik

@ -0,0 +1,7 @@
SWL stands for Shortwave Listener, whom may not hold an amateur radio licence to transmit but listen and report amateur radio QSOs. FT8CN supports "Save decoded messages" and "Save SWL QSO" for SWL.
Save decoded messages: FT8CN will save all decoded messages.
Save SWL QSO: FT8CN can save QSO log as SWL when both stations provide signal report and 73/RR73/RRR (Gridsqure is optional).
You can look up and export saved decoded messages or SWL QSO log via Web UI (under the same LAN).
You can delete them using "Clear Cache Data" in settings.

Wyświetl plik

@ -0,0 +1,3 @@
时间偏移是指本APP在每个周期相对于系统时钟的偏移值。
FT8的时钟周期是15秒所有电台的时钟都是以UTC时间为基准以每分钟的第0秒、15秒、30秒、45秒为周期的起始时间。
如果可以访问网络FT8CN会自动同步时间如果不能访问网络可以手动设定偏移值。

Wyświetl plik

@ -0,0 +1,3 @@
Local clock offset:
In FT8, all stations start TX/RX at each 0/15/45/60 seconds, which require accurate time synchronization.
FT8CN will automatically sync time via internet. When internet is not available, you can adjust the local clock by changing this local clock offset.

Wyświetl plik

@ -0,0 +1,7 @@
设置发射延迟(以毫秒为单位)是为了在上一个监听周期结束后,给解码运算一个时间。
设定一个延迟可以在发射前获知上一个周期对方的反馈情况,并在本周期内做出反应。
延迟时间的设定取决于本设备的运算能力,延迟时间不能小于解码运算的时间长度。
如果延迟时间过短,可能无法对上一周期的信号做出反应。
如果延迟时间过长,其它电台可能无法正常解析出您的信号。
在实际应用中由于在给电台发送PTT指令后电台会有一个响应时间FT8CN暂定为100毫秒所以实际声音信号的发射时间是发射延迟+100毫秒设定发射延迟500毫秒实际信号的发射是500+100=600毫秒。
FT8的信号实际周期长度为12.64秒建议最大延迟时间不要超过1.08秒。

Wyświetl plik

@ -0,0 +1,7 @@
Tx delay time (MS) is set to allow time for decoding operations at the end of the last listening cycle.
Setting a delay allows the user to get feedback from the previous cycle before launch and react during this cycle.
The setting of delay time depends on the operation ability of the device, and the delay time can not be less than the length of decoding operation time.
If the delay time is too short, the device may not be able to respond to signals from the previous period.
If the delay time is too long, other RIGs may not be able to parse your signal properly.
In practice, since the RIG will have a response time (FT8CN is tentatively set at 100 milliseconds) after sending PTT instruction to the RIG, the actual transmission time of the sound signal is the transmission delay plus 100 ms. For example, if the transmission delay is set to 500 ms, the actual signal transmission time is 500 plus 100, which equals 600 ms.
The actual signal cycle length of FT8 is 12.64 seconds. It is recommended that the maximum delay time should not exceed 1.08 seconds.

Wyświetl plik

@ -0,0 +1,46 @@
package com.bg7yoz.ft8cn;
/**
* WebView
* @author BGY70Z
* @date 2023-03-20
*/
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
public class FAQActivity extends AppCompatActivity {
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_faqactivity);
WebView webView = (WebView) findViewById(R.id.faqWebView);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true); // 这个要加上
/* 获得 webview url请注意url单词是product而不是productsproducts是旧版本的参数用错地址将不能成功提交 */
//String url = "https://www.qrz.com/db/BG7YOZ";
String url = "https://support.qq.com/product/415890";
/* WebView 内嵌 Client 可以在APP内打开网页而不是跳出到浏览器 */
WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
super.shouldOverrideUrlLoading(view, url);
view.loadUrl(url);
return true;
}
};
webView.setWebViewClient(webViewClient);
webView.loadUrl(url);
}
}

Wyświetl plik

@ -0,0 +1,25 @@
package com.bg7yoz.ft8cn;
/**
* FT8
* @author BGY70Z
* @date 2023-03-20
*/
public final class FT8Common {
public static final int FT8_MODE=0;
public static final int FT4_MODE=1;
public static final int SAMPLE_RATE=12000;
public static final int FT8_SLOT_TIME=15;
public static final int FT8_SLOT_TIME_MILLISECOND=15000;//一个周期的毫秒数
public static final int FT4_SLOT_TIME_MILLISECOND=7500;
public static final int FT8_5_SYMBOLS_MILLISECOND=800;//5个符号所需的
public static final float FT4_SLOT_TIME=7.5f;
public static final int FT8_SLOT_TIME_M=150;//15秒
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;//迭代次数
}

Wyświetl plik

@ -0,0 +1,501 @@
package com.bg7yoz.ft8cn;
/**
* Ft8MessageFT8
* UTC
* ----2022.5.6-----
* time_sec
* 1.便GetString
* -----2022.5.13---
* 2.i3,n3
* @author BG7YOZ
* @date 2022.5.6
*/
import android.annotation.SuppressLint;
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;
import com.bg7yoz.ft8cn.timer.UtcTimer;
import com.google.android.gms.maps.model.LatLng;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
public class Ft8Message {
private static String TAG = "Ft8Message";
public int i3 = 0;
public int n3 = 0;
public int signalFormat = FT8Common.FT8_MODE;//是不是FT8格式的消息
public long utcTime;//UTC时间
public boolean isValid;//是否是有效信息
public int snr = 0;//信噪比
public float time_sec = 0;//时间偏移(秒)
public float freq_hz = 0;//频率
public int score = 0;//得分
public int messageHash;//消息的哈希
public String callsignFrom = null;//发起呼叫的呼号
public String callsignTo = null;//接收呼叫的呼号
public String modifier = null;//目标呼号的修饰符 如CQ POTA BG7YOZ OL50中的POTA
public String extraInfo = null;
public String maidenGrid = null;
public int report = -100;//当-100时意味着没有信号报告
public long callFromHash10 = 0;//12位长度的哈希码
public long callFromHash12 = 0;//12位长度的哈希码
public long callFromHash22 = 0;//12位长度的哈希码
public long callToHash10 = 0;//12位长度的哈希码
public long callToHash12 = 0;//12位长度的哈希码
public long callToHash22 = 0;//12位长度的哈希码
//private boolean isCallMe = false;//是不是CALL我的消息
public long band;//载波频率
public String fromWhere = null;//用于显示地址
public String toWhere = null;//用于显示地址
public boolean isQSL_Callsign = false;//是不是通联过的呼号
public static MessageHashMap hashList = new MessageHashMap();
public boolean fromDxcc = false;
public boolean fromItu = false;
public boolean fromCq = false;
public boolean toDxcc = false;
public boolean toItu = false;
public boolean toCq = false;
public LatLng fromLatLng = null;
public LatLng toLatLng = null;
public boolean isWeakSignal=false;
@NonNull
@SuppressLint({"SimpleDateFormat", "DefaultLocale"})
@Override
public String toString() {
return String.format("%s %d %+4.2f %4.0f ~ %s Hash : %#06X",
new SimpleDateFormat("HHmmss").format(utcTime),
snr, time_sec, freq_hz, getMessageText(), messageHash);
}
/**
*
*
* @param signalFormat
*/
public Ft8Message(int signalFormat) {
this.signalFormat = signalFormat;
}
public Ft8Message(String callTo, String callFrom, String extraInfo) {
//如果是自由文本callTo=CQ,callFrom=MyCall,extraInfo=freeText
this.callsignTo = callTo.toUpperCase();
this.callsignFrom = callFrom.toUpperCase();
this.extraInfo = extraInfo.toUpperCase();
}
public Ft8Message(int i3, int n3, String callTo, String callFrom, String extraInfo) {
this.callsignTo = callTo;
this.callsignFrom = callFrom;
this.extraInfo = extraInfo;
this.i3 = i3;
this.n3 = n3;
this.utcTime = UtcTimer.getSystemTime();//用于显示TX
}
/**
*
*
* @param message messagenullmessage
*/
public Ft8Message(Ft8Message message) {
if (message != null) {
signalFormat = message.signalFormat;
utcTime = message.utcTime;
isValid = message.isValid;
snr = message.snr;
time_sec = message.time_sec;
freq_hz = message.freq_hz;
score = message.score;
band = message.band;
messageHash = message.messageHash;
if (message.callsignFrom.equals("<...>")) {//到哈希列表中查一下
callsignFrom = hashList.getCallsign(new long[]{message.callFromHash10, message.callFromHash12, message.callFromHash22});
} else {
callsignFrom = message.callsignFrom;
}
if (message.callsignTo.equals("<...>")) {//到哈希列表中查一下
callsignTo = hashList.getCallsign(new long[]{message.callToHash10, message.callToHash12, message.callToHash22});
} else {
callsignTo = message.callsignTo;
}
if (message.i3 == 4) {
hashList.addHash(FT8Package.getHash22(message.callsignFrom), message.callsignFrom);
hashList.addHash(FT8Package.getHash12(message.callsignFrom), message.callsignFrom);
hashList.addHash(FT8Package.getHash10(message.callsignFrom), message.callsignFrom);
}
extraInfo = message.extraInfo;
maidenGrid = message.maidenGrid;
report = message.report;
callToHash10 = message.callToHash10;
callToHash12 = message.callToHash12;
callToHash22 = message.callToHash22;
callFromHash10 = message.callFromHash10;
callFromHash12 = message.callFromHash12;
callFromHash22 = message.callFromHash22;
i3 = message.i3;
n3 = message.n3;
//把哈希和呼号对应关系保存到列表里
hashList.addHash(callToHash10, callsignTo);
hashList.addHash(callToHash12, callsignTo);
hashList.addHash(callToHash22, callsignTo);
hashList.addHash(callFromHash10, callsignFrom);
hashList.addHash(callFromHash12, callsignFrom);
hashList.addHash(callFromHash22, callsignFrom);
//Log.d(TAG, String.format("i3:%d,n3:%d,From:%s,To:%s", i3, n3, getCallsignFrom(), getCallsignTo()));
}
}
/**
* 使
*
* @return String 便
*/
@SuppressLint("DefaultLocale")
public String getFreq_hz() {
return String.format("%04.0f", freq_hz);
}
public String getMessageText(boolean showWeekSignal){
if (isWeakSignal && showWeekSignal){
return "*"+getMessageText();
}else {
return getMessageText();
}
}
/**
*
*
* @return String
*/
public String getMessageText() {
if (i3 == 0 && n3 == 0) {//说明是自由文本
if (extraInfo.length() < 13) {
return String.format("%-13s", extraInfo.toUpperCase());
} else {
return extraInfo.toUpperCase().substring(0, 13);
}
}
if (modifier != null && checkIsCQ()) {//修饰符
if (modifier.matches("[0-9]{3}|[A-Z]{1,4}")) {
return String.format("%s %s %s %s", callsignTo, modifier, callsignFrom, extraInfo).trim();
}
}
return String.format("%s %s %s", callsignTo, callsignFrom, extraInfo).trim();
}
/**
*
*
* @return
*/
@SuppressLint("DefaultLocale")
public String getMessageTextWithDb() {
return String.format("%d %s %s %s", snr, callsignTo, callsignFrom, extraInfo).trim();
}
/**
*
*
* @return String 便
*/
@SuppressLint("DefaultLocale")
public String getDt() {
return String.format("%.1f", time_sec);
}
/**
* dB000
*
* @return String 便
*/
public String getdB() {
return String.valueOf(snr);
}
/**
*
*
* @return boolean true0,30true
*/
public boolean isEvenSequence() {
if (signalFormat == FT8Common.FT8_MODE) {
return (utcTime / 1000) % 15 == 0;
} else {
return (utcTime / 100) % 75 == 0;
}
}
/**
*
*
* @return String
*/
@SuppressLint("DefaultLocale")
public int getSequence() {
if (signalFormat == FT8Common.FT8_MODE) {
return (int) ((((utcTime + 750) / 1000) / 15) % 2);
} else {
return (int) (((utcTime + 370) / 100) / 75) % 2;
}
}
@SuppressLint("DefaultLocale")
public int getSequence4() {
if (signalFormat == FT8Common.FT8_MODE) {
return (int) ((((utcTime + 750) / 1000) / 15) % 4);
} else {
return (int) (((utcTime + 370) / 100) / 75) % 4;
}
}
/**
* mycall
*
* @return boolean
*/
public boolean inMyCall() {
if (GeneralVariables.myCallsign.length() == 0) return false;
return this.callsignFrom.contains(GeneralVariables.myCallsign)
|| this.callsignTo.contains(GeneralVariables.myCallsign);
//return (this.callsignFrom.contains(mycall) || this.callsignTo.contains(mycall)) && (!mycall.equals(""));
}
/*
i3.n3
0.0 Free Text TNX BOB 73 GL f71
0.1 DXpedition K1ABC RR73; W9XYZ <KH1/KH7Z> -08 c28 c28 h10 r5
0.3 Field Day K1ABC W9XYZ 6A WI c28 c28 R1 n4 k3 S7
0.4 Field Day W9XYZ K1ABC R 17B EMA c28 c28 R1 n4 k3 S7
0.5 Telemetry 123456789ABCDEF012 t71
1. Std Msg K1ABC/R W9XYZ/R R EN37 c28 r1 c28 r1 R1 g15
2. EU VHF G4ABC/P PA9XYZ JO22 c28 p1 c28 p1 R1 g15
3. RTTY RU K1ABC W9XYZ 579 WI t1 c28 c28 R1 r3 s13
4. NonStd Call <W9XYZ> PJ4/K1ABC RRR h12 c58 h1 r2 c1
5. EU VHF <G4ABC> <PA9XYZ> R 570007 JO22DB h12 h22 R1 r3 s11 g25
*/
/*
c1 CQh12
c28 CQDEQRZ22
c58 11
f71 13
g15 4RRRRR7373
g25 6
h1
h10 10
h12 12
h22 22
k3 ClassABF
n4 1-1617-32
p1 /P
r1 /R
r2 RRRRR7373
r3 2-9529-59952-59
R1 R
r5 -30+30
s11 0-2047
s13 0-7999/
S7 ARRL/RAC
t1 TU;
t71 18
*/
/**
* fromTodecode.c---TO DO----
* i1i2,i3,i4,i5,i0.1,i0.3,i0.4
*
* @return String
*/
public String getCallsignFrom() {
if (callsignFrom == null) {
return "";
}
return callsignFrom.replace("<", "").replace(">", "");
}
/**
*
*
* @return
*/
public String getCallsignTo() {
if (callsignTo == null) {
return "";
}
if (callsignTo.length() < 2) {
return "";
}
if (callsignTo.substring(0, 2).equals("CQ") || callsignTo.substring(0, 2).equals("DE")
|| callsignTo.substring(0, 3).equals("QRZ")) {
return "";
}
return callsignTo.replace("<", "").replace(">", "");
}
/**
*
*
* @return String""
*/
public String getMaidenheadGrid(DatabaseOpr db) {
if (i3 != 1 && i3 != 2) {//一般只有i3=1或i3=2标准消息甚高频消息才有网格
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
} else {
String[] msg = getMessageText().split(" ");
if (msg.length < 1) {
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
}
String s = msg[msg.length - 1];
if (MaidenheadGrid.checkMaidenhead(s)) {
return s;
} else {//不是网格信息,就可能是信号报告
return GeneralVariables.getGridByCallsign(callsignFrom, db);//到对应表中找一下网格
}
}
}
public String getToMaidenheadGrid(DatabaseOpr db) {
if (checkIsCQ()) return "";
return GeneralVariables.getGridByCallsign(callsignTo, db);
}
/**
* CQ
*
* @return boolean CQtrue
*/
public boolean checkIsCQ() {
String s = callsignTo.trim().split(" ")[0];
if (s == null) {
return false;
} else {
return (s.equals("CQ") || s.equals("DE") || s.equals("QRZ"));
}
}
/**
* i3.n3
*
* @return
*/
public String getCommandInfo() {
return getCommandInfoByI3N3(i3, n3);
}
/**
* i3.n3
*
* @param i i3
* @param n n3
* @return
*/
@SuppressLint("DefaultLocale")
public static String getCommandInfoByI3N3(int i, int n) {
String format = "%d.%d:%s";
switch (i) {
case 1:
case 2:
return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.std_msg));
case 5:
case 3:
case 4:
return String.format(format, i, 0, GeneralVariables.getStringFromResource(R.string.none_std_msg));
case 0:
switch (n) {
case 0:
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.free_text));
case 1:
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.dXpedition));
case 3:
case 4:
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.field_day));
case 5:
return String.format(format, i, n, GeneralVariables.getStringFromResource(R.string.telemetry));
}
}
return "";
}
//获取发送者的传输对象
public TransmitCallsign getFromCallTransmitCallsign() {
return new TransmitCallsign(this.i3, this.n3, this.callsignFrom, freq_hz
, this.getSequence()
, snr);
}
//获取发送者的传输对象,注意!!!与发送者的时序是相反的!!!
public TransmitCallsign getToCallTransmitCallsign() {
if (report == -100) {//如果消息中没有信号报告就用发送方的SNR代替
return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, snr);
} else {
return new TransmitCallsign(this.i3, this.n3, this.callsignTo, freq_hz, (this.getSequence() + 1) % 2, report);
}
}
@SuppressLint("DefaultLocale")
public String toHtml() {
StringBuilder result = new StringBuilder();
result.append("<td class=\"default\" >");
result.append(UtcTimer.getDatetimeStr(utcTime));
result.append("</td>\n");
result.append("<td class=\"default\" >");
result.append(getdB());
result.append("</td>\n");
result.append("<td class=\"default\" >");
result.append(String.format("%.1f", time_sec));
result.append("</td>\n");
result.append("<td class=\"default\" >");
result.append(String.format("%.0f", freq_hz));
result.append("</td>\n");
result.append("<td class=\"default\" >");
result.append(getMessageText());
result.append("</td>\n");
result.append("<td class=\"default\" >");
result.append(BaseRigOperation.getFrequencyStr(band));
result.append("</td>\n");
return result.toString();
}
}

Wyświetl plik

@ -0,0 +1,587 @@
package com.bg7yoz.ft8cn;
/**
* mainContext
* mainContext
*/
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.lifecycle.MutableLiveData;
import com.bg7yoz.ft8cn.callsign.CallsignDatabase;
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GeneralVariables {
private static final String TAG = "GeneralVariables";
public static String VERSION = BuildConfig.VERSION_NAME;//版本号"0.62Beta 4";
public static String BUILD_DATE = BuildConfig.apkBuildTime;//编译的时间
public static int MESSAGE_COUNT = 3000;//消息的最大缓存数量
public static boolean saveSWLMessage=false;//保存解码消息开关
public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关
public static boolean deepDecodeMode=false;//是否开启深度解码
public static boolean audioOutput32Bit =true;//音频输出类型true=float,false=int16
public static int audioSampleRate=12000;//发射音频的采样率
public static MutableLiveData<Float> mutableVolumePercent = new MutableLiveData<>();
public static float volumePercent = 0.5f;//播放音频的音量,是百分比
public static int flexMaxRfPower=10;//flex电台的最大发射功率
public static int flexMaxTunePower=10;//flex电台的最大调谐功率
private Context mainContext;
public static CallsignDatabase callsignDatabase = null;
public void setMainContext(Context context) {
mainContext = context;
}
public static boolean isChina = true;//语言是不是中国
public static boolean isTraditionalChinese = true;//语言是不是繁体中文
//public static double maxDist = 0;//最远距离
//各已经通联的分区列表
public static final Map<String, String> dxccMap = new HashMap<>();
public static final Map<Integer, Integer> cqMap = new HashMap<>();
public static final Map<Integer, Integer> ituMap = new HashMap<>();
private static final Map<String, Integer> excludedCallsigns = new HashMap<>();
/**
*
*
* @param callsigns
*/
public static synchronized void addExcludedCallsigns(String callsigns) {
excludedCallsigns.clear();
String[] s = callsigns.toUpperCase().replace(" ", ",")
.replace("|", ",")
.replace("", ",").split(",");
for (int i = 0; i < s.length; i++) {
if (s[i].length() > 0) {
excludedCallsigns.put(s[i], 0);
}
}
}
/**
*
*
* @param callsign
* @return
*/
public static synchronized boolean checkIsExcludeCallsign(String callsign) {
Iterator<String> iterator = excludedCallsigns.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (callsign.toUpperCase().indexOf(key) == 0) {
return true;
}
}
return false;
}
/**
*
*
* @return
*/
public static synchronized String getExcludeCallsigns() {
StringBuilder calls = new StringBuilder();
Iterator<String> iterator = excludedCallsigns.keySet().iterator();
int i = 0;
while (iterator.hasNext()) {
String key = iterator.next();
if (i == 0) {
calls.append(key);
} else {
calls.append(",").append(key);
}
i++;
}
return calls.toString();
}
//通联记录列表,包括成功与不成功的
public static QslRecordList qslRecordList = new QslRecordList();
//此处有内存泄露警告但Application Context不应该会内存泄露所以注释掉
@SuppressLint("StaticFieldLeak")
private static GeneralVariables generalVariables = null;
public static GeneralVariables getInstance() {
if (generalVariables == null) {
generalVariables = new GeneralVariables();
}
return generalVariables;
}
public static Context getMainContext() {
return GeneralVariables.getInstance().mainContext;
}
public static MutableLiveData<String> mutableDebugMessage = new MutableLiveData<>();
public static int QUERY_FREQ_TIMEOUT = 2000;//轮询频率变化的时间间隔。2秒
public static int START_QUERY_FREQ_DELAY = 2000;//开始轮询频率的时间延迟
public static final int DEFAULT_LAUNCH_SUPERVISION = 10 * 60 * 1000;//发射监管默认值,10分钟
private static String myMaidenheadGrid = "";
public static MutableLiveData<String> mutableMyMaidenheadGrid = new MutableLiveData<>();
public static int connectMode = ConnectMode.USB_CABLE;//连接方式USB==0,BLUE_TOOTH==1
//public static String bluetoothDeviceAddress=null;//可以用于连接的蓝牙设备地址
//用于记录呼号于网格的对应关系 todo---应当把此处列表也放到后台追踪信息里
//public static ArrayList<CallsignMaidenheadGrid> callsignMaidenheadGrids=new ArrayList<>();
public static final Map<String, String> callsignAndGrids = new ConcurrentHashMap<>();
//private static final Map<String,String> callsignAndGrids=new HashMap<>();
public static String myCallsign = "";//我的呼号
public static String toModifier = "";//呼叫的修饰符
private static float baseFrequency = 1000;//声音频率
public static MutableLiveData<Float> mutableBaseFrequency = new MutableLiveData<>();
public static boolean synFrequency = false;//同频发射
public static int transmitDelay = 500;//发射延迟时间,这个时间也是给上一个周期的解码时间
public static int pttDelay = 100;//PTT的响应时间在给电台PTT指令后一般电台会有一个响应时间此处默认是100毫秒
public static int civAddress = 0xa4;//civ地址
public static int baudRate = 19200;//波特率
public static long band = 14074000;//载波频段
public static int instructionSet = 0;//指令集0:icom1:yaesu 2 代2:yaesu 3代。
public static int bandListIndex = -1;//电台波段的索引值
public static MutableLiveData<Integer> mutableBandChange = new MutableLiveData<>();//波段索引值变化
public static int controlMode = ControlMode.VOX;
public static int modelNo = 0;
public static int launchSupervision = DEFAULT_LAUNCH_SUPERVISION;//发射监管
public static long launchSupervisionStart = UtcTimer.getSystemTime();//自动发射的起始时间
public static int noReplyLimit = 0;//呼叫无回应次数0==忽略
public static int noReplyCount = 0;//没有回应的次数
//下面4个参数是ICOM网络方式连接的参数
public static String icomIp = "255.255.255.255";
public static int icomUdpPort = 50001;
public static String icomUserName = "ic705";
public static String icomPassword = "";
public static boolean autoFollowCQ = true;//自动关注CQ
public static boolean autoCallFollow = true;//自动呼叫关注的呼号
public static ArrayList<String> QSL_Callsign_list = new ArrayList<>();//QSL成功的呼号
public static ArrayList<String> QSL_Callsign_list_other_band = new ArrayList<>();//在其它波段QSL成功的呼号
public static final ArrayList<String> followCallsign = new ArrayList<>();//关注的呼号
public static ArrayList<Ft8Message> transmitMessages = new ArrayList<>();//放在呼叫界面,关注的列表
public static void setMyMaidenheadGrid(String grid) {
myMaidenheadGrid = grid;
mutableMyMaidenheadGrid.postValue(grid);
}
public static String getMyMaidenheadGrid() {
return myMaidenheadGrid;
}
public static float getBaseFrequency() {
return baseFrequency;
}
public static void setBaseFrequency(float baseFrequency) {
mutableBaseFrequency.postValue(baseFrequency);
GeneralVariables.baseFrequency = baseFrequency;
}
@SuppressLint("DefaultLocale")
public static String getBaseFrequencyStr() {
return String.format("%.0f", baseFrequency);
}
public static String getCivAddressStr() {
return String.format("%2X", civAddress);
}
public static String getTransmitDelayStr() {
return String.valueOf(transmitDelay);
}
public static String getBandString() {
return BaseRigOperation.getFrequencyAllInfo(band);
}
/**
*
*
* @param callsign
* @return
*/
public static boolean checkQSLCallsign(String callsign) {
return QSL_Callsign_list.contains(callsign);
}
/**
*
*
* @param callsign
* @return
*/
public static boolean checkQSLCallsign_OtherBand(String callsign) {
return QSL_Callsign_list_other_band.contains(callsign);
}
/**
*
*
* @param callsign
* @return
*/
public static boolean callsignInFollow(String callsign) {
return followCallsign.contains(callsign);
}
/**
*
*
* @param callsign
*/
public static void addQSLCallsign(String callsign) {
if (!checkQSLCallsign(callsign)) {
QSL_Callsign_list.add(callsign);
}
}
public static String getMyMaidenhead4Grid() {
if (myMaidenheadGrid.length() > 4) {
return myMaidenheadGrid.substring(0, 4);
}
return myMaidenheadGrid;
}
/**
*
*/
public static void resetLaunchSupervision() {
launchSupervisionStart = UtcTimer.getSystemTime();
}
/**
*
*
* @return
*/
public static int launchSupervisionCount() {
return (int) (UtcTimer.getSystemTime() - launchSupervisionStart);
}
public static boolean isLaunchSupervisionTimeout() {
if (launchSupervision == 0) return false;//0是不监管
return launchSupervisionCount() > launchSupervision;
}
/**
* extraInfo
*
* @param extraInfo
* @return
*/
public static int checkFunOrderByExtraInfo(String extraInfo) {
if (checkFun5(extraInfo)) return 5;
if (checkFun4(extraInfo)) return 4;
if (checkFun3(extraInfo)) return 3;
if (checkFun2(extraInfo)) return 2;
if (checkFun1(extraInfo)) return 1;
return -1;
}
/**
* -1
*
* @param message
* @return
*/
public static int checkFunOrder(Ft8Message message) {
if (message.checkIsCQ()) return 6;
return checkFunOrderByExtraInfo(message.extraInfo);
}
//是不是网格报告
public static boolean checkFun1(String extraInfo) {
//网格报告必须是4位,或没有网格
return (extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]") && !extraInfo.equals("RR73"))
|| (extraInfo.trim().length() == 0);
}
//是不是信号报告,如-10
public static boolean checkFun2(String extraInfo) {
if (extraInfo.trim().length() < 2) {
return false;
}//信号报告必须至少2位
try {
return Integer.parseInt(extraInfo.trim()) != 73;//如果是73说明是消息6不是消息2
//return true;
} catch (Exception e) {
return false;
}
}
//是不是带R的信号报告,如R-10
public static boolean checkFun3(String extraInfo) {
if (extraInfo.trim().length() < 3) {
return false;
}//带R信号报告必须至少3位
//第一位如果不是R或者第二位是R说明不是消息3
if ((extraInfo.trim().charAt(0) != 'R') || (extraInfo.trim().charAt(1) == 'R')) {
return false;
}
try {
Integer.parseInt(extraInfo.trim().substring(1));
return true;
} catch (Exception e) {
return false;
}
}
//是不是RRR或RR73值
public static boolean checkFun4(String extraInfo) {
return extraInfo.trim().equals("RR73") || extraInfo.trim().equals("RRR");
}
//是不是73值
public static boolean checkFun5(String extraInfo) {
return extraInfo.trim().equals("73");
}
/**
* report
* @param extraInfo
* @return ,-100
*/
public static int checkFun2_3(String extraInfo){
if (extraInfo.equals("73")) return -100;
if (extraInfo.matches("[R]?[+-]?[0-9]{1,2}")){
try {
return Integer.parseInt(extraInfo.replace("R",""));
} catch (Exception e) {
return -100;
}
}
return -100;
}
/**
* report
* @param extraInfo
* @return
*/
public static boolean checkFun1_6(String extraInfo){
return extraInfo.trim().matches("[A-Z][A-Z][0-9][0-9]")
&& !extraInfo.trim().equals("RR73");
}
/**
* RRRRR7373
* @param extraInfo
* @return
*/
public static boolean checkFun4_5(String extraInfo){
return extraInfo.trim().equals("RR73")
|| extraInfo.trim().equals("RRR")
||extraInfo.trim().equals("73");
}
/**
* String.xml
*
* @param id id
* @return
*/
public static String getStringFromResource(int id) {
if (getMainContext() != null) {
return getMainContext().getString(id);
} else {
return "";
}
}
/**
* DXCC
*
* @param dxccPrefix DXCC
*/
public static void addDxcc(String dxccPrefix) {
dxccMap.put(dxccPrefix, dxccPrefix);
}
/**
* DXCC
*
* @param dxccPrefix DXCC
* @return
*/
public static boolean getDxccByPrefix(String dxccPrefix) {
return dxccMap.containsKey(dxccPrefix);
}
/**
* CQ
*
* @param cqZone cq
*/
public static void addCqZone(int cqZone) {
cqMap.put(cqZone, cqZone);
}
/**
* CQ
*
* @param cq cq
* @return
*/
public static boolean getCqZoneById(int cq) {
return cqMap.containsKey(cq);
}
/**
* ituITU
*
* @param itu itu
*/
public static void addItuZone(int itu) {
ituMap.put(itu, itu);
}
/**
* Itu
*
* @param itu itu
* @return
*/
public static boolean getItuZoneById(int itu) {
return ituMap.containsKey(itu);
}
//用于触发新的网格
public static MutableLiveData<String> mutableNewGrid = new MutableLiveData<>();
/**
* --
*
* @param callsign
* @param grid
*/
public static void addCallsignAndGrid(String callsign, String grid) {
if (grid.length() >= 4) {
callsignAndGrids.put(callsign, grid);
mutableNewGrid.postValue(grid);
}
}
/**
* --
*
*
* @param callsign
* @return
*/
public static boolean getCallsignHasGrid(String callsign) {
return callsignAndGrids.containsKey(callsign);
}
/**
* --
*
*
* @param callsign
* @param grid
* @return
*/
public static boolean getCallsignHasGrid(String callsign, String grid) {
if (!callsignAndGrids.containsKey(callsign)) return false;//说明根本没有这个呼号
String s = callsignAndGrids.get(callsign);
if (s == null) return false;
return s.equals(grid);
}
public static String getGridByCallsign(String callsign, DatabaseOpr db) {
String s = callsign.replace("<", "").replace(">", "");
if (getCallsignHasGrid(s)) {
return callsignAndGrids.get(s);
} else {
db.getCallsignQTH(callsign);
return "";
}
}
/**
* --HTML
*
* @return HTML
*/
public static String getCallsignAndGridToHTML() {
StringBuilder result = new StringBuilder();
int order = 0;
for (String key : callsignAndGrids.keySet()) {
order++;
HtmlContext.tableKeyRow(result,order % 2 != 0,key,callsignAndGrids.get(key));
}
return result.toString();
}
public static synchronized void deleteArrayListMore(ArrayList<Ft8Message> list) {
if (list.size() > GeneralVariables.MESSAGE_COUNT) {
while (list.size() > GeneralVariables.MESSAGE_COUNT) {
list.remove(0);
}
}
}
/**
*
*
* @param str
* @return true, false
*/
public static boolean isInteger(String str) {
if (str != null && !"".equals(str.trim()))
return str.matches("^[0-9]*$");
else
return false;
}
/**
*
*/
public enum AudioOutputBitMode{
Float32,
Int16
}
}

Wyświetl plik

@ -0,0 +1,660 @@
package com.bg7yoz.ft8cn;
/**
* FT8CNActivityAPPFragmentFragment
* ----2022.5.6-----
*
* 1.MainViewModelMainViewModel
* 2.
* 3.Fragment
* 4.USB
* @author BG7YOZ
* @date 2022.5.6
*/
import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.bg7yoz.ft8cn.bluetooth.BluetoothStateBroadcastReceive;
import com.bg7yoz.ft8cn.callsign.CallsignDatabase;
import com.bg7yoz.ft8cn.connector.CableSerialPort;
import com.bg7yoz.ft8cn.database.DatabaseOpr;
import com.bg7yoz.ft8cn.database.OnAfterQueryConfig;
import com.bg7yoz.ft8cn.database.OperationBand;
import com.bg7yoz.ft8cn.databinding.MainActivityBinding;
import com.bg7yoz.ft8cn.floatview.FloatView;
import com.bg7yoz.ft8cn.floatview.FloatViewButton;
import com.bg7yoz.ft8cn.grid_tracker.GridTrackerMainActivity;
import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid;
import com.bg7yoz.ft8cn.timer.UtcTimer;
import com.bg7yoz.ft8cn.ui.FreqDialog;
import com.bg7yoz.ft8cn.ui.SetVolumeDialog;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private BluetoothStateBroadcastReceive mReceive;
private static final String TAG = "MainActivity";
private MainViewModel mainViewModel;
private NavController navController;
private static boolean animatorRunned = false;
//private boolean animationEnd = false;
private MainActivityBinding binding;
private FloatView floatView;
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO
, Manifest.permission.ACCESS_COARSE_LOCATION
, Manifest.permission.ACCESS_WIFI_STATE
, Manifest.permission.BLUETOOTH
, Manifest.permission.BLUETOOTH_ADMIN
, Manifest.permission.MODIFY_AUDIO_SETTINGS
, Manifest.permission.WAKE_LOCK
, Manifest.permission.ACCESS_FINE_LOCATION};
List<String> mPermissionList = new ArrayList<>();
private static final int PERMISSION_REQUEST = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions = new String[]{Manifest.permission.RECORD_AUDIO
, Manifest.permission.ACCESS_COARSE_LOCATION
, Manifest.permission.ACCESS_WIFI_STATE
, Manifest.permission.BLUETOOTH
, Manifest.permission.BLUETOOTH_ADMIN
, Manifest.permission.BLUETOOTH_CONNECT
, Manifest.permission.MODIFY_AUDIO_SETTINGS
, Manifest.permission.WAKE_LOCK
, Manifest.permission.ACCESS_FINE_LOCATION};
}
checkPermission();
//全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//禁止休眠
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState);
GeneralVariables.getInstance().setMainContext(getApplicationContext());
//判断是不是简体中文
GeneralVariables.isTraditionalChinese =
getResources().getConfiguration().locale.getDisplayCountry().equals("中國");
//确定是不是中国、香港、澳门、台湾
GeneralVariables.isChina = (getResources().getConfiguration().locale
.getLanguage().toUpperCase().startsWith("ZH"));
mainViewModel = MainViewModel.getInstance(this);
binding = MainActivityBinding.inflate(getLayoutInflater());
binding.initDataLayout.setVisibility(View.VISIBLE);//显示LOG页面
setContentView(binding.getRoot());
ToastMessage.getInstance();
registerBluetoothReceiver();//注册蓝牙动作改变的广播
if (mainViewModel.isBTConnected()) {
mainViewModel.setBlueToothOn();
}
//观察DEBUG信息
GeneralVariables.mutableDebugMessage.observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
if (s.length() > 1) {
binding.debugLayout.setVisibility(View.VISIBLE);
} else {
binding.debugLayout.setVisibility(View.GONE);
}
binding.debugMessageTextView.setText(s);
}
});
binding.debugLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
binding.debugLayout.setVisibility(View.GONE);
}
});
mainViewModel.mutableIsRecording.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
if (aBoolean) {
binding.utcProgressBar.setVisibility(View.VISIBLE);
} else {
binding.utcProgressBar.setVisibility(View.GONE);
}
}
});
//观察时钟的变化,显示进度条
mainViewModel.timerSec.observe(this, new Observer<Long>() {
@Override
public void onChanged(Long aLong) {
if (mainViewModel.ft8TransmitSignal.sequential == UtcTimer.getNowSequential()
&& mainViewModel.ft8TransmitSignal.isActivated()) {
binding.utcProgressBar.setBackgroundColor(getColor(R.color.calling_list_isMyCall_color));
} else {
binding.utcProgressBar.setBackgroundColor(getColor(R.color.progresss_bar_back_color));
}
binding.utcProgressBar.setProgress((int) ((aLong / 1000) % 15));
}
});
//添加点击发射消息提示窗口点击关闭动作
binding.transmittingLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
binding.transmittingLayout.setVisibility(View.GONE);
}
});
//清空缓存中的文件
//deleteFolderFile(this.getCacheDir().getPath());
//Log.e(TAG, this.getCacheDir().getPath());
//用于Fragment的导航。
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView);
assert navHostFragment != null;//断言不为空
navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(binding.navView, navController);
//此处增加回调是因为当APP主动navigation后无法回到解码的界面
binding.navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
//Log.e(TAG, "onNavigationItemSelected: "+item.toString() );
navController.navigate(item.getItemId());
//binding.navView.setLabelFor(item.getItemId());
return true;
}
});
//FT8CN Ver %s\nBG7YOZ\n%s
binding.welcomTextView.setText(String.format(getString(R.string.version_info)
, GeneralVariables.VERSION, GeneralVariables.BUILD_DATE));
floatView = new FloatView(this, 32);
if (!animatorRunned) {
animationImage();
animatorRunned = true;
} else {
binding.initDataLayout.setVisibility(View.GONE);
InitFloatView();
}
//初始化数据
InitData();
//观察是不是flex radio
mainViewModel.mutableIsFlexRadio.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
//if (floatView==null) return;
if (aBoolean) {
//添加flex配置按钮
floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon
, new View.OnClickListener() {
@Override
public void onClick(View view) {
navController.navigate(R.id.flexRadioInfoFragment);
// if (mainViewModel.baseRig != null) {
// if (mainViewModel.baseRig.isConnected()) {
// ToastMessage.show("flex connected");
// }else {
// ToastMessage.show("flex disconnected");
// }
// }else {
// ToastMessage.show("rig is null");
// }
}
});
} else {//删除flex配置按钮
floatView.deleteButtonByName("flex_radio");
}
}
});
//关闭串口设备列表按钮
binding.closeSelectSerialPortImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
binding.selectSerialPortLayout.setVisibility(View.GONE);
}
});
//观察串口设备列表的变化
mainViewModel.mutableSerialPorts.observe(this, new Observer<ArrayList<CableSerialPort.SerialPort>>() {
@Override
public void onChanged(ArrayList<CableSerialPort.SerialPort> serialPorts) {
setSelectUsbDevice();
}
});
//列USB设备列表
mainViewModel.getUsbDevice();
//设置发射消息框的动画
binding.transmittingMessageTextView.setAnimation(AnimationUtils.loadAnimation(this
, R.anim.view_blink));
//观察发射的状态
mainViewModel.ft8TransmitSignal.mutableIsTransmitting.observe(this,
new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
if (aBoolean) {
binding.transmittingLayout.setVisibility(View.VISIBLE);
} else {
binding.transmittingLayout.setVisibility(View.GONE);
}
}
});
//观察发射内容的变化
mainViewModel.ft8TransmitSignal.mutableTransmittingMessage.observe(this,
new Observer<String>() {
@Override
public void onChanged(String s) {
binding.transmittingMessageTextView.setText(s);
}
});
}
/**
*
*/
private void InitFloatView() {
//floatView = new FloatView(this, 32);
binding.container.addView(floatView);
floatView.setButtonMargin(0);
floatView.setFloatBoard(FloatView.FLOAT_BOARD.RIGHT);
floatView.setButtonBackgroundResourceId(R.drawable.float_button_style);
//动态添加按钮建议使用静态的ID静态ID在VALUES/FLOAT_BUTTON_IDS.XML中设置
floatView.addButton(R.id.float_nav, "float_nav", R.drawable.ic_baseline_fullscreen_24
, new View.OnClickListener() {
@Override
public void onClick(View view) {
FloatViewButton button = floatView.getButtonByName("float_nav");
if (binding.navView.getVisibility() == View.VISIBLE) {
binding.navView.setVisibility(View.GONE);
if (button != null) {
button.setImageResource(R.drawable.ic_baseline_fullscreen_exit_24);
}
} else {
binding.navView.setVisibility(View.VISIBLE);
if (button != null) {
button.setImageResource(R.drawable.ic_baseline_fullscreen_24);
}
}
}
});
floatView.addButton(R.id.float_freq, "float_freq", R.drawable.ic_baseline_freq_24
, new View.OnClickListener() {
@Override
public void onClick(View view) {
new FreqDialog(binding.container.getContext(), mainViewModel).show();
}
});
floatView.addButton(R.id.set_volume, "set_volume", R.drawable.ic_baseline_volume_up_24
, new View.OnClickListener() {
@Override
public void onClick(View view) {
new SetVolumeDialog(binding.container.getContext(), mainViewModel).show();
}
});
//打开网格追踪
floatView.addButton(R.id.grid_tracker, "grid_tracker", R.drawable.ic_baseline_grid_tracker_24
, new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getApplicationContext(), GridTrackerMainActivity.class);
startActivity(intent);
}
});
// floatView.addButton(R.id.flex_radio, "flex_radio", R.drawable.flex_icon
// , new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// navController.navigate(R.id.flexRadioInfoFragment);
// }
// });
floatView.initLocation();
}
/**
*
*/
private void InitData() {
if (mainViewModel.configIsLoaded) return;//如果数据已经读取一遍了,就不用再读取了。
//读取波段数据
if (mainViewModel.operationBand == null) {
mainViewModel.operationBand = OperationBand.getInstance(getBaseContext());
}
mainViewModel.databaseOpr.getQslDxccToMap();
//获取所有的配置参数
mainViewModel.databaseOpr.getAllConfigParameter(new OnAfterQueryConfig() {
@Override
public void doOnBeforeQueryConfig(String KeyName) {
}
@Override
public void doOnAfterQueryConfig(String KeyName, String Value) {
mainViewModel.configIsLoaded = true;
//此处梅登海德已经通过数据库得到了但是如果GPS能获取到还是用GPS的
String grid = MaidenheadGrid.getMyMaidenheadGrid(getApplicationContext());
if (!grid.equals("")) {//说明获取到了GPS数据
GeneralVariables.setMyMaidenheadGrid(grid);
//写到数据库中
mainViewModel.databaseOpr.writeConfig("grid", grid, null);
}
mainViewModel.ft8TransmitSignal.setTimer_sec(GeneralVariables.transmitDelay);
//如果呼号、网格为空,就进入设置界面
if (GeneralVariables.getMyMaidenheadGrid().equals("")
|| GeneralVariables.myCallsign.equals("")) {
runOnUiThread(new Runnable() {
@Override
public void run() {//导航到设置页面
navController.navigate(R.id.menu_nav_config);
}
});
}
}
});
//把历史中通联成功的呼号与网格的对应关系
new DatabaseOpr.GetCallsignMapGrid(mainViewModel.databaseOpr.getDb()).execute();
mainViewModel.getFollowCallsignsFromDataBase();
//打开呼号位置信息的数据库,目前是以内存数据库方式。
if (GeneralVariables.callsignDatabase == null) {
GeneralVariables.callsignDatabase = CallsignDatabase.getInstance(getBaseContext(), null, 1);
}
}
/**
*
*/
private void checkPermission() {
mPermissionList.clear();
//判断哪些权限未授予
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
mPermissionList.add(permission);
}
}
//判断是否为空
if (!mPermissionList.isEmpty()) {//请求权限方法
String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);//将List转为数组
ActivityCompat.requestPermissions(MainActivity.this, permissions, PERMISSION_REQUEST);
}
}
/**
*
*
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != PERMISSION_REQUEST) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
*
*/
public void setSelectUsbDevice() {
ArrayList<CableSerialPort.SerialPort> ports = mainViewModel.mutableSerialPorts.getValue();
binding.selectSerialPortLinearLayout.removeAllViews();
for (int i = 0; i < ports.size(); i++) {//动态添加串口设备列表
View layout = LayoutInflater.from(getApplicationContext())
.inflate(R.layout.select_serial_port_list_view_item, null);
layout.setId(i);
TextView textView = layout.findViewById(R.id.selectSerialPortListViewItemTextView);
textView.setText(ports.get(i).information());
binding.selectSerialPortLinearLayout.addView(layout);
layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//连接电台并做电台的频率设置等操作
mainViewModel.connectCableRig(getApplicationContext(), ports.get(view.getId()));
binding.selectSerialPortLayout.setVisibility(View.GONE);
}
});
}
//选择串口设备弹框
if ((ports.size() >= 1) && (!mainViewModel.isRigConnected())) {
binding.selectSerialPortLayout.setVisibility(View.VISIBLE);
} else {//说明没有可以识别的驱动,不显示设备弹框
binding.selectSerialPortLayout.setVisibility(View.GONE);
}
}
/**
*
*
* @param filePath
*/
public static void deleteFolderFile(String filePath) {
try {
File file = new File(filePath);//获取SD卡指定路径
File[] files = file.listFiles();//获取SD卡指定路径下的文件或者文件夹
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {//如果是文件直接删除
File tempFile = new File(files[i].getPath());
tempFile.delete();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void animationImage() {
ObjectAnimator navigationAnimator = ObjectAnimator.ofFloat(binding.navView, "translationY", 200);
navigationAnimator.setDuration(3000);
navigationAnimator.setFloatValues(200, 200, 200, 0);
ObjectAnimator hideLogoAnimator = ObjectAnimator.ofFloat(binding.initDataLayout, "alpha", 1f, 1f, 1f, 0);
hideLogoAnimator.setDuration(3000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(navigationAnimator, hideLogoAnimator);
//animatorSet.playTogether(initPositionStrAnimator, logoAnimator, navigationAnimator, hideLogoAnimator);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
//animationEnd = true;
binding.initDataLayout.setVisibility(View.GONE);
binding.utcProgressBar.setVisibility(View.VISIBLE);
InitFloatView();//显示浮窗
//binding.floatView.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animatorSet.start();
}
//此方法只有在android:launchMode="singleTask"模式下起作用
@Override
protected void onNewIntent(Intent intent) {
if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(intent.getAction())) {
mainViewModel.getUsbDevice();
}
super.onNewIntent(intent);
}
@Override
public void onBackPressed() {
if (navController.getGraph().getStartDestination() == navController.getCurrentDestination().getId()) {//说明是到最后一个页面了
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setMessage(getString(R.string.exit_confirmation))
.setPositiveButton(getString(R.string.exit)
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (mainViewModel.ft8TransmitSignal.isActivated()) {
mainViewModel.ft8TransmitSignal.setActivated(false);
}
closeThisApp();//退出APP
}
}).setNegativeButton(getString(R.string.cancel)
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
builder.create().show();
} else {//退出activity堆栈
navController.navigateUp();
//setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
}
}
private void closeThisApp() {
mainViewModel.ft8TransmitSignal.setActivated(false);
if (mainViewModel.baseRig != null) {
if (mainViewModel.baseRig.getConnector() != null) {
mainViewModel.baseRig.getConnector().disconnect();
}
}
mainViewModel.ft8SignalListener.stopListen();
mainViewModel = null;
System.exit(0);
}
/**
* 广
*/
private void registerBluetoothReceiver() {
if (mReceive == null) {
mReceive = new BluetoothStateBroadcastReceive(getApplicationContext(), mainViewModel);
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE);
intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
intentFilter.addAction(AudioManager.EXTRA_SCO_AUDIO_STATE);
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(BluetoothAdapter.EXTRA_CONNECTION_STATE);
intentFilter.addAction(BluetoothAdapter.EXTRA_STATE);
intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_OFF");
intentFilter.addAction("android.bluetooth.BluetoothAdapter.STATE_ON");
registerReceiver(mReceive, intentFilter);
}
/**
* 广
*/
private void unregisterBluetoothReceiver() {
if (mReceive != null) {
unregisterReceiver(mReceive);
mReceive = null;
}
}
@Override
protected void onDestroy() {
unregisterBluetoothReceiver();
super.onDestroy();
}
}

Wyświetl plik

@ -0,0 +1,921 @@
package com.bg7yoz.ft8cn;
/**
* -----2022.5.6-----
* MainViewModelFT8APP
* 1.decoded_countermutable_Decoded_Counter
* 2.Ft8MessageArrayListft8MessagesmutableFt8MessageList
* 3.UTC15UtcTimer
* 4.UTCtimerSecUtcTimer100
* 5.getInstanceMainViewModel
* 6.HamAudioRecorder----TO DO---
* 7.JNICft8cncppCMakeLists.txtdecode_ft8.cpp
* -----2022.5.9-----
*
*
* 30014.77
* <p>
*
* @author BG7YOZ
* @date 2022.5.6
*/
import static com.bg7yoz.ft8cn.GeneralVariables.getStringFromResource;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioManager;
import android.os.Handler;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.bg7yoz.ft8cn.callsign.CallsignDatabase;
import com.bg7yoz.ft8cn.callsign.CallsignInfo;
import com.bg7yoz.ft8cn.callsign.OnAfterQueryCallsignLocation;
import com.bg7yoz.ft8cn.connector.BluetoothRigConnector;
import com.bg7yoz.ft8cn.connector.CableConnector;
import com.bg7yoz.ft8cn.connector.CableSerialPort;
import com.bg7yoz.ft8cn.connector.ConnectMode;
import com.bg7yoz.ft8cn.connector.FlexConnector;
import com.bg7yoz.ft8cn.connector.IComWifiConnector;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.database.DatabaseOpr;
import com.bg7yoz.ft8cn.database.OnAfterQueryFollowCallsigns;
import com.bg7yoz.ft8cn.database.OperationBand;
import com.bg7yoz.ft8cn.flex.FlexRadio;
import com.bg7yoz.ft8cn.ft8listener.FT8SignalListener;
import com.bg7yoz.ft8cn.ft8listener.OnFt8Listen;
import com.bg7yoz.ft8cn.ft8transmit.FT8TransmitSignal;
import com.bg7yoz.ft8cn.ft8transmit.OnDoTransmitted;
import com.bg7yoz.ft8cn.ft8transmit.OnTransmitSuccess;
import com.bg7yoz.ft8cn.html.LogHttpServer;
import com.bg7yoz.ft8cn.icom.IComWifiRig;
import com.bg7yoz.ft8cn.log.QSLCallsignRecord;
import com.bg7yoz.ft8cn.log.QSLRecord;
import com.bg7yoz.ft8cn.log.SWLQsoList;
import com.bg7yoz.ft8cn.rigs.BaseRig;
import com.bg7yoz.ft8cn.rigs.BaseRigOperation;
import com.bg7yoz.ft8cn.rigs.ElecraftRig;
import com.bg7yoz.ft8cn.rigs.Flex6000Rig;
import com.bg7yoz.ft8cn.rigs.FlexNetworkRig;
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.Wolf_sdr_450Rig;
import com.bg7yoz.ft8cn.rigs.XieGu6100Rig;
import com.bg7yoz.ft8cn.rigs.XieGuRig;
import com.bg7yoz.ft8cn.rigs.Yaesu2Rig;
import com.bg7yoz.ft8cn.rigs.Yaesu38Rig;
import com.bg7yoz.ft8cn.rigs.Yaesu38_450Rig;
import com.bg7yoz.ft8cn.rigs.Yaesu39Rig;
import com.bg7yoz.ft8cn.rigs.YaesuDX10Rig;
import com.bg7yoz.ft8cn.spectrum.SpectrumListener;
import com.bg7yoz.ft8cn.timer.OnUtcTimer;
import com.bg7yoz.ft8cn.timer.UtcTimer;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import com.bg7yoz.ft8cn.wave.HamRecorder;
import com.bg7yoz.ft8cn.wave.OnGetVoiceDataDone;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainViewModel extends ViewModel {
String TAG = "ft8cn MainViewModel";
public boolean configIsLoaded = false;
private static MainViewModel viewModel = null;//当前存在的实例。
//public static Application application;
//public int decoded_counter = 0;//解码的总条数
public final ArrayList<Ft8Message> ft8Messages = new ArrayList<>();//消息列表
public UtcTimer utcTimer;//同步触发动作的计时器。
//public boolean showTrackerInfo=true;
//public CallsignDatabase callsignDatabase = null;//呼号信息的数据库
public DatabaseOpr databaseOpr;//配置信息,和相关数据的数据库
public MutableLiveData<Integer> mutable_Decoded_Counter = new MutableLiveData<>();//解码的总条数
public int currentDecodeCount = 0;//本次解码的条数
public MutableLiveData<ArrayList<Ft8Message>> mutableFt8MessageList = new MutableLiveData<>();//消息列表
public MutableLiveData<Long> timerSec = new MutableLiveData<>();//当前UTC时间。更新频率由UtcTimer确定未触发时约100毫秒。
public MutableLiveData<Boolean> mutableIsRecording = new MutableLiveData<>();//是否处于录音状态
public MutableLiveData<Boolean> mutableHamRecordIsRunning = new MutableLiveData<>();//HamRecord是否运转
public MutableLiveData<Float> mutableTimerOffset = new MutableLiveData<>();//本周期的时间延迟
public MutableLiveData<Boolean> mutableIsDecoding = new MutableLiveData<>();//会触发频谱图中的标记动作
public ArrayList<Ft8Message> currentMessages = null;//本周期解码的消息(用于画到频谱上)
public MutableLiveData<Boolean> mutableIsFlexRadio = new MutableLiveData<>();//是不是flex电台
private final ExecutorService getQTHThreadPool = Executors.newCachedThreadPool();
private final ExecutorService sendWaveDataThreadPool = Executors.newCachedThreadPool();
private final GetQTHRunnable getQTHRunnable = new GetQTHRunnable(this);
private final SendWaveDataRunnable sendWaveDataRunnable = new SendWaveDataRunnable();
public HamRecorder hamRecorder;//用于录音的对象
public FT8SignalListener ft8SignalListener;//用于监听FT8信号并解码的对象
public FT8TransmitSignal ft8TransmitSignal;//用于发射信号用的对象
public SpectrumListener spectrumListener;//用于画频谱的对象
public boolean markMessage = true;//是否标记消息开关
//控制电台的方式
public OperationBand operationBand = null;
private SWLQsoList swlQsoList = new SWLQsoList();//用于记录SWL的QSO对象对SWL QSO做判断防止重复。
public MutableLiveData<ArrayList<CableSerialPort.SerialPort>> mutableSerialPorts = new MutableLiveData<>();
private ArrayList<CableSerialPort.SerialPort> serialPorts;//串口列表
public BaseRig baseRig;//电台
private final OnRigStateChanged onRigStateChanged = new OnRigStateChanged() {
@Override
public void onDisconnected() {
//与电台连接中断
ToastMessage.show(getStringFromResource(R.string.disconnect_rig));
}
@Override
public void onConnected() {
//与电台建立连接
ToastMessage.show(getStringFromResource(R.string.connected_rig));
}
@Override
public void onPttChanged(boolean isOn) {
}
@Override
public void onFreqChanged(long freq) {
//当前频率:%s
ToastMessage.show(String.format(getStringFromResource(R.string.current_frequency)
, BaseRigOperation.getFrequencyAllInfo(freq)));
//把频率的变化写回到全局变量中
GeneralVariables.band = freq;
GeneralVariables.bandListIndex = OperationBand.getIndexByFreq(freq);
GeneralVariables.mutableBandChange.postValue(GeneralVariables.bandListIndex);
databaseOpr.getAllQSLCallsigns();//通联成功的呼号读出来
}
@Override
public void onRunError(String message) {
//与电台通讯出现错误,
ToastMessage.show(String.format(getStringFromResource(R.string.radio_communication_error)
, message));
}
};
//发射信号用的消息列表
//public ArrayList<Ft8Message> transmitMessages = new ArrayList<>();
//public MutableLiveData<ArrayList<Ft8Message>> mutableTransmitMessages = new MutableLiveData<>();
public MutableLiveData<Integer> mutableTransmitMessagesCount = new MutableLiveData<>();
public boolean deNoise = false;//在频谱中抑制噪声
//*********日志查询需要的变量********************
public boolean logListShowCallsign = false;//在日志查询列表的表现形式
public String queryKey = "";//查询的关键字
public int queryFilter = 0;//过滤0全部1确认2未确认
public MutableLiveData<Integer> mutableQueryFilter = new MutableLiveData<>();
public ArrayList<QSLCallsignRecord> callsignRecords = new ArrayList<>();
//public ArrayList<QSLRecordStr> qslRecords=new ArrayList<>();
//********************************************
//关注呼号的列表
//public ArrayList<String> followCallsign = new ArrayList<>();
//日志管理HTTP SERVER
private final LogHttpServer httpServer;
/**
* MainViewModelMainViewModelAPP
*
* @param owner ViewModelStoreOwner ActivityFragment
* @return MainViewModel MainViewModel
*/
public static MainViewModel getInstance(ViewModelStoreOwner owner) {
if (viewModel == null) {
viewModel = new ViewModelProvider(owner).get(MainViewModel.class);
}
return viewModel;
}
/**
*
*
* @param position Mutable
* @return Ft8Message
*/
public Ft8Message getFt8Message(int position) {
return Objects.requireNonNull(ft8Messages.get(position));
}
/**
* MainViewModel
* 1.UTCUtcTimerTimerTimerTask线线
* 2.Mutable
*/
//@RequiresApi(api = Build.VERSION_CODES.N)
public MainViewModel() {
//获取配置信息。
databaseOpr = DatabaseOpr.getInstance(GeneralVariables.getMainContext()
, "data.db");
mutableIsDecoding.postValue(false);//解码状态
//创录音对象
hamRecorder = new HamRecorder(null);
hamRecorder.startRecord();
mutableIsFlexRadio.setValue(false);
//创建用于显示时间的计时器
utcTimer = new UtcTimer(10, false, new OnUtcTimer() {
@Override
public void doHeartBeatTimer(long utc) {//不触发时的时钟信息
}
@Override
public void doOnSecTimer(long utc) {//当指定间隔时触发时
timerSec.postValue(utc);//发送当前UTC时间
mutableIsRecording.postValue(hamRecorder.isRunning());
mutableHamRecordIsRunning.postValue(hamRecorder.isRunning());//发送当前计时器状态
}
});
utcTimer.start();//启动计时器
//同步一下时间。microsoft的NTP服务器
UtcTimer.syncTime(null);
mutableFt8MessageList.setValue(ft8Messages);
//创建监听对象,回调中的动作用于处理解码、发射、关注的呼号列表添加等操作
ft8SignalListener = new FT8SignalListener(databaseOpr, new OnFt8Listen() {
@Override
public void beforeListen(long utc) {
mutableIsDecoding.postValue(true);
}
@Override
public void afterDecode(long utc, float time_sec, int sequential
, ArrayList<Ft8Message> messages, boolean isDeep) {
if (messages.size() == 0) return;//没有解码出消息,不触发动作
synchronized (ft8Messages) {
ft8Messages.addAll(messages);//添加消息到列表
}
GeneralVariables.deleteArrayListMore(ft8Messages);//删除多余的消息,FT8CN限定的可展示消息的总数量
mutableFt8MessageList.postValue(ft8Messages);//触发添加消息的动作,让界面能观察到
mutableTimerOffset.postValue(time_sec);//本次时间偏移量
findIncludedCallsigns(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);//解码的状态,会触发频谱图中的标记动作
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) {
swlQsoList.findSwlQso(messages, ft8Messages, new SWLQsoList.OnFoundSwlQso() {
@Override
public void doFound(QSLRecord record) {
databaseOpr.addSWL_QSO(record);//把SWL QSO保存到数据库
ToastMessage.show(record.swlQSOInfo());
}
});
}
//从列表中查找呼号和网格对应关系,并添加到表中
getCallsignAndGrid(messages);
}
});
ft8SignalListener.setOnWaveDataListener(new FT8SignalListener.OnWaveDataListener() {
@Override
public void getVoiceData(int duration, boolean afterDoneRemove, OnGetVoiceDataDone getVoiceDataDone) {
hamRecorder.getVoiceData(duration, afterDoneRemove, getVoiceDataDone);
}
});
ft8SignalListener.startListen();
//频谱监听对象
spectrumListener = new SpectrumListener(hamRecorder);
//创建发射对象回调发射前发射后、QSL成功后。
ft8TransmitSignal = new FT8TransmitSignal(databaseOpr, new OnDoTransmitted() {
@Override
public void onBeforeTransmit(Ft8Message message, int functionOder) {
if (GeneralVariables.controlMode == ControlMode.CAT
|| GeneralVariables.controlMode == ControlMode.RTS
|| GeneralVariables.controlMode == ControlMode.DTR) {
if (baseRig != null) {
if (GeneralVariables.connectMode != ConnectMode.NETWORK) stopSco();
baseRig.setPTT(true);
}
}
if (ft8TransmitSignal.isActivated()) {
GeneralVariables.transmitMessages.add(message);
//mutableTransmitMessages.postValue(GeneralVariables.transmitMessages);
mutableTransmitMessagesCount.postValue(1);
}
}
@Override
public void onAfterTransmit(Ft8Message message, int functionOder) {
if (GeneralVariables.controlMode == ControlMode.CAT
|| GeneralVariables.controlMode == ControlMode.RTS
|| GeneralVariables.controlMode == ControlMode.DTR) {
if (baseRig != null) {
baseRig.setPTT(false);
if (GeneralVariables.connectMode != ConnectMode.NETWORK) startSco();
}
}
}
@Override
public void onTransmitByWifi(Ft8Message msg) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
if (baseRig != null) {
if (baseRig.isConnected()) {
sendWaveDataRunnable.baseRig = baseRig;
sendWaveDataRunnable.message = msg;
//以线程池的方式执行网络数据包发送
sendWaveDataThreadPool.execute(sendWaveDataRunnable);
}
}
}
}
}, new OnTransmitSuccess() {//当通联成功时
@Override
public void doAfterTransmit(QSLRecord qslRecord) {
databaseOpr.addQSL_Callsign(qslRecord);//两个操作把呼号和QSL记录下来
if (qslRecord.getToCallsign() != null) {//把通联成功的分区加入到分区列表
GeneralVariables.callsignDatabase.getCallsignInformation(qslRecord.getToCallsign()
, new OnAfterQueryCallsignLocation() {
@Override
public void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo) {
GeneralVariables.addDxcc(callsignInfo.DXCC);
GeneralVariables.addItuZone(callsignInfo.ITUZone);
GeneralVariables.addCqZone(callsignInfo.CQZone);
}
});
}
}
});
//打开HTTP SERVER
httpServer = new LogHttpServer(this, LogHttpServer.DEFAULT_PORT);
try {
httpServer.start();
} catch (IOException e) {
Log.e(TAG, "http server error:" + e.getMessage());
}
}
public void setTransmitIsFreeText(boolean isFreeText) {
if (ft8TransmitSignal != null) {
ft8TransmitSignal.setTransmitFreeText(isFreeText);
}
}
public boolean getTransitIsFreeText() {
if (ft8TransmitSignal != null) {
return ft8TransmitSignal.isTransmitFreeText();
}
return false;
}
/**
*
*
* @param messages
*/
private synchronized void findIncludedCallsigns(ArrayList<Ft8Message> messages) {
Log.d(TAG, "findIncludedCallsigns: 查找关注的呼号");
if (ft8TransmitSignal.isActivated() && ft8TransmitSignal.sequential != UtcTimer.getNowSequential()) {
return;
}
int count = 0;
for (Ft8Message msg : messages) {
//与我的呼号有关,与关注的呼号有关
if (msg.getCallsignFrom().equals(GeneralVariables.myCallsign)
|| msg.getCallsignTo().equals(GeneralVariables.myCallsign)
|| GeneralVariables.callsignInFollow(msg.getCallsignFrom())
|| (GeneralVariables.callsignInFollow(msg.getCallsignTo()) && (msg.getCallsignTo() != null))
|| (GeneralVariables.autoFollowCQ && msg.checkIsCQ())) {//是CQ并且允许关注CQ
//看不是通联成功的呼号的消息
msg.isQSL_Callsign = GeneralVariables.checkQSLCallsign(msg.getCallsignFrom());
if (!GeneralVariables.checkIsExcludeCallsign(msg.callsignFrom)) {//不在排除呼号前缀的,才加入列表
count++;
GeneralVariables.transmitMessages.add(msg);
}
}
}
GeneralVariables.deleteArrayListMore(GeneralVariables.transmitMessages);//删除多余的消息
//mutableTransmitMessages.postValue(GeneralVariables.transmitMessages);
mutableTransmitMessagesCount.postValue(count);
}
/**
*
*/
public void clearTransmittingMessage() {
GeneralVariables.transmitMessages.clear();
mutableTransmitMessagesCount.postValue(0);
}
/**
*
*
* @param messages
*/
private void getCallsignAndGrid(ArrayList<Ft8Message> messages) {
for (Ft8Message msg : messages) {
if (GeneralVariables.checkFun1(msg.extraInfo)) {//检查是不是网格
//如果内存表中没有,或不一致,就写入数据库中
if (!GeneralVariables.getCallsignHasGrid(msg.getCallsignFrom(), msg.maidenGrid)) {
databaseOpr.addCallsignQTH(msg.getCallsignFrom(), msg.maidenGrid);//写数据库
}
GeneralVariables.addCallsignAndGrid(msg.getCallsignFrom(), msg.maidenGrid);
}
}
}
/**
*
*/
public void clearFt8MessageList() {
ft8Messages.clear();
mutable_Decoded_Counter.postValue(ft8Messages.size());
mutableFt8MessageList.postValue(ft8Messages);
}
/**
*
*
* @param fileName
*/
public static void deleteFile(String fileName) {
File file = new File(fileName);
// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
if (file.exists() && file.isFile()) {
file.delete();
}
}
/**
*
*
* @param callsign
*/
public void addFollowCallsign(String callsign) {
if (!GeneralVariables.followCallsign.contains(callsign)) {
GeneralVariables.followCallsign.add(callsign);
databaseOpr.addFollowCallsign(callsign);
}
}
/**
*
*/
public void getFollowCallsignsFromDataBase() {
databaseOpr.getFollowCallsigns(new OnAfterQueryFollowCallsigns() {
@Override
public void doOnAfterQueryFollowCallsigns(ArrayList<String> callsigns) {
for (String s : callsigns) {
if (!GeneralVariables.followCallsign.contains(s)) {
GeneralVariables.followCallsign.add(s);
}
}
}
});
}
/**
*
*/
public void setOperationBand() {
if (!isRigConnected()) {
return;
}
//先设置上边带,再设置频率
baseRig.setUsbModeToRig();//设置上边带
//此处延迟1秒发送第二个指令是防止协谷X6100断开连接的问题
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
baseRig.setFreq(GeneralVariables.band);//设置频率
baseRig.setFreqToRig();
}
}, 800);
}
public void setCivAddress() {
if (baseRig != null) {
baseRig.setCivAddress(GeneralVariables.civAddress);
}
}
public void setControlMode() {
if (baseRig != null) {
baseRig.setControlMode(GeneralVariables.controlMode);
}
}
/**
* USB
*
* @param context context
* @param port
*/
public void connectCableRig(Context context, CableSerialPort.SerialPort port) {
if (GeneralVariables.controlMode == ControlMode.VOX) {//如果当前是VOX就改成CAT模式
GeneralVariables.controlMode = ControlMode.CAT;
}
connectRig();
if (baseRig == null) {
return;
}
baseRig.setControlMode(GeneralVariables.controlMode);
CableConnector connector = new CableConnector(context, port, GeneralVariables.baudRate
, GeneralVariables.controlMode);
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(connector);
connector.connect();
//晚1秒钟设置模式防止有的电台反应不过来
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
setOperationBand();//设置载波频率
}
}, 1000);
}
public void connectBluetoothRig(Context context, BluetoothDevice device) {
GeneralVariables.controlMode = ControlMode.CAT;//蓝牙控制模式只能是CAT控制
connectRig();
if (baseRig == null) {
return;
}
baseRig.setControlMode(GeneralVariables.controlMode);
BluetoothRigConnector connector = BluetoothRigConnector.getInstance(context, device.getAddress()
, GeneralVariables.controlMode);
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(connector);
new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的等2秒再设置频率
@Override
public void run() {
setOperationBand();//设置载波频率
}
}, 5000);
}
public void connectIComWifiRig(Context context, IComWifiRig iComWifiRig) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
if (baseRig != null) {
if (baseRig.getConnector() != null) {
baseRig.getConnector().disconnect();
}
}
}
GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式
IComWifiConnector iComWifiConnector = new IComWifiConnector(GeneralVariables.controlMode
, iComWifiRig);
iComWifiConnector.setOnWifiDataReceived(new IComWifiConnector.OnWifiDataReceived() {
@Override
public void OnWaveReceived(int bufferLen, float[] buffer) {
hamRecorder.doOnWaveDataReceived(bufferLen, buffer);
}
@Override
public void OnCivReceived(byte[] data) {
}
});
iComWifiConnector.connect();
connectRig();
baseRig.setControlMode(GeneralVariables.controlMode);
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(iComWifiConnector);
//
new Handler().postDelayed(new Runnable() {//蓝牙连接是需要时间的等2秒再设置频率
@Override
public void run() {
setOperationBand();//设置载波频率
}
}, 1000);
}
/**
* flexRadio
*
* @param context context
* @param flexRadio flexRadio
*/
public void connectFlexRadioRig(Context context, FlexRadio flexRadio) {
if (GeneralVariables.connectMode == ConnectMode.NETWORK) {
if (baseRig != null) {
if (baseRig.getConnector() != null) {
baseRig.getConnector().disconnect();
}
}
}
GeneralVariables.controlMode = ControlMode.CAT;//网络控制模式
FlexConnector flexConnector = new FlexConnector(context, flexRadio, GeneralVariables.controlMode);
flexConnector.setOnWaveDataReceived(new FlexConnector.OnWaveDataReceived() {
@Override
public void OnDataReceived(int bufferLen, float[] buffer) {
hamRecorder.doOnWaveDataReceived(bufferLen, buffer);
}
});
flexConnector.connect();
connectRig();
baseRig.setOnRigStateChanged(onRigStateChanged);
baseRig.setConnector(flexConnector);
//
new Handler().postDelayed(new Runnable() {//连接是需要时间的等2秒再设置频率
@Override
public void run() {
setOperationBand();//设置载波频率
}
}, 3000);
}
/**
*
*/
private void connectRig() {
if ((GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK)
|| (GeneralVariables.instructionSet == InstructionSet.ICOM
&& GeneralVariables.connectMode == ConnectMode.NETWORK)) {
hamRecorder.setDataFromLan();
} else {
hamRecorder.setDataFromMic();
}
baseRig = null;
//此处判断是用什么类型的电台ICOM,YAESU 2,YAESU 3
switch (GeneralVariables.instructionSet) {
case InstructionSet.ICOM:
baseRig = new IcomRig(GeneralVariables.civAddress);
break;
case InstructionSet.YAESU_2:
baseRig = new Yaesu2Rig();
break;
case InstructionSet.YAESU_3_9:
baseRig = new Yaesu39Rig();//yaesu3代指令9位频率
break;
case InstructionSet.YAESU_3_8:
baseRig = new Yaesu38Rig();//yaesu3代指令8位频率
break;
case InstructionSet.YAESU_3_450:
baseRig = new Yaesu38_450Rig();//yaesu3代指令8位频率
break;
case InstructionSet.KENWOOD_TK90:
baseRig = new KenwoodKT90Rig();//建伍TK90
break;
case InstructionSet.YAESU_DX10:
baseRig = new YaesuDX10Rig();//YAESU DX10 DX101
break;
case InstructionSet.KENWOOD_TS590:
baseRig = new KenwoodTS590Rig();//KENWOOD TS590
break;
case InstructionSet.GUOHE_Q900:
baseRig = new GuoHeQ900Rig();//国赫Q900
break;
case InstructionSet.XIEGUG90S://协谷USB模式
baseRig = new XieGuRig(GeneralVariables.civAddress);//协谷G90S
break;
case InstructionSet.ELECRAFT:
baseRig = new ElecraftRig();//ELECRAFT
break;
case InstructionSet.FLEX_CABLE:
baseRig = new Flex6000Rig();//FLEX6000
break;
case InstructionSet.FLEX_NETWORK:
baseRig = new FlexNetworkRig();
break;
case InstructionSet.XIEGU_6100:
baseRig = new XieGu6100Rig(GeneralVariables.civAddress);//协谷6100
break;
case InstructionSet.KENWOOD_TS2000:
baseRig = new KenwoodTS2000Rig();//建伍TS2000
break;
case InstructionSet.WOLF_SDR_DIGU:
baseRig = new Wolf_sdr_450Rig(false);
break;
case InstructionSet.WOLF_SDR_USB:
baseRig = new Wolf_sdr_450Rig(true);
break;
}
mutableIsFlexRadio.postValue(GeneralVariables.instructionSet == InstructionSet.FLEX_NETWORK);
}
/**
* ,rigBaseClass
*
* @return
*/
public boolean isRigConnected() {
if (baseRig == null) {
return false;
} else {
return baseRig.isConnected();
}
}
/**
*
*/
public void getUsbDevice() {
serialPorts =
CableSerialPort.listSerialPorts(GeneralVariables.getMainContext());
mutableSerialPorts.postValue(serialPorts);
}
public void startSco() {
AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext()
.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) return;
if (!audioManager.isBluetoothScoAvailableOffCall()) {
//蓝牙设备不支持录音
ToastMessage.show(getStringFromResource(R.string.does_not_support_recording));
return;
}
audioManager.setBluetoothScoOn(true);
audioManager.startBluetoothSco();//71毫秒
audioManager.setSpeakerphoneOn(false);//进入耳机模式
}
public void stopSco() {
AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext()
.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) return;
if (audioManager.isBluetoothScoOn()) {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
audioManager.setSpeakerphoneOn(true);//退出耳机模式
}
}
public void setBlueToothOn() {
AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext()
.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) return;
if (!audioManager.isBluetoothScoAvailableOffCall()) {
//蓝牙设备不支持录音
ToastMessage.show(getStringFromResource(R.string.does_not_support_recording));
}
/*
MODE_NORMAL, 使audioManager.setSpeakerphoneOn(true).
使,MODE_IN_CALL(3.0)MODE_IN_COMMUNICATION(3.0).
*/
audioManager.setMode(AudioManager.MODE_NORMAL);//178毫秒
audioManager.setBluetoothScoOn(true);
audioManager.stopBluetoothSco();
audioManager.startBluetoothSco();//71毫秒
audioManager.setSpeakerphoneOn(false);//进入耳机模式
//进入到蓝牙耳机模式
ToastMessage.show(getStringFromResource(R.string.bluetooth_headset_mode));
}
public void setBlueToothOff() {
AudioManager audioManager = (AudioManager) GeneralVariables.getMainContext()
.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) return;
if (audioManager.isBluetoothScoOn()) {
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
audioManager.setSpeakerphoneOn(true);//退出耳机模式
}
//离开蓝牙耳机模式
ToastMessage.show(getStringFromResource(R.string.bluetooth_Headset_mode_cancelled));
}
/**
*
*
* @return
*/
@SuppressLint("MissingPermission")
public boolean isBTConnected() {
BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter();
if (blueAdapter == null) return false;
//蓝牙头戴式耳机,支持语音输入输出
int headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
int a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
return headset == BluetoothAdapter.STATE_CONNECTED || a2dp == BluetoothAdapter.STATE_CONNECTED;
}
private static class GetQTHRunnable implements Runnable {
MainViewModel mainViewModel;
ArrayList<Ft8Message> messages;
public GetQTHRunnable(MainViewModel mainViewModel) {
this.mainViewModel = mainViewModel;
}
@Override
public void run() {
CallsignDatabase.getMessagesLocation(
GeneralVariables.callsignDatabase.getDb(), messages);
mainViewModel.mutableFt8MessageList.postValue(mainViewModel.ft8Messages);
}
}
private static class SendWaveDataRunnable implements Runnable {
BaseRig baseRig;
//float[] data;
Ft8Message message;
@Override
public void run() {
if (baseRig != null && message != null) {
baseRig.sendWaveData(message);//实际生成的数据是12.64+0.04,0.04是生成的0数据
}
}
}
}

Wyświetl plik

@ -0,0 +1,55 @@
package com.bg7yoz.ft8cn;
/**
*
* @author BGY70Z
* @date 2023-03-20
*/
import android.util.Log;
import java.util.HashMap;
public class MessageHashMap extends HashMap<Long,String> {
private static final String TAG = "MessageHashMap";
/**
*
*
* @param hashCode
* @param callsign
* @return false
*/
public synchronized void addHash(long hashCode, String callsign) {
//if (callsign.length()<2){return;}
//if (){return;}
if (callsign.equals("CQ")||callsign.equals("QRZ")||callsign.equals("DE")){
return;
}
if (hashCode == 0 || checkHash(hashCode)|| callsign.charAt(0) == '<') {
return;
}
Log.d(TAG, String.format("addHash: callsign:%s ,hash:%x",callsign,hashCode ));
put(hashCode,callsign);
}
//检查是否存在这个hash码
public boolean checkHash(long hashCode) {
return get(hashCode)!=null;
// for (HashStruct hash : this) {
// if (hash.hashCode == hashCode) {
// return true;
// }
// }
// return false;
}
//通过哈希码查呼号
public synchronized String getCallsign(long[] hashCode) {
for (long l : hashCode) {
if (checkHash(l)) {
return String.format("<%s>", get(l));
}
}
return "<...>";
}
}

Wyświetl plik

@ -0,0 +1,63 @@
package com.bg7yoz.ft8cn.bluetooth;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.ParcelUuid;
import com.bg7yoz.ft8cn.BuildConfig;
/**
*
*/
public class BluetoothConstants {
// values have to be globally unique
static final String INTENT_ACTION_DISCONNECT = BuildConfig.APPLICATION_ID + ".Disconnect";
static final String NOTIFICATION_CHANNEL = BuildConfig.APPLICATION_ID + ".Channel";
static final String INTENT_CLASS_MAIN_ACTIVITY = BuildConfig.APPLICATION_ID + ".MainActivity";
// values have to be unique within each app
static final int NOTIFY_MANAGER_START_FOREGROUND_SERVICE = 1001;
public static boolean checkBluetoothIsOpen(){
BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
if (adapter==null){
return false;
}else {
return adapter.isEnabled();
}
}
public static boolean checkIsSpp(BluetoothDevice device) {
@SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids();
if (parcelUuids != null) {
for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的
if (parcelUuids[i].getUuid().toString().toUpperCase().equals("00001101-0000-1000-8000-00805F9B34FB")) {
return true;
}
}
}
return false;
}
public static boolean checkIsHeadSet(BluetoothDevice device){
@SuppressLint("MissingPermission") ParcelUuid[] parcelUuids = device.getUuids();
boolean audioSinkService=false;
boolean handsFreeService=false;
if (parcelUuids != null) {
for (int i = 0; i < parcelUuids.length; i++) {//只保留UUID是串口的
if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000111e-0000-1000-8000-00805f9b34fb")) {
handsFreeService=true;
}
if (parcelUuids[i].getUuid().toString().toLowerCase().equals("0000110b-0000-1000-8000-00805f9b34fb")) {
audioSinkService=true;
}
}
}
return audioSinkService&&handsFreeService;
}
}

Wyświetl plik

@ -0,0 +1,13 @@
package com.bg7yoz.ft8cn.bluetooth;
/**
*
* BG7YOZ
* 2023-03
*/
public interface BluetoothSerialListener {
void onSerialConnect ();
void onSerialConnectError (Exception e);
void onSerialRead (byte[] data);
void onSerialIoError (Exception e);
}

Wyświetl plik

@ -0,0 +1,213 @@
package com.bg7yoz.ft8cn.bluetooth;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
/**
*
* BG7YOZ
* 2023-03
*/
public class BluetoothSerialService extends Service implements BluetoothSerialListener {
public class SerialBinder extends Binder {
public BluetoothSerialService getService() { return BluetoothSerialService.this; }
}
private enum QueueType {Connect, ConnectError, Read, IoError}
private static class QueueItem {
QueueType type;
byte[] data;
Exception e;
QueueItem(QueueType type, byte[] data, Exception e) { this.type=type; this.data=data; this.e=e; }
}
private final Handler mainLooper;
private final IBinder binder;
private final Queue<QueueItem> queue1, queue2;
private BluetoothSerialSocket socket;
private BluetoothSerialListener listener;
private boolean connected;
/**
* Lifecylce
*/
public BluetoothSerialService() {
mainLooper = new Handler(Looper.getMainLooper());
binder = new SerialBinder();
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
@Override
public void onDestroy() {
//cancelNotification();
disconnect();
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
/**
* Api
*/
public void connect(BluetoothSerialSocket socket) throws IOException {
socket.connect(this);
this.socket = socket;
connected = true;
}
public void disconnect() {
connected = false; // ignore data,errors while disconnecting
//cancelNotification();
if(socket != null) {
socket.disconnect();
socket = null;
}
}
public void write(byte[] data) throws IOException {
if(!connected)
throw new IOException("not connected");
socket.write(data);
}
public void attach(BluetoothSerialListener listener) {
if(Looper.getMainLooper().getThread() != Thread.currentThread())
throw new IllegalArgumentException("not in main thread");
//cancelNotification();
// use synchronized() to prevent new items in queue2
// new items will not be added to queue1 because mainLooper.post and attach() run in main thread
synchronized (this) {
this.listener = listener;
}
for(QueueItem item : queue1) {
switch(item.type) {
case Connect: listener.onSerialConnect (); break;
case ConnectError: listener.onSerialConnectError (item.e); break;
case Read: listener.onSerialRead (item.data); break;
case IoError: listener.onSerialIoError (item.e); break;
}
}
for(QueueItem item : queue2) {
switch(item.type) {
case Connect: listener.onSerialConnect (); break;
case ConnectError: listener.onSerialConnectError (item.e); break;
case Read: listener.onSerialRead (item.data); break;
case IoError: listener.onSerialIoError (item.e); break;
}
}
queue1.clear();
queue2.clear();
}
public void detach() {
if (connected){
disconnect();
}
listener = null;
}
/**
* SerialListener
*/
public void onSerialConnect() {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialConnect();
} else {
queue1.add(new QueueItem(QueueType.Connect, null, null));
}
});
} else {
queue2.add(new QueueItem(QueueType.Connect, null, null));
}
}
}
}
public void onSerialConnectError(Exception e) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialConnectError(e);
} else {
queue1.add(new QueueItem(QueueType.ConnectError, null, e));
//cancelNotification();
disconnect();
}
});
} else {
queue2.add(new QueueItem(QueueType.ConnectError, null, e));
//cancelNotification();
disconnect();
}
}
}
}
public void onSerialRead(byte[] data) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialRead(data);
} else {
queue1.add(new QueueItem(QueueType.Read, data, null));
}
});
} else {
queue2.add(new QueueItem(QueueType.Read, data, null));
}
}
}
}
public void onSerialIoError(Exception e) {
if(connected) {
synchronized (this) {
if (listener != null) {
mainLooper.post(() -> {
if (listener != null) {
listener.onSerialIoError(e);
} else {
queue1.add(new QueueItem(QueueType.IoError, null, e));
//cancelNotification();
disconnect();
}
});
} else {
queue2.add(new QueueItem(QueueType.IoError, null, e));
//cancelNotification();
disconnect();
}
}
}
}
}

Wyświetl plik

@ -0,0 +1,127 @@
package com.bg7yoz.ft8cn.bluetooth;
/**
* SOCKET
* BG7YOZ
* 2023-03
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.Executors;
public class BluetoothSerialSocket implements Runnable {
private static final UUID BLUETOOTH_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private final BroadcastReceiver disconnectBroadcastReceiver;
private final Context context;
private BluetoothSerialListener listener;
private final BluetoothDevice device;
private BluetoothSocket socket;
private boolean connected;
public BluetoothSerialSocket(Context context, BluetoothDevice device) {
if(context instanceof Activity)
throw new InvalidParameterException("expected non UI context");
this.context = context;
this.device = device;
disconnectBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if(listener != null)
listener.onSerialIoError(new IOException("background disconnect"));
disconnect(); // disconnect now, else would be queued until UI re-attached
}
};
}
@SuppressLint("MissingPermission")
String getName() {
return device.getName() != null ? device.getName() : device.getAddress();
}
/**
* connect-success and most connect-errors are returned asynchronously to listener
*/
void connect(BluetoothSerialListener listener) throws IOException {
this.listener = listener;
context.registerReceiver(disconnectBroadcastReceiver, new IntentFilter(BluetoothConstants.INTENT_ACTION_DISCONNECT));
Executors.newCachedThreadPool().submit(this);
}
void disconnect() {
listener = null; // ignore remaining data and errors
// connected = false; // run loop will reset connected
if(socket != null) {
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
}
try {
context.unregisterReceiver(disconnectBroadcastReceiver);
} catch (Exception ignored) {
}
}
void write(byte[] data) throws IOException {
if (!connected)
throw new IOException("not connected");
socket.getOutputStream().write(data);
}
@SuppressLint("MissingPermission")
@Override
public void run() { // connect & read
try {
socket = device.createRfcommSocketToServiceRecord(BLUETOOTH_SPP);
socket.connect();
if(listener != null)
listener.onSerialConnect();
} catch (Exception e) {
if(listener != null)
listener.onSerialConnectError(e);
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
return;
}
connected = true;
try {
byte[] buffer = new byte[1024];
int len;
//noinspection InfiniteLoopStatement
while (true) {
len = socket.getInputStream().read(buffer);
byte[] data = Arrays.copyOf(buffer, len);
if(listener != null)
listener.onSerialRead(data);
}
} catch (Exception e) {
connected = false;
if (listener != null)
listener.onSerialIoError(e);
try {
socket.close();
} catch (Exception ignored) {
}
socket = null;
}
}
}

Wyświetl plik

@ -0,0 +1,109 @@
package com.bg7yoz.ft8cn.bluetooth;
/**
* 广
* @writer bg7yoz
* @date 2022-07-22
*/
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.MainViewModel;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.ui.ToastMessage;
public class BluetoothStateBroadcastReceive extends BroadcastReceiver {
private static final String TAG="BluetoothStateBroadcastReceive";
private Context context;
private MainViewModel mainViewModel;
public BluetoothStateBroadcastReceive(Context context, MainViewModel mainViewModel) {
this.context = context;
this.mainViewModel = mainViewModel;
}
@SuppressLint("MissingPermission")
@Override
public void onReceive(Context context, Intent intent) {
this.context=context;
String action = intent.getAction();
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
BluetoothAdapter blueAdapter = BluetoothAdapter.getDefaultAdapter();
int headset=-1;
int a2dp=-1;
if (blueAdapter!=null) {
headset = blueAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
a2dp = blueAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
}
switch (action) {
case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothAdapter.EXTRA_CONNECTION_STATE:
case BluetoothAdapter.EXTRA_STATE:
if(headset == BluetoothProfile.STATE_CONNECTED ||a2dp==BluetoothProfile.STATE_CONNECTED){
//if(headset == BluetoothProfile.STATE_CONNECTED){
//if(a2dp==BluetoothProfile.STATE_CONNECTED){
mainViewModel.setBlueToothOn();
}else {
mainViewModel.setBlueToothOff();
}
break;
case BluetoothDevice.ACTION_ACL_CONNECTED:
if (device!=null) {
ToastMessage.show(String.format(
GeneralVariables.getStringFromResource(R.string.bluetooth_is_connected)
,device.getName()));
}
break;
case BluetoothDevice.ACTION_ACL_DISCONNECTED:
if (device!=null) {
ToastMessage.show(String.format(
GeneralVariables.getStringFromResource(R.string.bluetooth_is_diconnected)
,device.getName()));
}
break;
case AudioManager.ACTION_AUDIO_BECOMING_NOISY:
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.sound_source_switched));
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueState) {
case BluetoothAdapter.STATE_OFF:
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_off));
break;
case BluetoothAdapter.STATE_ON:
ToastMessage.show(GeneralVariables.getStringFromResource(R.string.bluetooth_turn_on));
break;
}
break;
}
}
// static final int PROFILE_HEADSET = 0;
// static final int PROFILE_A2DP = 1;
// static final int PROFILE_OPP = 2;
// static final int PROFILE_HID = 3;
// static final int PROFILE_PANU = 4;
// static final int PROFILE_NAP = 5;
// static final int PROFILE_A2DP_SINK = 6;
//
// private boolean checkBluetoothClass(BluetoothClass bluetoothClass,int proFile){
// if (proFile==PROFILE_A2DP){
// bluetoothClass.hasService(BluetoothClass.Service.RENDER);
// return true;
// }
// }
}

Wyświetl plik

@ -0,0 +1,277 @@
package com.bg7yoz.ft8cn.callsign;
/**
* CTY.DAT
* @author BG7YOZ
* 2023-03-20
*/
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import com.bg7yoz.ft8cn.Ft8Message;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.google.android.gms.maps.model.LatLng;
import java.util.ArrayList;
import java.util.Set;
public class CallsignDatabase extends SQLiteOpenHelper {
private static final String TAG = "CallsignDatabase";
@SuppressLint("StaticFieldLeak")
private static CallsignDatabase instance;
private final Context context;
private SQLiteDatabase db;
public static CallsignDatabase getInstance(@Nullable Context context, @Nullable String databaseName, int version) {
if (instance == null) {
instance = new CallsignDatabase(context, databaseName, null, version);
}
return instance;
}
public CallsignDatabase(@Nullable Context context, @Nullable String name
, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
this.context = context;
//链接数据库如果实体库不存在就会调用onCreate方法在onCreate方法中初始化数据库
db = this.getWritableDatabase();
}
public SQLiteDatabase getDb() {
return db;
}
/**
*
*
* @param sqLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
Log.d(TAG, "Create database.");
db = sqLiteDatabase;//把数据库链接保存下来
createTables();//创建数据表
new InitDatabase(context, db).execute();//导入数据
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
private void createTables() {
try {
db.execSQL("CREATE TABLE countries (\n" +
"id INTEGER NOT NULL PRIMARY KEY,\n" +
"CountryNameEn TEXT,\n" +
"CountryNameCN TEXT,\n" +
"CQZone INTEGER,\n" +
"ITUZone INTEGER,\n" +
"Continent TEXT,\n" +
"Latitude REAL,\n" +
"Longitude REAL,\n" +
"GMT_offset REAL,\n" +
"DXCC TEXT)");
db.execSQL("CREATE INDEX countries_id_IDX ON countries (id)");
db.execSQL("CREATE TABLE callsigns (countryId INTEGER NOT NULL,callsign TEXT)");
db.execSQL("CREATE INDEX callsigns_callsign_IDX ON callsigns (callsign)");
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
//查呼号的归属地
public void getCallsignInformation(String callsign, OnAfterQueryCallsignLocation afterQueryCallsignLocation) {
new QueryCallsignInformation(db, callsign, afterQueryCallsignLocation).execute();
}
public CallsignInfo getCallInfo(String callsign) {
return getCallsignInfo(db, callsign);
}
/**
*
*
* @param ft8Messages
*/
public static synchronized void getMessagesLocation(SQLiteDatabase db, ArrayList<Ft8Message> ft8Messages ) {
if (ft8Messages==null) return;
ArrayList<Ft8Message> messages = new ArrayList<>(ft8Messages);//防止线程访问冲突
for (Ft8Message msg : messages) {
if (msg.i3==0&&msg.n3==0) continue;//如果是自由文本,就不查了
CallsignInfo fromCallsignInfo = getCallsignInfo(db,
msg.callsignFrom.replace("<","").replace(">",""));
if (fromCallsignInfo != null) {
msg.fromDxcc = !GeneralVariables.getDxccByPrefix(fromCallsignInfo.DXCC);
msg.fromItu = !GeneralVariables.getItuZoneById(fromCallsignInfo.ITUZone);
msg.fromCq = !GeneralVariables.getCqZoneById(fromCallsignInfo.CQZone);
if (GeneralVariables.isChina) {
msg.fromWhere = fromCallsignInfo.CountryNameCN;
} else {
msg.fromWhere = fromCallsignInfo.CountryNameEn;
}
msg.fromLatLng = new LatLng(fromCallsignInfo.Latitude, fromCallsignInfo.Longitude * -1);
}
if (msg.checkIsCQ() || msg.getCallsignTo().contains("...")) {//CQ就不查了
continue;
}
CallsignInfo toCallsignInfo = getCallsignInfo(db,
msg.callsignTo.replace("<","").replace(">",""));
if (toCallsignInfo != null) {
msg.toDxcc = !GeneralVariables.getDxccByPrefix(toCallsignInfo.DXCC);
msg.toItu = !GeneralVariables.getItuZoneById(toCallsignInfo.ITUZone);
msg.toCq = !GeneralVariables.getCqZoneById(toCallsignInfo.CQZone);
if (GeneralVariables.isChina) {
msg.toWhere = toCallsignInfo.CountryNameCN;
} else {
msg.toWhere = toCallsignInfo.CountryNameEn;
}
msg.toLatLng = new LatLng(toCallsignInfo.Latitude, toCallsignInfo.Longitude*-1);
}
}
}
@SuppressLint("Range")
private static CallsignInfo getCallsignInfo(SQLiteDatabase db, String callsign) {
CallsignInfo callsignInfo = null;
String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" +
"WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" +
"order by LENGTH(callsign) desc\n" +
"LIMIT 1";
Cursor cursor = db.rawQuery(querySQL, new String[]{callsign.toUpperCase(), callsign.toUpperCase()});
if (cursor.moveToFirst()) {
callsignInfo = new CallsignInfo(callsign.toUpperCase()
, cursor.getString(cursor.getColumnIndex("CountryNameEn"))
, cursor.getString(cursor.getColumnIndex("CountryNameCN"))
, cursor.getInt(cursor.getColumnIndex("CQZone"))
, cursor.getInt(cursor.getColumnIndex("ITUZone"))
, cursor.getString(cursor.getColumnIndex("Continent"))
, cursor.getFloat(cursor.getColumnIndex("Latitude"))
, cursor.getFloat(cursor.getColumnIndex("Longitude"))
, cursor.getFloat(cursor.getColumnIndex("GMT_offset"))
, cursor.getString(cursor.getColumnIndex("DXCC")));
}
cursor.close();
return callsignInfo;
}
static class QueryCallsignInformation extends AsyncTask<Void, Void, Void> {
private final SQLiteDatabase db;
private final String sqlParameter;
private final OnAfterQueryCallsignLocation afterQueryCallsignLocation;
public QueryCallsignInformation(SQLiteDatabase db, String sqlParameter, OnAfterQueryCallsignLocation afterQueryCallsignLocation) {
this.db = db;
this.sqlParameter = sqlParameter;
this.afterQueryCallsignLocation = afterQueryCallsignLocation;
}
@SuppressLint("Range")
@Override
protected Void doInBackground(Void... voids) {
// String querySQL = "select a.*,b.* from callsigns as a left join countries as b on a.countryId =b.id \n" +
// "WHERE (SUBSTR(?,1,LENGTH(callsign))=callsign) OR (callsign=\"=\"||?)\n" +
// "order by LENGTH(callsign) desc\n" +
// "LIMIT 1";
//
// Cursor cursor = db.rawQuery(querySQL, new String[]{sqlParameter.toUpperCase(), sqlParameter.toUpperCase()});
// if (cursor.moveToFirst()) {
// CallsignInfo callsignInfo = new CallsignInfo();
// callsignInfo.CallSign = sqlParameter.toUpperCase();
// callsignInfo.CountryNameEn = cursor.getString(cursor.getColumnIndex("CountryNameEn"));
// callsignInfo.CountryNameCN = cursor.getString(cursor.getColumnIndex("CountryNameCN"));
// callsignInfo.CQZone = cursor.getInt(cursor.getColumnIndex("CQZone"));
// callsignInfo.ITUZone = cursor.getInt(cursor.getColumnIndex("ITUZone"));
// callsignInfo.Continent = cursor.getString(cursor.getColumnIndex("Continent"));
// callsignInfo.Latitude = cursor.getFloat(cursor.getColumnIndex("Latitude"));
// callsignInfo.Longitude = cursor.getFloat(cursor.getColumnIndex("Longitude"));
// callsignInfo.GMT_offset = cursor.getFloat(cursor.getColumnIndex("GMT_offset"));
// callsignInfo.DXCC = cursor.getString(cursor.getColumnIndex("DXCC"));
// if (afterQueryCallsignLocation!=null){
// afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo);
// }
// }
// cursor.close();
CallsignInfo callsignInfo = getCallsignInfo(db, sqlParameter);
if (callsignInfo != null && afterQueryCallsignLocation != null) {
afterQueryCallsignLocation.doOnAfterQueryCallsignLocation(callsignInfo);
}
return null;
}
}
static class InitDatabase extends AsyncTask<Void, Void, Void> {
@SuppressLint("StaticFieldLeak")
private final Context context;
private final SQLiteDatabase db;
public InitDatabase(Context context, SQLiteDatabase db) {
this.context = context;
this.db = db;
}
@Override
protected Void doInBackground(Void... voids) {
Log.d(TAG, "开始导入呼号位置数据...");
String insertCountriesSQL = "INSERT INTO countries (id,CountryNameEn,CountryNameCN,CQZone" +
",ITUZone,Continent,Latitude,Longitude,GMT_offset,DXCC)\n" +
"VALUES(?,?,?,?,?,?,?,?,?,?)";
ArrayList<CallsignInfo> callsignInfos =
CallsignFileOperation.getCallSingInfoFromFile(context);
ContentValues values = new ContentValues();
for (int i = 0; i < callsignInfos.size(); i++) {
try {
//把国家和地区数据写进表中id用于关联呼号
db.execSQL(insertCountriesSQL, new Object[]{
i,//id号
callsignInfos.get(i).CountryNameEn,
callsignInfos.get(i).CountryNameCN,
callsignInfos.get(i).CQZone,
callsignInfos.get(i).ITUZone,
callsignInfos.get(i).Continent,
callsignInfos.get(i).Latitude,
callsignInfos.get(i).Longitude,
callsignInfos.get(i).GMT_offset,
callsignInfos.get(i).DXCC});
Set<String> calls = CallsignFileOperation.getCallsigns(callsignInfos.get(i).CallSign);
for (String s : calls
) {
values.put("countryId", i);
values.put("callsign", s);
db.insert("callsigns", null, values);
values.clear();
}
} catch (Exception e) {
Log.e(TAG, "错误:" + e.getMessage());
}
}
Log.d(TAG, "呼号位置数据导入完毕!");
return null;
}
}
}

Wyświetl plik

@ -0,0 +1,140 @@
package com.bg7yoz.ft8cn.callsign;
/**
* CTY.DAT
* @author BG7YOZ
* @date 2023-03-20
*/
import android.content.Context;
import android.content.res.AssetManager;
import com.bg7yoz.ft8cn.GeneralVariables;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class CallsignFileOperation {
public static String TAG="CallsignFileOperation";
public static String[][] countries;
/**
* assetscty.dat
* @param context getAssets()
* @return ArrayList<CallsignInfo> CallsignInfo
*/
public static ArrayList<CallsignInfo> getCallSingInfoFromFile(Context context){
ArrayList<CallsignInfo> callsignInfos=new ArrayList<>();
//读出国家和地区的中英文对应翻译表。保存到countries二维数组中。
countries=getCountryNameToCN(context);
AssetManager assetManager = context.getAssets();
try {
InputStream inputStream= assetManager.open("cty.dat");
String[] st=getLinesFromInputStream(inputStream,";");
for (int i = 0; i <st.length ; i++) {
if (!st[i].contains(":")){
continue;
}
CallsignInfo callsignInfo=new CallsignInfo(st[i]);
//查找对用的中文名字
callsignInfo.CountryNameCN=searchForCountryName(callsignInfo.CountryNameEn);
callsignInfos.add(callsignInfo);
}
inputStream.close();
//Log.d(TAG,String.format("size:%d",st.length));
} catch (IOException e) {
e.printStackTrace();
}
return callsignInfos;
}
/**
* countries01
* @param country
* @return String null
*/
public static String searchForCountryName(String country){
for (int i = 0; i < countries[0].length; i++) {
if (countries[0][i].equals(country)){
return countries[1][i];
}
}
return null;
}
/**
* assetscountry_en2cn.dat
* @param context getAssets
* @return 01
*/
public static String[][] getCountryNameToCN(Context context){
AssetManager assetManager = context.getAssets();
try {
InputStream inputStream;
if (GeneralVariables.isTraditionalChinese) {
inputStream = assetManager.open("country_en2hk.dat");//繁体中文
}else {
inputStream = assetManager.open("country_en2cn.dat");//简体中文
}
String[] st=getLinesFromInputStream(inputStream,"\n");
String[][] countries=new String[2][st.length];
for (int i = 0; i <st.length ; i++) {
if (!st[i].contains(":")){
continue;
}
String[] cc=st[i].split(":");
countries[0][i]=cc[0];
countries[1][i]=cc[1];
}
inputStream.close();
return countries;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* InputStream
* @param inputStream
* @param deLimited
* @return String ,null
*/
public static String[] getLinesFromInputStream(InputStream inputStream, String deLimited) {
try {
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
return (new String(bytes)).split(deLimited);
}catch (IOException e){
return null;
}
}
public static Set<String> getCallsigns(String s){
String[] ls=s.replace("\n","").split(",");
Set<String> callsigns=new HashSet<>();
for (int i = 0; i < ls.length ; i++) {
if (ls[i].contains(")")) {
//Log.d(TAG,ls[i]);
ls[i] = ls[i].substring(0, ls[i].indexOf("("));
//Log.d(TAG,ls[i]+" (((");
}
if (ls[i].contains("[")) {
//Log.d(TAG,ls[i]);
ls[i] = ls[i].substring(0, ls[i].indexOf("["));
//Log.d(TAG,ls[i]+" 【【【");
}
callsigns.add(ls[i].trim());
}
return callsigns;
}
}

Wyświetl plik

@ -0,0 +1,79 @@
package com.bg7yoz.ft8cn.callsign;
/**
*
*
* @author BG7YOZ
* @date 2023-03-20
*/
import android.annotation.SuppressLint;
import android.util.Log;
import androidx.annotation.NonNull;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
public class CallsignInfo {
public static String TAG="CallsignInfo";
public String CallSign;//呼号
public String CountryNameEn;//国家
public String CountryNameCN;//国家中文名
public int CQZone;//CQ分区
public int ITUZone;//ITU分区
public String Continent;//大陆缩写
public float Latitude;//以度为单位的纬度,+ 表示北
public float Longitude;//以度为单位的经度,+ 表示西
public float GMT_offset;//与 GMT 的本地时间偏移
public String DXCC;//DXCC前缀
@SuppressLint("DefaultLocale")
@NonNull
@Override
public String toString() {
String country;
if (GeneralVariables.isChina) {
country=CountryNameCN;
}else {
country=CountryNameEn;
}
//return String.format("呼号:%s\n位置:%s\nCQ分区:%d\nITU分区:%d\n大陆:%s\n经纬度:%.2f,%.2f\n时区:%.0f\nDXCC前缀:%s"
return String.format(GeneralVariables.getStringFromResource(R.string.callsign_info)
, CallSign, country, CQZone, ITUZone, Continent, Longitude, Latitude, GMT_offset, DXCC);
}
public CallsignInfo(String callSign, String countryNameEn,
String countryNameCN, int CQZone, int ITUZone,
String continent, float latitude, float longitude,
float GMT_offset, String DXCC) {
CallSign = callSign;
CountryNameEn = countryNameEn;
CountryNameCN = countryNameCN;
this.CQZone = CQZone;
this.ITUZone = ITUZone;
Continent = continent;
Latitude = latitude;
Longitude = longitude;
this.GMT_offset = GMT_offset;
this.DXCC = DXCC;
}
public CallsignInfo(String s) {
String[] info = s.split(":");
if (info.length<9){
Log.e(TAG,"呼号数据格式错误!"+s);
return;
}
CountryNameEn = info[0].replace("\n", "").trim();
CQZone = Integer.parseInt(info[1].replace("\n", "").replace(" ", ""));
ITUZone = Integer.parseInt(info[2].replace("\n", "").replace(" ", ""));
Continent = info[3].replace("\n", "").replace(" ", "");
Latitude = Float.parseFloat(info[4].replace("\n", "").replace(" ", ""));
Longitude = Float.parseFloat(info[5].replace("\n", "").replace(" ", ""));
GMT_offset = Float.parseFloat(info[6].replace("\n", "").replace(" ", ""));
DXCC = info[7].replace("\n", "").replace(" ", "");
CallSign= info[8].replace("\n", "").replace(" ", "");
}
}

Wyświetl plik

@ -0,0 +1,12 @@
package com.bg7yoz.ft8cn.callsign;
/**
*
*
* @author BG7YOZ
* @date 2023-03-20
*
*/
public interface OnAfterQueryCallsignLocation {
void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo);
}

Wyświetl plik

@ -0,0 +1,103 @@
package com.bg7yoz.ft8cn.connector;
/**
* USB线FLEXICOM
*
* @author BG7YOZ
* @date 2023-03-20
*/
import com.bg7yoz.ft8cn.rigs.OnConnectReceiveData;
import com.bg7yoz.ft8cn.rigs.OnRigStateChanged;
public class BaseRigConnector {
private boolean connected;//是否处于连接状态
private OnConnectReceiveData onConnectReceiveData;//当接收到数据后的动作
private int controlMode;//控制模式
private OnRigStateChanged onRigStateChanged;
private OnConnectorStateChanged onConnectorStateChanged=new OnConnectorStateChanged() {
@Override
public void onDisconnected() {
if (onRigStateChanged!=null){
onRigStateChanged.onDisconnected();
}
connected=false;
}
@Override
public void onConnected() {
if (onRigStateChanged!=null){
onRigStateChanged.onConnected();
}
connected=true;
}
@Override
public void onRunError(String message) {
if (onRigStateChanged!=null){
onRigStateChanged.onRunError(message);
}
connected=false;
}
};
public BaseRigConnector(int controlMode) {
this.controlMode=controlMode;
}
/**
*
* @param data
*/
public synchronized void sendData(byte[] data){};
/**
* PTTON OFF,RTSDTR线CableConnector
* @param on ON
*/
public void setPttOn(boolean on){};
/**
* 使PTT
* @param command
*/
public void setPttOn(byte[] command){};
public void setControlMode(int mode){
controlMode=mode;
}
public int getControlMode() {
return controlMode;
}
public void setOnConnectReceiveData(OnConnectReceiveData receiveData){
onConnectReceiveData=receiveData;
}
public void sendWaveData(float[] data){
//留给网络方式发送音频流
}
public OnConnectReceiveData getOnConnectReceiveData() {
return onConnectReceiveData;
}
public void connect(){
}
public void disconnect(){
}
public OnRigStateChanged getOnRigStateChanged() {
return onRigStateChanged;
}
public void setOnRigStateChanged(OnRigStateChanged onRigStateChanged) {
this.onRigStateChanged = onRigStateChanged;
}
public OnConnectorStateChanged getOnConnectorStateChanged() {
return onConnectorStateChanged;
}
public boolean isConnected(){
return connected;
}
}

Wyświetl plik

@ -0,0 +1,184 @@
package com.bg7yoz.ft8cn.connector;
/**
* Connector,BaseRigConnector
*
* @author BG7YOZ
* @date 2023-03-20
*/
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialListener;
import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialService;
import com.bg7yoz.ft8cn.bluetooth.BluetoothSerialSocket;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import java.io.IOException;
public class BluetoothRigConnector extends BaseRigConnector implements ServiceConnection, BluetoothSerialListener {
private enum Connected {False, Pending, True}
private static BluetoothRigConnector connector=null;
public static BluetoothRigConnector getInstance(Context context, String address, int controlMode){
if (connector!=null){
if (!connector.getDeviceAddress().equals(address)) {
if (connector.connected== Connected.True) {
connector.socketDisconnect();
}
connector.setDeviceAddress(address);
connector.socketConnect();
}
return connector;
}else {
return new BluetoothRigConnector(context,address,controlMode);
}
}
private static final String TAG = "BluetoothRigConnector";
//private static ServiceConnection connection;
private boolean initialStart = true;
private BluetoothSerialService service = null;
private Connected connected = Connected.False;
private String deviceAddress;
private Context context;
public BluetoothRigConnector(Context context, String address, int controlMode) {
super(controlMode);
connector=this;
deviceAddress = address;
this.context = context;
context.stopService(new Intent(context, BluetoothSerialService.class));
context.bindService(new Intent(context, BluetoothSerialService.class), this, Context.BIND_AUTO_CREATE);
}
public String getDeviceAddress() {
return deviceAddress;
}
public void setDeviceAddress(String deviceAddress) {
this.deviceAddress = deviceAddress;
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
service = ((BluetoothSerialService.SerialBinder) iBinder).getService();
service.attach(this);
if (initialStart) {
initialStart = false;
//getActivity().runOnUiThread(this::connect);
socketConnect();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
socketDisconnect();
service = null;
}
@Override
public void onSerialConnect() {
Log.d(TAG, "onSerialConnect: connected");
connected = Connected.True;
getOnConnectorStateChanged().onConnected();
}
@Override
public void onSerialConnectError(Exception e) {
Log.e(TAG, "onSerialConnectError: " + e.getMessage());
getOnConnectorStateChanged().onRunError(e.getMessage());
socketDisconnect();
}
@Override
public void onSerialRead(byte[] data) {
if (data.length > 0) {
//Log.d(TAG, "onSerialRead: " + BaseRig.byteToStr(data));
if (getOnConnectReceiveData()!=null){
getOnConnectReceiveData().onData(data);
}
}
}
@Override
public void onSerialIoError(Exception e) {
Log.e(TAG, "onSerialIoError: " + e.getMessage());
getOnConnectorStateChanged().onRunError(e.getMessage());
socketDisconnect();
}
public void socketDisconnect() {
connected = Connected.False;
getOnConnectorStateChanged().onDisconnected();
service.disconnect();
}
/*
* Serial + UI
*/
public void socketConnect() {
try {
ToastMessage.show(String.format(
GeneralVariables.getStringFromResource(R.string.connect_bluetooth_spp)
,deviceAddress));
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress);
Log.d(TAG, "connecting...");
connected = Connected.Pending;
BluetoothSerialSocket socket = new BluetoothSerialSocket(context, device);
service.connect(socket);
} catch (Exception e) {
onSerialConnectError(e);
}
}
public void sendCommand(byte[] data) {
//Log.d(TAG, "sendCommand: "+BaseRig.byteToStr(data) );
if (connected != Connected.True) {
Log.e(TAG, "sendCommand: 蓝牙没连接");
socketConnect();
return;
}
try {
service.write(data);
} catch (IOException e) {
getOnConnectorStateChanged().onRunError(e.getMessage());
}
}
@Override
public synchronized void sendData(byte[] data) {
sendCommand(data);
}
@Override
public void setPttOn(byte[] command) {
sendData(command);//以CAT指令发送PTT
}
@Override
public void connect() {
super.connect();
socketConnect();
}
@Override
public void disconnect() {
super.disconnect();
socketDisconnect();
}
}

Wyświetl plik

@ -0,0 +1,75 @@
package com.bg7yoz.ft8cn.connector;
import android.content.Context;
import android.util.Log;
import com.bg7yoz.ft8cn.database.ControlMode;
import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
/**
* 线ConnectorUSBBaseRigConnector
*
* @author BG7YOZ
* @date 2023-03-20
*/
public class CableConnector extends BaseRigConnector {
private static final String TAG="CableConnector";
private final CableSerialPort cableSerialPort;
public CableConnector(Context context,CableSerialPort.SerialPort serialPort, int baudRate
, int controlMode) {
super(controlMode);
cableSerialPort= new CableSerialPort(context,serialPort,baudRate,getOnConnectorStateChanged());
cableSerialPort.ioListener=new SerialInputOutputManager.Listener() {
@Override
public void onNewData(byte[] data) {
if (getOnConnectReceiveData()!=null){
getOnConnectReceiveData().onData(data);
}
}
@Override
public void onRunError(Exception e) {
Log.e(TAG, "CableConnector error: "+e.getMessage() );
getOnConnectorStateChanged().onRunError("与串口失去连接:"+e.getMessage());
}
} ;
//connect();
}
@Override
public synchronized void sendData(byte[] data) {
cableSerialPort.sendData(data);
}
@Override
public void setPttOn(boolean on) {
//只处理RTS和DTR
switch (getControlMode()){
case ControlMode.DTR: cableSerialPort.setDTR_On(on);//打开和关闭DTR
break;
case ControlMode.RTS:cableSerialPort.setRTS_On(on);//打开和关闭RTS
break;
}
}
@Override
public void setPttOn(byte[] command) {
cableSerialPort.sendData(command);//以CAT指令发送PTT
}
@Override
public void connect() {
super.connect();
cableSerialPort.connect();
}
@Override
public void disconnect() {
super.disconnect();
cableSerialPort.disconnect();
}
}

Wyświetl plik

@ -0,0 +1,374 @@
package com.bg7yoz.ft8cn.connector;
/**
* USBUSBserialportCDCCH34xCP21xxFTDI
*
* @author BGY70Z
* @date 2023-03-20
*/
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.util.Log;
import com.bg7yoz.ft8cn.BuildConfig;
import com.bg7yoz.ft8cn.serialport.CdcAcmSerialDriver;
import com.bg7yoz.ft8cn.serialport.UsbSerialDriver;
import com.bg7yoz.ft8cn.serialport.UsbSerialPort;
import com.bg7yoz.ft8cn.serialport.UsbSerialProber;
import com.bg7yoz.ft8cn.serialport.util.SerialInputOutputManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
public class CableSerialPort {
private static final String TAG = "CableSerialPort";
private OnConnectorStateChanged onStateChanged;
public static final int SEND_TIMEOUT = 2000;
private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID;
public enum UsbPermission {Unknown, Requested, Granted, Denied}
private UsbPermission usbPermission = UsbPermission.Unknown;
private BroadcastReceiver broadcastReceiver;
private final Context context;
private int vendorId = 0x0c26;//设备号
private int portNum = 0;//端口号
private int baudRate = 19200;//波特率
private UsbSerialPort usbSerialPort;
private SerialInputOutputManager usbIoManager;
public SerialInputOutputManager.Listener ioListener = null;
private UsbManager usbManager;
private UsbDeviceConnection usbConnection;
private UsbSerialDriver driver;
private boolean connected = false;//是否处于连接状态
public CableSerialPort(Context mContext, SerialPort serialPort, int baud, OnConnectorStateChanged connectorStateChanged) {
vendorId = serialPort.vendorId;
portNum = serialPort.portNum;
baudRate = baud;
context = mContext;
this.onStateChanged=connectorStateChanged;
doBroadcast();
}
public CableSerialPort(Context mContext) {
context = mContext;
doBroadcast();
}
private void doBroadcast() {
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (INTENT_ACTION_GRANT_USB.equals(intent.getAction())) {
usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
? UsbPermission.Granted : UsbPermission.Denied;
connect();
}
}
};
}
private boolean prepare() {
registerRigSerialPort(context);
UsbDevice device = null;
usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
//此处把connection设成Null,这样在后面来通过是否null判断是否有权限。
usbConnection = null;
//此处是不是做个权限判断?
if (usbManager == null) {
return false;
}
for (UsbDevice v : usbManager.getDeviceList().values()) {
if (v.getVendorId() == vendorId) {
device = v;
}
}
if (device == null) {
Log.e(TAG, String.format("串口设备打开失败: 没有找到设备0x%04x", vendorId));
return false;
}
driver = UsbSerialProber.getDefaultProber().probeDevice(device);
if (driver == null) {
//试着把未知的设备加入到cdc驱动上
driver = new CdcAcmSerialDriver(device);
}
if (driver.getPorts().size() < portNum) {
Log.e(TAG, "串口号不存在,无法打开。");
return false;
}
Log.e(TAG, "connect: port size:" + String.valueOf(driver.getPorts().size()));
usbSerialPort = driver.getPorts().get(portNum);
usbConnection = usbManager.openDevice(driver.getDevice());
return true;
}
//@RequiresApi(api = Build.VERSION_CODES.S)
public boolean connect() {
connected = false;
if (!prepare()) {
//return false;
}
if (driver == null) {
if (onStateChanged!=null){
onStateChanged.onRunError("无法连接串口,没有驱动或串口不存在!");
}
return false;
}
if (usbConnection == null && usbPermission == UsbPermission.Unknown
&& !usbManager.hasPermission(driver.getDevice())) {
usbPermission = UsbPermission.Requested;
PendingIntent usbPermissionIntent;
//在android12 开始增加了PendingIntent.FLAG_MUTABLE保护机制所以要做版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
usbPermissionIntent = PendingIntent.getBroadcast(context, 0
, new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE);
} else {
usbPermissionIntent = PendingIntent.getBroadcast(context, 0
, new Intent(INTENT_ACTION_GRANT_USB), 0);
}
//PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0
// , new Intent(INTENT_ACTION_GRANT_USB), PendingIntent.FLAG_MUTABLE);
// , new Intent(INTENT_ACTION_GRANT_USB), 0);
usbManager.requestPermission(driver.getDevice(), usbPermissionIntent);
prepare();
}
if (usbConnection == null) {
if (onStateChanged!=null){
onStateChanged.onRunError("无法连接串口可能没有访问USB设备的权限");
}
return false;
}
try {
usbSerialPort.open(usbConnection);
//波特率、停止位
usbSerialPort.setParameters(baudRate, 8, 1, UsbSerialPort.PARITY_NONE);
usbIoManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() {
@Override
public void onNewData(byte[] data) {
if (ioListener != null) {
ioListener.onNewData(data);
}
}
@Override
public void onRunError(Exception e) {
if (ioListener != null) {
ioListener.onRunError(e);
}
disconnect();
}
});
usbIoManager.start();
Log.d(TAG, "串口打开成功!");
connected = true;
if (onStateChanged!=null){
onStateChanged.onConnected();
}
} catch (Exception e) {
Log.e(TAG, "串口打开失败: " + e.getMessage());
if (onStateChanged!=null){
onStateChanged.onRunError("串口打开失败: " + e.getMessage());
}
disconnect();
return false;
}
return true;
}
public boolean sendData(final byte[] src) {
if (usbSerialPort != null) {
try {
usbSerialPort.write(src, SEND_TIMEOUT);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "发送数据出错:" + e.getMessage());
return false;
}
return true;
} else {
Log.e(TAG, "无法发送数据,串口没有打开。");
return false;
}
}
public void disconnect() {
connected = false;
if (onStateChanged!=null){
onStateChanged.onDisconnected();
}
if (usbIoManager != null) {
usbIoManager.setListener(null);
usbIoManager.stop();
}
usbIoManager = null;
try {
if (usbSerialPort != null) {
usbSerialPort.close();
}
} catch (IOException ignored) {
}
usbSerialPort = null;
}
public void registerRigSerialPort(Context context) {
Log.d(TAG, "registerRigSerialPort: registered!");
context.registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB));
}
public void unregisterRigSerialPort(Activity activity) {
Log.d(TAG, "unregisterRigSerialPort: unregistered!");
activity.unregisterReceiver(broadcastReceiver);
}
/**
* RTS
*
* @param rts_on truefalse
*/
public void setRTS_On(boolean rts_on) {
try {
EnumSet<UsbSerialPort.ControlLine> controlLines = usbSerialPort.getSupportedControlLines();
if (controlLines.contains(UsbSerialPort.ControlLine.RTS)) {
usbSerialPort.setRTS(rts_on);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void setDTR_On(boolean dtr_on) {
try {
EnumSet<UsbSerialPort.ControlLine> controlLines = usbSerialPort.getSupportedControlLines();
if (controlLines.contains(UsbSerialPort.ControlLine.DTR)) {
usbSerialPort.setDTR(dtr_on);
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "setDTR_On: " + e.getMessage());
}
}
public OnConnectorStateChanged getOnStateChanged() {
return onStateChanged;
}
public void setOnStateChanged(OnConnectorStateChanged onStateChanged) {
this.onStateChanged = onStateChanged;
}
public int getVendorId() {
return vendorId;
}
public void setVendorId(int deviceId) {
this.vendorId = deviceId;
}
public int getPortNum() {
return portNum;
}
public void setPortNum(int portNum) {
this.portNum = portNum;
}
public int getBaudRate() {
return baudRate;
}
public void setBaudRate(int baudRate) {
this.baudRate = baudRate;
}
/**
*
*
* @param context context
* @return
*/
public static ArrayList<SerialPort> listSerialPorts(Context context) {
ArrayList<SerialPort> serialPorts = new ArrayList<>();
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
for (UsbDevice device : usbManager.getDeviceList().values()) {
UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device);
if (driver == null) {
continue;
//试着把未知的设备加入到cdc驱动上
//driver = new CdcAcmSerialDriver(device);
}
for (int i = 0; i < driver.getPorts().size(); i++) {
serialPorts.add(new SerialPort(device.getDeviceId(), device.getVendorId()
, device.getProductId(), i));
}
}
return serialPorts;
}
public boolean isConnected() {
return connected;
}
public static class SerialPort {
public int deviceId = 0;
public int vendorId = 0x0c26;//厂商号
public int productId = 0;//设备号
public int portNum = 0;//端口号
public SerialPort(int deviceId, int vendorId, int productId, int portNum) {
this.deviceId = deviceId;
this.vendorId = vendorId;
this.productId = productId;
this.portNum = portNum;
}
@SuppressLint("DefaultLocale")
@Override
public String toString() {
return String.format("SerialPort:deviceId=0x%04X, vendorId=0x%04X, portNum=%d"
, deviceId, vendorId, portNum);
}
@SuppressLint("DefaultLocale")
public String information() {
return String.format("\\0x%04X\\0x%04X\\0x%04X\\0x%d", deviceId, vendorId, productId, portNum);
}
}
}

Wyświetl plik

@ -0,0 +1,24 @@
package com.bg7yoz.ft8cn.connector;
/**
*
* @author BGY70Z
* @date 2023-03-20
*/
public class ConnectMode {
public static final int USB_CABLE=0;
public static final int BLUE_TOOTH=1;
public static final int NETWORK=2;
public static String getModeStr(int mode){
switch (mode){
case USB_CABLE:
return "USB Cable";
case BLUE_TOOTH:
return "Bluetooth";
case NETWORK:
return "Network";
default:
return "-";
}
}
}

Wyświetl plik

@ -0,0 +1,325 @@
package com.bg7yoz.ft8cn.connector;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.flex.FlexCommand;
import com.bg7yoz.ft8cn.flex.FlexMeterInfos;
import com.bg7yoz.ft8cn.flex.FlexMeterList;
import com.bg7yoz.ft8cn.flex.FlexRadio;
import com.bg7yoz.ft8cn.flex.RadioTcpClient;
import com.bg7yoz.ft8cn.flex.VITA;
import com.bg7yoz.ft8cn.ui.ToastMessage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
/**
* 线ConnectorUSB
* @author BGY70Z
* @date 2023-03-20
*/
public class FlexConnector extends BaseRigConnector {
public MutableLiveData<FlexMeterList> mutableMeterList=new MutableLiveData<>();
public FlexMeterInfos flexMeterInfos =new FlexMeterInfos("");
public FlexMeterList meterList=new FlexMeterList();
public interface OnWaveDataReceived{
void OnDataReceived(int bufferLen,float[] buffer);
}
public int maxRfPower;
public int maxTunePower;
private static final String TAG = "CableConnector";
private FlexRadio flexRadio;
private OnWaveDataReceived onWaveDataReceived;
public FlexConnector(Context context, FlexRadio flexRadio, int controlMode) {
super(controlMode);
this.flexRadio = flexRadio;
maxTunePower=GeneralVariables.flexMaxTunePower;
maxRfPower=GeneralVariables.flexMaxRfPower;
setFlexRadioInterface();
//connect();
}
public static int[] byteDataTo16BitData(byte[] buffer){
int[] data=new int[buffer.length /2];
for (int i = 0; i < buffer.length/2; i++) {
int res = (buffer[i*2+1] & 0x000000FF) | (((int) buffer[i*2]) << 8);
data[i]=res;
}
return data;
}
private void setFlexRadioInterface() {
flexRadio.setOnReceiveStreamData(new FlexRadio.OnReceiveStreamData() {
@Override
public void onReceiveAudio(byte[] data) {
if (onWaveDataReceived!=null){
float[] buffer=getMonoFloatFromBytes(data);//把24000转成12000立体声转成单声道
onWaveDataReceived.OnDataReceived(buffer.length,buffer);
}
}
@Override
public void onReceiveIQ(byte[] data) {
}
@Override
public void onReceiveFFT(VITA vita) {
//if (vita.streamId==0x40000000) {
// mutableVita.postValue(vita.showHeadStr() + "\n" + vita.showPayloadHex());
//}
}
@Override
public void onReceiveMeter(VITA vita) {
//Log.e(TAG, "onReceiveMeter: "+vita.showPayloadHex() );
meterList.setMeters(vita.payload,flexMeterInfos);
mutableMeterList.postValue(meterList);
}
@Override
public void onReceiveUnKnow(byte[] data) {
}
});
//当有命令返回值时的事件
flexRadio.setOnCommandListener(new FlexRadio.OnCommandListener() {
@Override
public void onResponse(FlexRadio.FlexResponse response) {
if (response.resultValue!=0) {//只显示失败的命令
//ToastMessage.show(response.resultStatus());
//Log.e(TAG, "onResponse: "+response.resultStatus());
}
Log.e(TAG, "onResponse: command:"+response.flexCommand.toString());
//Log.e(TAG, "onResponse: "+response.resultStatus());
Log.e(TAG, "onResponse: "+response.rawData );
if (response.flexCommand== FlexCommand.METER_LIST){
//FlexMeters flexMeters=new FlexMeters(response.exContent);
flexMeterInfos.setMeterInfos(response.exContent);
flexRadio.commandSubMeterAll();//显示全部仪表消息
//flexMeters.getAllMeters();
//Log.e(TAG, "onResponse: ----->>>"+flexMeters.getAllMeters() );
}
if (response.flexCommand==FlexCommand.STREAM_CREATE_DAX_TX){
flexRadio.streamTxId=response.daxTxStreamId;
}
// if (response.flexCommand== FlexCommand.METER_LIST){
// Log.e(TAG, "onResponse: ."+response.rawData.replace("#","\n") );
// }
}
});
//当有状态信息接收到时
flexRadio.setOnStatusListener(new FlexRadio.OnStatusListener() {
@Override
public void onStatus(FlexRadio.FlexResponse response) {
//显示状态消息
//ToastMessage.show(response.content);
Log.e(TAG, "onStatus: "+response.rawData );
}
});
flexRadio.setOnTcpConnectStatus(new FlexRadio.OnTcpConnectStatus() {
@SuppressLint("DefaultLocale")
@Override
public void onConnectSuccess(RadioTcpClient tcpClient) {
ToastMessage.show(String.format(GeneralVariables.getStringFromResource(R.string.init_flex_operation)
,flexRadio.getModel()));
flexRadio.commandClientDisconnect();//断开之前的全部连接
flexRadio.commandClientGui();//创建GUI
flexRadio.commandSubDaxAll();//注册全部DAX流
flexRadio.commandClientSetEnforceNetWorkGui();//对网络MTU做设置
//flexRadio.commandSliceList();//列slice
flexRadio.commandSliceCreate();//创建slice
flexRadio.commandSetDaxAudio(1, 0, true);//打开DAX
//todo 防止流的端口没有释放,把端口变换一下?
//FlexRadio.streamPort++;
flexRadio.commandUdpPort();//设置UDP端口
flexRadio.commandStreamCreateDaxRx(1);//创建流数据到DAX通道1
flexRadio.commandStreamCreateDaxTx(1);//创建流数据到DAX通道1
//TODO 是否设置??? dax tx T 或者 dax tx 1
flexRadio.commandSliceTune(0,String.format("%.3f",GeneralVariables.band/1000000f));
flexRadio.commandSliceSetMode(0, FlexRadio.FlexMode.DIGU);//设置操作模式
flexRadio.commandSetFilter(0, 0, 3000);//设置滤波为3000HZ
flexRadio.commandMeterList();//列一下仪表
//flexRadio.commandSubMeterAll();//此处订阅指令放到了接收响应部分
setMaxRfPower(maxRfPower);//设置发射功率
setMaxTunePower(maxTunePower);//设置调谐功率
//flexRadio.commandSubMeterById(5);//列指定的仪表
//flexRadio.commandSliceSetNR(0, true);
//flexRadio.commandSliceSetNB(0, true);
//flexRadio.commandDisplayPan(10, 10);
//flexRadio.commandSetFilter(0,0,3000);
// flexRadio.sendCommand(FlexCommand.FILT_SET, "filt 0 0 3000");
//flexRadio.commandMeterCreateAmp();
//flexRadio.commandMeterList();
//flexRadio.sendCommand(FlexCommand.INFO, "info");
//flexRadio.commandGetInfo();
//flexRadio.commandSliceGetError(0);
//flexRadio.sendCommand(FlexCommand.SLICE_GET_ERROR, "slice get_error 0");
//flexRadio.sendCommand(FlexCommand.REMOTE_RADIO_RX_ON, "remote_audio rx_on on");
//
//flexRadio.sendCommand("c1|client gui\n");
//playData();
}
@Override
public void onConnectFail(RadioTcpClient tcpClient) {
ToastMessage.show(String.format(GeneralVariables.getStringFromResource
(R.string.flex_connect_failed),flexRadio.getModel()));
}
});
}
public void setMaxRfPower(int power){
maxRfPower=power;
GeneralVariables.flexMaxRfPower=power;
flexRadio.commandSetRfPower(maxRfPower);//设置发射功率
}
public void setMaxTunePower(int power){
maxTunePower=power;
GeneralVariables.flexMaxTunePower=power;
flexRadio.commandSetTunePower(maxTunePower);//设置调谐功率
}
public void startATU(){
flexRadio.commandStartATU();
}
public void tuneOnOff(boolean on){
flexRadio.commandTuneTransmitOnOff(on);
}
public void subAllMeters(){
if (flexMeterInfos.size()==0) {
flexRadio.commandMeterList();//列一下仪表
flexRadio.commandSubMeterAll();//显示全部仪表消息
}
}
@Override
public void sendData(byte[] data) {
flexRadio.sendData(data);
}
@Override
public void setPttOn(boolean on) {
flexRadio.isPttOn=on;
flexRadio.commandPTTOnOff(on);
}
@Override
public void setPttOn(byte[] command) {
//cableSerialPort.sendData(command);//以CAT指令发送PTT
}
@Override
public void sendWaveData(float[] data) {
//Log.e(TAG, "sendWaveData: flexConnector:"+data.length );
flexRadio.sendWaveData(data);
}
@Override
public void connect() {
super.connect();
flexRadio.openAudio();
flexRadio.connect();
flexRadio.openStreamPort();
}
@Override
public void disconnect() {
super.disconnect();
flexRadio.closeAudio();
flexRadio.closeStreamPort();
flexRadio.disConnect();
}
public OnWaveDataReceived getOnWaveDataReceived() {
return onWaveDataReceived;
}
public void setOnWaveDataReceived(OnWaveDataReceived onWaveDataReceived) {
this.onWaveDataReceived = onWaveDataReceived;
}
/**
* ,24000hz12000
* @param bytes
* @return
*/
public static float[] getMonoFloatFromBytes(byte[] bytes) {
float[] floats = new float[bytes.length / 16];
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
for (int i = 0; i < floats.length; i++) {
try {
float f1,f2;
f1=dis.readFloat();
dis.readFloat();//放弃一个声道
f2=dis.readFloat();
floats[i] = Math.max(f1,f2);//取最大值
dis.readFloat();//放弃1个声道
} catch (IOException e) {
e.printStackTrace();
break;
}
}
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
return floats;
}
@Override
public boolean isConnected() {
return flexRadio.isConnect();
}
}

Wyświetl plik

@ -0,0 +1,112 @@
package com.bg7yoz.ft8cn.connector;
/**
* ICom
* IComIntFloat
*
* @author BGY70Z
* @date 2023-03-20
*/
import com.bg7yoz.ft8cn.icom.IComWifiRig;
public class IComWifiConnector extends BaseRigConnector{
private static final String TAG = "IComWifiConnector";
public interface OnWifiDataReceived{
void OnWaveReceived(int bufferLen,float[] buffer);
void OnCivReceived(byte[] data);
}
private IComWifiRig iComWifiRig;
private OnWifiDataReceived onWifiDataReceived;
public IComWifiConnector(int controlMode,IComWifiRig iComWifiRig) {
super(controlMode);
this.iComWifiRig=iComWifiRig;
this.iComWifiRig.setOnIComDataEvents(new IComWifiRig.OnIComDataEvents() {
@Override
public void onReceivedCivData(byte[] data) {
if (getOnConnectReceiveData()!=null){
getOnConnectReceiveData().onData(data);
}
if (onWifiDataReceived!=null) {
onWifiDataReceived.OnCivReceived(data);
}
}
@Override
public void onReceivedWaveData(byte[] data) {//接收音频数据事件把音频数据转换成float格式的。
if (onWifiDataReceived!=null){
float[] waveFloat=new float[data.length/2];
for (int i = 0; i <waveFloat.length ; i++) {
waveFloat[i]=readShortBigEndianData(data,i*2)/32768.0f;
}
onWifiDataReceived.OnWaveReceived(waveFloat.length,waveFloat);
}
}
});
}
@Override
public void sendWaveData(float[] data) {
if (iComWifiRig.opened) {
iComWifiRig.sendWaveData(data);
}
}
@Override
public void connect() {
super.connect();
iComWifiRig.start();
}
@Override
public void disconnect() {
super.disconnect();
iComWifiRig.close();
}
@Override
public void sendData(byte[] data) {
iComWifiRig.sendCivData(data);
}
@Override
public void setPttOn(byte[] command) {
iComWifiRig.sendCivData(command);
}
@Override
public void setPttOn(boolean on) {
if (iComWifiRig.opened){
iComWifiRig.setPttOn(on);
}
}
public OnWifiDataReceived getOnWifiDataReceived() {
return onWifiDataReceived;
}
@Override
public boolean isConnected() {
return iComWifiRig.opened;
}
public void setOnWifiDataReceived(OnWifiDataReceived onDataReceived) {
this.onWifiDataReceived = onDataReceived;
}
/**
* Short
*
* @param data
* @param start
* @return Int16
*/
public static short readShortBigEndianData(byte[] data, int start) {
if (data.length - start < 2) return 0;
return (short) ((short) data[start] & 0xff
| ((short) data[start + 1] & 0xff) << 8);
}
}

Wyświetl plik

@ -0,0 +1,12 @@
package com.bg7yoz.ft8cn.connector;
/**
*
* @author BGY70Z
* @date 2023-03-20
*/
public interface OnConnectorStateChanged {
void onDisconnected();
void onConnected();
void onRunError(String message);
}

Wyświetl plik

@ -0,0 +1,555 @@
package com.bg7yoz.ft8cn.count;
/**
*
*
*
* @author BGY70Z
* @date 2023-03-20
*/
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import com.bg7yoz.ft8cn.GeneralVariables;
import com.bg7yoz.ft8cn.R;
import com.bg7yoz.ft8cn.callsign.CallsignInfo;
import com.bg7yoz.ft8cn.maidenhead.MaidenheadGrid;
import java.util.ArrayList;
public class CountDbOpr {
private static final String TAG="CountDbOpr";
public enum ChartType {Bar, Pie, Line, None}
public static final int ChartBar=0;
public static final int ChartPie=1;
public static final int ChartLine=2;
public static final int ChartNone=3;
public static void getQSLTotal(SQLiteDatabase db,AfterCount afterCount){
//通联的呼号数量
//SELECT count(DISTINCT callsign) FROM QslCallsigns qc
new GetTotal(db,afterCount).execute();
}
public static void getDxcc(SQLiteDatabase db,AfterCount afterCount){
new GetDxccCount(db,afterCount).execute();
}
public static void getCQZoneCount(SQLiteDatabase db,AfterCount afterCount){
new GetCqZoneCount(db,afterCount).execute();
}
public static void getItuCount(SQLiteDatabase db,AfterCount afterCount){
new GetItuZoneCount(db,afterCount).execute();
}
public static void getBandCount(SQLiteDatabase db,AfterCount afterCount){
new GetBandCount(db,afterCount).execute();
}
public static void getDistanceCount(SQLiteDatabase db,AfterCount afterCount){
new DistanceCount(db,afterCount).execute();
}
static class DistanceCount extends AsyncTask<Void,Void,Void>{
private final SQLiteDatabase db;
private final AfterCount afterCount;
public DistanceCount(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint("Range")
@Override
protected Void doInBackground(Void... voids) {
String querySQL;
Cursor cursor;
double maxDistance=-1;
String maxDistanceGrid="";
double minDistance=6553500f;
String minDistanceGrid="";
ArrayList<CountValue> values=new ArrayList<>();
ArrayList<String> bands=new ArrayList<>();
querySQL="select DISTINCT band from QSLTable q WHERE gridsquare<>\"\"";
cursor = db.rawQuery(querySQL,null);
while (cursor.moveToNext()){
bands.add(cursor.getString(cursor.getColumnIndex("band")));
}
for (String band:bands) {
querySQL = "SELECT DISTINCT SUBSTR(gridsquare,1,4) as g FROM QSLTable q where (gridsquare <>\"\")and(band=?)";
cursor = db.rawQuery(querySQL, new String[]{band});
double max=-1;
String maxGrid="";
double min=6553500f;
String minGrid="";
while (cursor.moveToNext()) {
String grid = cursor.getString(cursor.getColumnIndex("g"));
double distance = MaidenheadGrid.getDist(grid, GeneralVariables.getMyMaidenheadGrid());
if (distance > maxDistance) {
maxDistance = distance;
maxDistanceGrid = grid;
}
if (distance < minDistance) {
minDistance = distance;
minDistanceGrid = grid;
}
if (distance > max) {
max = distance;
maxGrid = grid;
}
if (distance < min) {
min = distance;
minGrid = grid;
}
}
cursor.close();
if ((max>-1)&&(min<6553500f)){
values.add(new CountValue((int) Math.round(max),String.format(
GeneralVariables.getStringFromResource(R.string.maximum_distance)
,getQSLInfo(maxGrid))));
values.add(new CountValue((int) Math.round(min),String.format(
GeneralVariables.getStringFromResource(R.string.minimum_distance)
,getQSLInfo(minGrid))));
}
}
String info=String.format(GeneralVariables.getStringFromResource(R.string.count_distance_info)
,maxDistance,maxDistanceGrid,minDistance,minDistanceGrid);
if (afterCount!=null &&(maxDistance>0)&&(minDistance<6553500f)){
afterCount.countInformation(new CountInfo(info
,ChartType.None
,GeneralVariables.getStringFromResource(R.string.distance_statistics)
,values));
}
return null;
}
@SuppressLint("Range")
private String getQSLInfo(String grid){
String querySQL="SELECT call,band,freq,qso_date,time_on,gridsquare FROM QSLTable q " +
"where SUBSTR(gridsquare,1,4) =? LIMIT 1";
Cursor cursor = db.rawQuery(querySQL,new String[]{grid});
StringBuilder result=new StringBuilder();
int breakLine=0;
while (cursor.moveToNext()){
String call=cursor.getString(cursor.getColumnIndex("call"));
String band=cursor.getString(cursor.getColumnIndex("band"));
String freq=cursor.getString(cursor.getColumnIndex("freq"));
//String qso_date=cursor.getString(cursor.getColumnIndex("qso_date"));
//String time_on=cursor.getString(cursor.getColumnIndex("time_on"));
String gridsquare=cursor.getString(cursor.getColumnIndex("gridsquare"));
//获取呼号的位置
CallsignInfo callsignInfo= GeneralVariables.callsignDatabase.getCallInfo(call);
if (breakLine>0) result.append("\n");
result.append(String.format("%s %s(%s) %s\n %s", call, freq, band
,gridsquare,callsignInfo.toString()));
breakLine++;
}
return result.toString();
}
}
/**
*
*/
static class GetBandCount extends AsyncTask<Void, Void, Void> {
private final SQLiteDatabase db;
private final AfterCount afterCount;
public GetBandCount(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint({"Range", "DefaultLocale"})
@Override
protected Void doInBackground(Void... voids) {
int successCount=0;
ArrayList<CountValue> values=new ArrayList<>();
String querySQL;
Cursor cursor;
querySQL="SELECT UPPER( band) as band ,count(*) as c FROM QSLTable q \n" +
"GROUP BY UPPER( band) ORDER BY COUNT(*) desc ";
cursor = db.rawQuery(querySQL,null);
while (cursor.moveToNext()){
int count=cursor.getInt(cursor.getColumnIndex("c"));
successCount=successCount+count;
values.add(new CountValue(count
,String.format("%s",cursor.getString(cursor.getColumnIndex("band")))));
}
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append(String.format(GeneralVariables.getStringFromResource(R.string.count_total),successCount));
for (int i = 0; i < values.size(); i++) {
stringBuilder.append(String.format("\n%s:\t%d",values.get(i).name,values.get(i).value));
}
cursor.close();
if (afterCount!=null){
afterCount.countInformation(new CountInfo(
stringBuilder.toString()
,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.band_statistics)
,values));
}
return null;
}
}
/**
*
*/
static class GetTotal extends AsyncTask<Void, Void, Void>{
private final SQLiteDatabase db;
private final AfterCount afterCount;
public GetTotal(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint({"DefaultLocale", "Range"})
@Override
protected Void doInBackground(Void... voids) {
int logCount=0;//日志的数量
int callsignCount=0;//呼号的数量
int isQslCount=0;//确认的数量
int isLotwQslCount=0;//三方平台确认的数量
//通联的呼号数量
String querySQL;
Cursor cursor;
querySQL="SELECT count(*) AS C FROM QSLTable q";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
logCount=cursor.getInt(cursor.getColumnIndex("C"));
cursor.close();
querySQL="SELECT count(DISTINCT \"call\") AS C FROM QSLTable q ";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
callsignCount=cursor.getInt(cursor.getColumnIndex("C"));
cursor.close();
querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isQSL =1 or isLotW_QSL =1";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
isQslCount=cursor.getInt(cursor.getColumnIndex("C"));
cursor.close();
querySQL="SELECT count(*) AS C FROM QSLTable q WHERE isLotW_QSL =1";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
isLotwQslCount=cursor.getInt(cursor.getColumnIndex("C"));
cursor.close();
float qslPercent=0;
if (logCount>0){
qslPercent=100f*(float) isQslCount/(float) logCount;
}
//result.append(String.format("通联的呼号数量:%d",cursor.getInt(cursor.getColumnIndex("C"))));
if (afterCount!=null){
ArrayList<CountValue> values=new ArrayList<>();
values.add(new CountValue(isQslCount,GeneralVariables.getStringFromResource(R.string.count_confirmed)));
values.add(new CountValue(logCount-isQslCount,GeneralVariables.getStringFromResource(R.string.count_unconfirmed)));
StringBuilder stringBuilder=new StringBuilder();
stringBuilder.append(GeneralVariables.getStringFromResource(R.string.count_total_logs));
stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_log));
stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_lotw_confirmed));
stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_manually_confirmed));
stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_confirmed_proportion));
stringBuilder.append("\n"+GeneralVariables.getStringFromResource(R.string.count_tota_callsigns));
afterCount.countInformation(new CountInfo(
//String.format("日志数:%d\n确认的日志数:%d\n平台确认的日志数:%d" +
// "\n手动确认的日志数:%d\n日志确认比例:%.1f%%\n呼号数量:%d"
String.format(stringBuilder.toString()
,logCount,isQslCount,isLotwQslCount,isQslCount-isLotwQslCount,qslPercent,callsignCount)
,ChartType.Pie,GeneralVariables.getStringFromResource(R.string.confirmation_statistics)
,values));
}
return null;
}
}
/**
* ITU
*/
static class GetItuZoneCount extends AsyncTask<Void, Void, Void> {
private static final String TAG = "GetItuZoneCount";
private final SQLiteDatabase db;
private final AfterCount afterCount;
public GetItuZoneCount(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint({"Range", "DefaultLocale"})
@Override
protected Void doInBackground(Void... voids) {
ArrayList<CountValue> countValues=new ArrayList<>();
int ituZoneCount=0;
int successCount=0;
String querySQL;
Cursor cursor;
querySQL="SELECT count(DISTINCT itu) as c From ituList il ";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
ituZoneCount=cursor.getInt(cursor.getColumnIndex("c"));
cursor.close();
querySQL="SELECT il.itu ,count(*) as c FROM ituList il \n" +
"inner join QSLTable q\n" +
"on il.grid =UPPER(SUBSTR(q.gridsquare,1,4))\n" +
"GROUP BY il.itu ORDER BY COUNT(*) DESC";
cursor = db.rawQuery(querySQL,null);
while (cursor.moveToNext()){
successCount++;
countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c"))
,String.format(GeneralVariables.getStringFromResource(R.string.count_zone)
,cursor.getString(cursor.getColumnIndex("itu")))));
}
if (afterCount!=null){
afterCount.countInformation(new CountInfo(""
,ChartType.Bar
,GeneralVariables.getStringFromResource(R.string.itu_completion_statistics)
,countValues));
}
if (afterCount!=null&&ituZoneCount!=0){
ArrayList<CountValue> values=new ArrayList<>();
values.add(new CountValue(successCount
,GeneralVariables.getStringFromResource(R.string.count_completed)));
values.add(new CountValue(ituZoneCount-successCount
,GeneralVariables.getStringFromResource(R.string.count_incomplete)));
afterCount.countInformation(new CountInfo(
String.format(GeneralVariables.getStringFromResource(R.string.count_total_itu)
,ituZoneCount,successCount,100f*(float)successCount/(float) ituZoneCount)
,ChartType.Pie
,GeneralVariables.getStringFromResource(R.string.count_itu_completed_scale)
,values));
}
return null;
}
}
/**
* CQ
*/
static class GetCqZoneCount extends AsyncTask<Void, Void, Void> {
private static final String TAG = "GetCqZoneCount";
private final SQLiteDatabase db;
private final AfterCount afterCount;
public GetCqZoneCount(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint({"Range", "DefaultLocale"})
@Override
protected Void doInBackground(Void... voids) {
ArrayList<CountValue> countValues=new ArrayList<>();
int cqZoneCount=0;
int successCount=0;
String querySQL;
Cursor cursor;
querySQL="SELECT count(DISTINCT cqzone) as c From cqzoneList cl ";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
cqZoneCount=cursor.getInt(cursor.getColumnIndex("c"));
cursor.close();
querySQL="SELECT cl.cqzone ,count(*) as c FROM cqzoneList cl \n" +
"inner join QSLTable q \n" +
"on cl.grid =UPPER(SUBSTR(q.gridsquare,1,4)) \n" +
"GROUP BY cl.cqzone ORDER BY COUNT(*) DESC";
cursor = db.rawQuery(querySQL,null);
while (cursor.moveToNext()){
successCount++;
countValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c"))
,String.format(GeneralVariables.getStringFromResource(R.string.count_zone)
,cursor.getString(cursor.getColumnIndex("cqzone")))));
}
if (afterCount!=null){
afterCount.countInformation(new CountInfo(""
,ChartType.Bar
,GeneralVariables.getStringFromResource(R.string.count_cqzone_completed)
,countValues));
}
if (afterCount!=null&&cqZoneCount!=0){
ArrayList<CountValue> values=new ArrayList<>();
values.add(new CountValue(successCount
,GeneralVariables.getStringFromResource(R.string.count_completed)));
values.add(new CountValue(cqZoneCount-successCount
,GeneralVariables.getStringFromResource(R.string.count_incomplete)));
afterCount.countInformation(new CountInfo(
String.format(GeneralVariables.getStringFromResource(R.string.count_total_cqzone)
,cqZoneCount,successCount,100f*(float)successCount/(float) cqZoneCount)
,ChartType.Pie
,GeneralVariables.getStringFromResource(R.string.count_cqzone_proportion)
,values));
}
return null;
}
}
/**
* DXCC
*/
static class GetDxccCount extends AsyncTask<Void, Void, Void>{
private static final String TAG="GetDxccCount";
private final SQLiteDatabase db;
private final AfterCount afterCount;
class DxccInfo{
String name;
int dxcc;
int count=0;
public DxccInfo(String name, int dxcc, int count) {
this.name = name;
this.dxcc = dxcc;
this.count = count;
}
}
public GetDxccCount(SQLiteDatabase db, AfterCount afterCount) {
this.db = db;
this.afterCount = afterCount;
}
@SuppressLint({"Range", "DefaultLocale"})
@Override
protected Void doInBackground(Void... voids) {
//DXCC的数量
ArrayList<CountValue> dxccValues=new ArrayList<>();
int dxccCount=0;
Cursor cursor;
String querySQL;
querySQL="SELECT count(*) as c FROM dxcclist";
cursor = db.rawQuery(querySQL,null);
cursor.moveToFirst();
dxccCount=cursor.getInt(cursor.getColumnIndex("c"));
cursor.close();
if (GeneralVariables.isChina) {
querySQL = "SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_grid dg\n" +
"inner join QSLTable q \n" +
"on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" +
"GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC";
// querySQL="SELECT dg.dxcc,count(*) as c ,dl.name as dxccName FROM dxcc_prefix dg\n" +
// "inner join QSLTable q\n" +
// //"on (q.call like (dg.prefix||'%'))\n" +
// "on ((SUBSTR( q.call,1,LENGTH( dg.prefix)) = (dg.prefix))) \n"+
// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" +
// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC";
}else {
// querySQL="SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_prefix dg\n" +
// "inner join QSLTable q\n" +
// "on (q.call like (dg.prefix||'%'))\n" +
// "LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" +
// "GROUP BY dg.dxcc ,dl.name ORDER BY count(*) DESC";
querySQL = "SELECT dg.dxcc,count(*) as c ,dl.aname as dxccName FROM dxcc_grid dg\n" +
"inner join QSLTable q \n" +
"on dg.grid =UPPER(SUBSTR(q.gridsquare,1,4)) LEFT JOIN dxccList dl on dg.dxcc =dl.dxcc \n" +
"GROUP BY dg.dxcc ,dl.aname ORDER BY count(*) DESC";
}
cursor = db.rawQuery(querySQL,null);
int successCount=0;
while (cursor.moveToNext()){
dxccValues.add(new CountValue(cursor.getInt(cursor.getColumnIndex("c"))
,cursor.getString(cursor.getColumnIndex("dxccName"))));
successCount++;
}
if (afterCount!=null){
afterCount.countInformation(new CountInfo(""
,ChartType.Bar
,GeneralVariables.getStringFromResource(R.string.dxcc_completion_statistics)
,dxccValues));
}
if (afterCount!=null&&dxccCount!=0){
ArrayList<CountValue> values=new ArrayList<>();
values.add(new CountValue(successCount
,GeneralVariables.getStringFromResource(R.string.count_completed)));
values.add(new CountValue(dxccCount-successCount
,GeneralVariables.getStringFromResource(R.string.count_incomplete)));
afterCount.countInformation(new CountInfo(
String.format(GeneralVariables.getStringFromResource(R.string.count_total_dxcc)
,dxccCount,successCount,100f*(float)successCount/(float) dxccCount)
,ChartType.Pie
,GeneralVariables.getStringFromResource(R.string.count_dxcc_proportion)
,values));
}
return null;
}
}
public interface AfterCount{
void countInformation(CountInfo countInfo);
}
static public class CountInfo{
public String title;
public String info;
public ArrayList<CountValue> values=null;
public ChartType chartType;
public CountInfo(String info,ChartType chartType,String title
, ArrayList<CountValue> values) {
this.info = info;
this.values = values;
this.title=title;
this.chartType=chartType;
}
}
static public class CountValue{
public int value;
public String name;
public CountValue(int value, String name) {
this.value = value;
this.name = name;
}
}
}

Wyświetl plik

@ -0,0 +1,93 @@
package com.bg7yoz.ft8cn.count;
/**
*
*
* @author BGY70Z
* @date 2023-03-20
*/
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bg7yoz.ft8cn.MainViewModel;
import com.bg7yoz.ft8cn.databinding.FragmentCountBinding;
import java.util.ArrayList;
public class CountFragment extends Fragment {
private static final String TAG="CountFragment";
private MainViewModel mainViewModel;
private FragmentCountBinding binding;
private ArrayList<CountDbOpr.CountInfo> countInfoList=new ArrayList<>();
private MutableLiveData<ArrayList<CountDbOpr.CountInfo>> mutableInfoList=new MutableLiveData<>();
private RecyclerView countInfoListRecyclerView;
private CountInfoAdapter countInfoAdapter;
private CountDbOpr.AfterCount afterCount=new CountDbOpr.AfterCount() {
@Override
public void countInformation(CountDbOpr.CountInfo countInfo) {
countInfoList.add(countInfo);
mutableInfoList.postValue(countInfoList);
}
};
public CountFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainViewModel = MainViewModel.getInstance(this);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding=FragmentCountBinding.inflate(getLayoutInflater());
mainViewModel = MainViewModel.getInstance(this);
countInfoList.clear();
countInfoListRecyclerView=binding.countRecyclerView;
countInfoAdapter=new CountInfoAdapter(requireContext(),countInfoList);
countInfoListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
countInfoListRecyclerView.setAdapter(countInfoAdapter);
countInfoAdapter.notifyDataSetChanged();
CountDbOpr.getQSLTotal(mainViewModel.databaseOpr.getDb(),afterCount);
CountDbOpr.getDxcc(mainViewModel.databaseOpr.getDb(),afterCount);
CountDbOpr.getCQZoneCount(mainViewModel.databaseOpr.getDb(),afterCount);
CountDbOpr.getItuCount(mainViewModel.databaseOpr.getDb(),afterCount);
CountDbOpr.getBandCount(mainViewModel.databaseOpr.getDb(),afterCount);
CountDbOpr.getDistanceCount(mainViewModel.databaseOpr.getDb(),afterCount);
mutableInfoList.observe(requireActivity(), new Observer<ArrayList<CountDbOpr.CountInfo>>() {
@Override
public void onChanged(ArrayList<CountDbOpr.CountInfo> countInfos) {
countInfoAdapter.notifyDataSetChanged();
}
});
return binding.getRoot();
}
}

Wyświetl plik

@ -0,0 +1,260 @@
package com.bg7yoz.ft8cn.count;
/**
* Adapter
*
* @author BGY70Z
* @date 2023-03-20
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bg7yoz.ft8cn.R;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.components.Description;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
import java.util.ArrayList;
import java.util.List;
public class CountInfoAdapter extends RecyclerView.Adapter<CountInfoAdapter.CountInfoItemHolder>{
private static final String TAG="CountInfoAdapter";
private final ArrayList<CountDbOpr.CountInfo> countInfoList;
private final Context context;
public CountInfoAdapter(Context context,ArrayList<CountDbOpr.CountInfo> countInfoList) {
this.countInfoList = countInfoList;
this.context=context;
}
@NonNull
@Override
public CountInfoItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view ;
switch (viewType){
case CountDbOpr.ChartPie:
view= layoutInflater.inflate(R.layout.count_info_pie_item, parent, false);
break;
case CountDbOpr.ChartBar:
view= layoutInflater.inflate(R.layout.count_info_bar_item, parent, false);
break;
default:
view= layoutInflater.inflate(R.layout.count_info_none_item, parent, false);
break;
}
return new CountInfoItemHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CountInfoItemHolder holder, int position) {
holder.countInfo=countInfoList.get(position);
holder.countInfoTextView.setText(holder.countInfo.info);
holder.countTitleTextView.setText(holder.countInfo.title);
switch (holder.countInfo.chartType){
case Pie:
drawPie(holder);
break;
case Bar:
drawBar(holder);
break;
default:
addDetail(holder);
}
}
private void addDetail(@NonNull CountInfoItemHolder holder){
if (holder.detailLayout==null) return;
holder.detailLayout.removeAllViews();
for (int i = 0; i <holder.countInfo.values.size() ; i++) {
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT
, LinearLayout.LayoutParams.WRAP_CONTENT);
View view=LayoutInflater.from(context).inflate(R.layout.count_detail_item,null);
view.setLayoutParams(lp);
if (i%2==0) {
view.setBackgroundResource(R.drawable.count_detail_item_0_style);
}else {
view.setBackgroundResource(R.drawable.count_detail_item_1_style);
}
TextView tv1=view.findViewById(R.id.countDetailTextView);
tv1.setText(holder.countInfo.values.get(i).name);
TextView tv2=view.findViewById(R.id.countDetailValueTextView);
tv2.setText(String.valueOf(holder.countInfo.values.get(i).value));
holder.detailLayout.addView(view);
}
}
private void drawBar(@NonNull CountInfoItemHolder holder){
addDetail(holder);
BarChart barChart;
barChart=(BarChart) holder.countChart;
barChart.getLegend().setEnabled(false);//不显示图例
barChart.getXAxis().setDrawLabels(true);//显示X轴标签
barChart.getXAxis().setDrawGridLines(false);//不显示X网格线
barChart.getXAxis().setPosition(XAxis.XAxisPosition.TOP);
barChart.getXAxis().setTextColor(context.getResources().getColor(R.color.text_view_color));
barChart.getLegend().setTextColor(context.getResources().getColor(R.color.text_view_color));
barChart.getAxisLeft().setTextColor(context.getResources().getColor(R.color.text_view_color));
List<BarEntry> barEntries=new ArrayList<>();
for (int i = 0; i <holder.countInfo.values.size() ; i++) {
BarEntry barEntry = new BarEntry((float) (i)
, (float) holder.countInfo.values.get(i).value);
barEntries.add(barEntry);
}
BarDataSet dataSet = new BarDataSet(barEntries, null);
dataSet.setColors(new int[]{R.color.char_bar_1 },context);
BarData barData = new BarData(dataSet);
barData.setValueTextColor(context.getResources().getColor(R.color.text_view_color));
barChart.setData(barData);
//显示标签信息
IndexAxisValueFormatter formatter=new IndexAxisValueFormatter(){
@Override
public String getFormattedValue(float value) {
if (holder.countInfo.values.size()>(int) value) {
return holder.countInfo.values.get((int) value).name;
}else {
return "";
}
}
};
barChart.getXAxis().setValueFormatter(formatter);
barChart.setDescription(null);
barChart.animateY(500);
barChart.invalidate();
}
private void drawPie(@NonNull CountInfoItemHolder holder){
PieChart countPieChart;
countPieChart=(PieChart) holder.countChart;
countPieChart.getLegend().setTextColor(context.getResources().getColor(R.color.text_view_color));
List<PieEntry> pieEntries=new ArrayList<>();
for (int i = 0; i <holder.countInfo.values.size() ; i++) {
pieEntries.add(new PieEntry(holder.countInfo.values.get(i).value,holder.countInfo.values.get(i).name));
}
PieDataSet dataSet=new PieDataSet(pieEntries,null);
dataSet.setColors(new int[]{R.color.char_bar_1
,R.color.char_bar_2
,R.color.char_bar_3
,R.color.char_bar_4
,R.color.char_bar_5
,R.color.char_bar_6
,R.color.char_bar_7
,R.color.char_bar_8
,R.color.char_bar_9
,R.color.char_bar_10
,R.color.char_bar_11
,R.color.char_bar_12
,R.color.char_bar_13
,R.color.char_bar_14
,R.color.char_bar_15
,R.color.char_bar_16
},context);
PieData pieData=new PieData(dataSet);
pieData.setValueTextColor(context.getResources().getColor(R.color.text_view_color));
countPieChart.setData(pieData);
Description description = new Description();
description.setText(holder.countInfo.title);
countPieChart.setDescription(null);
countPieChart.animateY(500);
countPieChart.invalidate();
}
@Override
public int getItemCount() {
return countInfoList.size();
}
@Override
public int getItemViewType(int position) {
switch (countInfoList.get(position).chartType){
case Bar:return CountDbOpr.ChartBar;
case Pie:return CountDbOpr.ChartPie;
case Line:return CountDbOpr.ChartLine;
case None:return CountDbOpr.ChartNone;
}
return super.getItemViewType(position);
}
static class CountInfoItemHolder extends RecyclerView.ViewHolder{
TextView countInfoTextView,countTitleTextView;
CountDbOpr.CountInfo countInfo;
Object countChart;
LinearLayout detailLayout;
ImageButton countDownImageButton,countUpImageButton;
public CountInfoItemHolder(@NonNull View itemView) {
super(itemView);
countInfoTextView = itemView.findViewById(R.id.countInfoTextView);
countTitleTextView = itemView.findViewById(R.id.countTitleTextView);
countDownImageButton = itemView.findViewById(R.id.countDownImageButton);
countUpImageButton = itemView.findViewById(R.id.countUpImageButton);
detailLayout = itemView.findViewById(R.id.countDetailLayout);
countChart = itemView.findViewById(R.id.countChart);
if (countUpImageButton!=null) {
countUpImageButton.setVisibility(View.GONE);
countUpImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (detailLayout!=null){
detailLayout.setVisibility(View.GONE);
countUpImageButton.setVisibility(View.GONE);
countDownImageButton.setVisibility(View.VISIBLE);
}
}
});
}
if (countDownImageButton!=null){
countDownImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (detailLayout!=null){
detailLayout.setVisibility(View.VISIBLE);
countDownImageButton.setVisibility(View.GONE);
countUpImageButton.setVisibility(View.VISIBLE);
}
}
});
}
if (detailLayout!=null){
detailLayout.setVisibility(View.GONE);
countDownImageButton.setVisibility(View.VISIBLE);
}
}
}
}

Wyświetl plik

@ -0,0 +1,5 @@
package com.bg7yoz.ft8cn.database;
public interface AfterInsertQSLData {
void doAfterInsert(boolean isInvalid,boolean isNewQSL);
}

Some files were not shown because too many files have changed in this diff Show More