kopia lustrzana https://github.com/N0BOY/FT8CN
v0.9 release
- 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-171pull/66/head
rodzic
d6e50da2e0
commit
824b4bdd00
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -0,0 +1 @@
|
|||
Ft8CN
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RenderSettings">
|
||||
<option name="useLiveRendering" value="false" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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'//用于HashTable(多key的HashMap)
|
||||
|
||||
|
||||
|
||||
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.
|
@ -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.
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
|||
关于音频输出设置
|
||||
|
||||
采样位深:也称采样精度,FT8CN只有16位整型和32位浮点可选。采样位数是表示声音强度量化后的精细程度,它的数值越大,波动幅度的分辨率也就越高,所发出声音的能力越强。
|
||||
|
||||
采样率:也称取样频率, 指每秒钟取得声音样本的次数。采样频率越高,声音的质量也就越好,但占的资源也多。
|
|
@ -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.
|
|
@ -0,0 +1,5 @@
|
|||
关于自动关注CQ
|
||||
自动关注CQ,是指当FT8CN解码出的消息中有CQ消息时,会把这条消息推送到“呼叫”界面的消息列表,方便在“呼叫”界面中对CQ消息做出反应。自动关注的CQ消息,该呼号不会被长久保存到关注的呼号数据库中。
|
||||
关于自动呼叫关注的呼号
|
||||
目标呼号被设定为关注后,当目标呼号有CQ行为时,FT8CN会自动对该呼号进行呼叫。
|
||||
FT8CN会长久保存您关注的呼号,您每次使用FT8CN时,FT8CN会把曾经关注过的呼号的新消息推送到“呼叫”界面的列表中。如果想取消对某呼号的关注,可以通过后台操作(在局域网中用浏览器访问FT8CN)删除该呼号。
|
|
@ -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).
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
请输入您的呼号。
|
||||
呼号是您用本APP进行FT8呼叫的基本要求。您如果在中国境内,请使用符合《中华人民共和国无线电管理条例》的合法呼号,禁止一切非法发射!
|
||||
|
||||
关于修饰符
|
||||
CQ后面可能跟有三个十进制数字或一到四个字母的修饰符,一般是000-999或A-Z或AA-ZZ或AAA-ZZZ或AAAA-ZZZZ。例如:CQ POTA callsign grid。
|
|
@ -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. It’s 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*
|
|
@ -0,0 +1,5 @@
|
|||
关于CI-V地址和波特率
|
||||
CI-V地址:是指电台的CI-V地址。CI-V(Communications Interface V)是控制端与电台之间的一种通讯协议。
|
||||
波特率:电台与APP之间的数据传输速率。
|
||||
按照CI-V的要求,必须要有彼此的通讯地址来区分(因为控制线路上可能有不止一台设备)接收端和发送端,一般来说,每个型号的设备在出厂时都有默认的地址,不同型号的电台默认地址不一定相同。控制端的默认地址一般是0xE0,广播地址是0x00。以IC-705为例,它的默认地址是0xA4。
|
||||
本APP已经查找到部分型号电台的默认地址以及该型号电台最大可支持的波特率,可以通过选择“电台型号”自动设置地址和波特率。
|
|
@ -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 general,the 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".
|
|
@ -0,0 +1,9 @@
|
|||
关于清空缓存
|
||||
|
||||
FT8CN在数据库中保存有您关注的呼号、通联过程中的消息(可以通过后台查看)。可以通过清空缓存操作彻底删除这些保存的临时数据。
|
||||
|
||||
清空缓存不会删除QSO数据。
|
||||
|
||||
清空“关注的呼号”,关注的呼号是在平时通联是您关注的呼号,在该呼号出现时,会自动添加的呼叫列表中,方便自动呼叫。清空关注的呼号,是把所有关注的呼号清空。
|
||||
|
||||
清空“解码的消息”,FT8CN可以保存所有解码的消息,这些消息暂时没有在FT8CN界面中显示,可以在后台中查看和查询。
|
|
@ -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)
|
|
@ -0,0 +1,7 @@
|
|||
连接方式,是指FT8CN控制电台的连接方式。
|
||||
FT8CN与电台之间有三种连接方式:有线连接(USB转串口)、蓝牙连接、网络连接。对于有线连接和蓝牙连接,FT8CN都是通过串口指令对电台的操作进行控制(PTT开/关、频率设置、模式设置等)。而网络连接是使用电台的网络协议对电台进行控制,包括收发的音频也是通过网络进行传输。
|
||||
目前,FT8CN已经支持ICOM、FlexRadio网络协议,ICOM已经完全支持音频信号的收发,FlexRadio目前仅支持音频的接收。
|
||||
注:
|
||||
1.只有手机在蓝牙开启状态才能进入蓝牙连接模式。
|
||||
2.蓝牙串口与蓝牙音频不同,蓝牙音频只能用于音频的收发,无法控制电台。具有蓝牙串口的电台才能被FT8CN控制。
|
||||
3.在ICOM使用网络连接时,传输过程对路由器性能要求很高,低性能的路由器,在发射音频时会有严重的丢包情况,建议使用电台的AP或手机的AP来连接。
|
|
@ -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.
|
|
@ -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拓展坞可能会使电台的底噪增加,请选择合适的转接口。
|
|
@ -0,0 +1,16 @@
|
|||
Control Mode refers to the control mode of the RIG when FT8CN transmits signals.
|
||||
VOX:Signal transmission is initiated by voice control.
|
||||
CAT:It 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.
|
|
@ -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.:爱德华王子和马里恩岛
|
|
@ -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.:爱德华王子群岛和马里恩岛
|
|
@ -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
|
@ -0,0 +1,4 @@
|
|||
关于解码模式
|
||||
快速解码:沿用原来FT8CN的解码,对接收到的音频做一次性的解码。
|
||||
多次解码:在快速解码的基础上,再做多次的解码,增加迭代的次数,并尝试解码频率有叠加的信号。
|
||||
注:多次解码增加的运算量,总的解码时间会变长,会缩短设备的续航时间。
|
|
@ -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.
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,3 @@
|
|||
排除的呼号前缀。是指FT8CN解码消息后,不会把带有指定呼号前缀列入到“呼叫”列表中,为自动程序排除掉对该呼号的呼叫。
|
||||
呼号前缀可以是多个,用逗号、或空格分隔。
|
||||
排除的呼号前缀的动作具有最高优先级,高于“关注”、“CQ”、”与我有关“。
|
|
@ -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".
|
|
@ -0,0 +1,4 @@
|
|||
发射FT8信号的频率是指在当前波段上,发出的声音频率,频率范围一般是10-3000赫兹之间。
|
||||
同频发射:是指声音频率使用您呼叫目标的频率。此时默认频率值无效。
|
||||
异频发射:是指您的声音频率使用设定的默认频率。在异频发射模式下,您也可以在频谱界面中通过触摸瀑布图修改默认频率。
|
||||
关于发射频率,为了防止您的发射频率与其他友台的频率重叠,建议在频谱中观察一下当前所接收到的信号的频率情况,然后选择与接收的各信号不重叠的频率。
|
|
@ -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=RX(locked transmission): It refers to the frequency of your target call. The default frequency value is not valid at this point.
|
||||
Tx/Rx Split(split 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
|
@ -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 exceeded,the launch procedure will automatically stop.
|
|
@ -0,0 +1,3 @@
|
|||
关于发射监管
|
||||
与JT65等协议的1分钟通联时序,用户可以有大约10秒钟的时间来决定是否继续下一阶段的动作,而FT8和FT4的较短序列需要更快的响应,在实际使用中需要软件内置适量的自动化,这就是FT8CN中使用自动程序的原因。
|
||||
FT8CN赞同乔尔·泰勒理念,反对使用机器人用于FT8操作,只能有限度地使用自动化程序,所以FT8CN要对自动程序进行监管,当无人干预超过发射监管时间限制后,发射程序将自动停止。
|
|
@ -0,0 +1,3 @@
|
|||
请输入您梅登海德网格信息,至少4位,如果是6位,在FT8呼叫时会自动忽略后两位。
|
||||
如果您给本APP授权定位的权限,点击输入框旁边的定位按钮,APP会自动计算出您当前所在的网格。
|
||||
通过您的网格数据,APP会自动计算发起呼叫的电台与您的距离。
|
|
@ -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.
|
@ -0,0 +1,2 @@
|
|||
关于无回应次数则中断呼叫
|
||||
在与对方通联时,基于各种原因,对方没有回应您的呼叫,FT8CN会以当前阶段的消息内容重复呼叫,直到对方回应才进入下一阶段的呼叫。当设置无回应的次数时,当达到重复呼叫的次数达到设定值时,自动程序会停止对该目标的通联,转入下一个目标
|
|
@ -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.
|
|
@ -0,0 +1,3 @@
|
|||
载波频段,是指电台在发射时所使用的载波频段。载波频段,会记录到每一个通联的日志中。
|
||||
在CAT模式下,APP会自动设置电台的载波频段。
|
||||
在VOX模式下,只用于保存日志。
|
|
@ -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.
|
|
@ -0,0 +1,2 @@
|
|||
PTT延迟,是指本电台接收到PTT按下的指令后的响应时间。
|
||||
当FT8CN发送PTT按下的指令后,往往电台会稍有滞后的响应,设定此时间,是为了电台真正处于发射状态后,FT8CN才发送声音,防止因PTT滞后造成声音无法完全发送。
|
|
@ -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.
|
|
@ -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.完成国赫,YAESU,KENWOOD部分型号电台的指令集。
|
||||
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进行调试。
|
||||
BG8BXM(M哥),为FT8CN的使用做推广,抖音和B站上有很多他的教学视频。
|
||||
BG7MFQ,为FT8CN的使用做推广,帮助测试。
|
||||
BG2EFX,提供大数据量的日志用于测试。
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
关于电台型号:根据选择的电台型号,FT8CN有针对性的使用可以控制该电台的指令集,不同的品牌的电台指令集并不相同,请在列表中选择您正在使用的电台。
|
||||
对于ICOM系列电台,本列表中所列的是已知默认CI-V地址和最大支持波特率的各电台型号,可以通过选择本列表中的电台,自动设置CI-V地址和波特率。如果您使用的电台型号不在本列表中,需要您自行设置合适的CI-V地址和波特率。
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
SWL是英文Shortwave Listener的简称,即短波收听爱好者。
|
||||
SWL模式是仅仅收听其他业余无线电爱好者的通联,而不进行无线电发射的模式。
|
||||
针对SWL,FT8CN增加了“保存解码消息”和“保存SWL记录”功能。
|
||||
保存解码消息:就是把FT8CN所有解码的消息都保存下来。
|
||||
保存SWL记录:就是把守听到的其他业余无线电台通联的QSO日志保存下来。在FT8CN中,认定成功的SWL QSO标准是:有结束语(73、RR73、RRR)且有双方的信号报告,双方的网格报告不是必须项。
|
||||
保存的“解码消息”和“SWL记录”可以在后台(需要在同网段的局域网,用浏览器访问)做查询以及导出操作。
|
||||
保存的“解码消息”和“SWL记录”的删除操作在”设置界面“的”清空缓存“中操作。
|
|
@ -0,0 +1,7 @@
|
|||
SWL stands for Shortwave Listener, whom may not hold an amateur radio licence to transmit but listen and report amateur radio QSOs. FT8CN supports "Save decoded messages" and "Save SWL QSO" for SWL.
|
||||
|
||||
Save decoded messages: FT8CN will save all decoded messages.
|
||||
Save SWL QSO: FT8CN can save QSO log as SWL when both stations provide signal report and 73/RR73/RRR (Gridsqure is optional).
|
||||
|
||||
You can look up and export saved decoded messages or SWL QSO log via Web UI (under the same LAN).
|
||||
You can delete them using "Clear Cache Data" in settings.
|
|
@ -0,0 +1,3 @@
|
|||
时间偏移,是指本APP在每个周期相对于系统时钟的偏移值。
|
||||
FT8的时钟周期是15秒,所有电台的时钟都是以UTC时间为基准,以每分钟的第0秒、15秒、30秒、45秒为周期的起始时间。
|
||||
如果可以访问网络,FT8CN会自动同步时间,如果不能访问网络,可以手动设定偏移值。
|
|
@ -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.
|
|
@ -0,0 +1,7 @@
|
|||
设置发射延迟(以毫秒为单位)是为了在上一个监听周期结束后,给解码运算一个时间。
|
||||
设定一个延迟可以在发射前获知上一个周期对方的反馈情况,并在本周期内做出反应。
|
||||
延迟时间的设定取决于本设备的运算能力,延迟时间不能小于解码运算的时间长度。
|
||||
如果延迟时间过短,可能无法对上一周期的信号做出反应。
|
||||
如果延迟时间过长,其它电台可能无法正常解析出您的信号。
|
||||
在实际应用中,由于在给电台发送PTT指令后,电台会有一个响应时间(FT8CN暂定为100毫秒),所以实际声音信号的发射时间是发射延迟+100毫秒,如:设定发射延迟500毫秒,实际信号的发射是500+100=600毫秒。
|
||||
FT8的信号实际周期长度为12.64秒,建议最大延迟时间不要超过1.08秒。
|
|
@ -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.
|
|
@ -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而不是products,products是旧版本的参数,用错地址将不能成功提交 */
|
||||
//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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;//迭代次数
|
||||
}
|
|
@ -0,0 +1,501 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* Ft8Message类是用于展现FT8信号的解析结果。
|
||||
* 包括UTC时间、信噪比、时间偏移、频率、得分、消息的文本、消息的哈希值
|
||||
* ----2022.5.6-----
|
||||
* time_sec可能是时间偏移,目前还不能完全确定,待后续解决。
|
||||
* 1.为方便在列表中显示,各要素通过Get方法,返回String类型的结果。
|
||||
* -----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 如果message不为null,则创建一个与message内容一样的解码消息对象
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回解码消息的信噪比dB值,该计算方法还为搞定,暂时用000代替
|
||||
*
|
||||
* @return String 为方便显示,返回值是字符串
|
||||
*/
|
||||
public String getdB() {
|
||||
return String.valueOf(snr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息处于奇数还是偶数序列。
|
||||
*
|
||||
* @return boolean 处于偶数序列true,第0,30秒为true
|
||||
*/
|
||||
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 第一个呼号是CQ;h12被忽略
|
||||
c28 标准呼号、CQ、DE、QRZ或22位哈希
|
||||
c58 非标准呼号,最多11个字符
|
||||
f71 自由文本,最多13个字符
|
||||
g15 4字符网格、报告、RRR、RR73、73或空白
|
||||
g25 6字符网格
|
||||
h1 哈希呼号是第二个呼号
|
||||
h10 哈希呼号,10位
|
||||
h12 哈希呼号,12位
|
||||
h22 哈希呼号,22位
|
||||
k3 野外日级别(Class):A、B、…F
|
||||
n4 发射器数量:1-16、17-32
|
||||
p1 呼号后缀 /P
|
||||
r1 呼号后缀/R
|
||||
r2 RRR、RR73、73、或空白
|
||||
r3 报告:2-9,显示为529-599或52-59
|
||||
R1 R
|
||||
r5 报告:-30到+30,仅偶数
|
||||
s11 序列号(0-2047)
|
||||
s13 序列号(0-7999)或州/省
|
||||
S7 ARRL/RAC部分
|
||||
t1 TU;
|
||||
t71 遥感数据,最多18位十六进制数字
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 获取发送者的呼号,fromTo的最终解决办法要在decode.c中解决---TO DO----
|
||||
* 可获取发送者呼号的消息类型为i1,i2,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 是CQ返回true
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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.62(Beta 4)";
|
||||
public static String BUILD_DATE = BuildConfig.apkBuildTime;//编译的时间
|
||||
public static int MESSAGE_COUNT = 3000;//消息的最大缓存数量
|
||||
public static boolean saveSWLMessage=false;//保存解码消息开关
|
||||
public static boolean saveSWL_QSO=false;//保存解码消息消息中的QSO开关
|
||||
|
||||
public static 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:icom,1: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");
|
||||
}
|
||||
/**
|
||||
* 检查是否是通联结束:RRR、RR73、73
|
||||
* @param extraInfo 消息后缀
|
||||
* @return 是否
|
||||
*/
|
||||
public static boolean checkFun4_5(String extraInfo){
|
||||
return extraInfo.trim().equals("RR73")
|
||||
|| extraInfo.trim().equals("RRR")
|
||||
||extraInfo.trim().equals("73");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从String.xml中提取字符串
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把itu分区添加到已通联的ITU列表中
|
||||
*
|
||||
* @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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,660 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* FT8CN程序的主Activity。本APP采用Fragment框架实现,每个Fragment实现不同的功能。
|
||||
* ----2022.5.6-----
|
||||
* 主要完成以下功能:
|
||||
* 1.生成MainViewModel实例。MainViewModel是用于整个生存周期,用于录音、解析等功能。
|
||||
* 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,921 @@
|
|||
package com.bg7yoz.ft8cn;
|
||||
/**
|
||||
* -----2022.5.6-----
|
||||
* MainViewModel类,用于解码FT8信号以及保存与解码有关的变量数据。生存于APP的整个生命周期。
|
||||
* 1.解码的总条数。decoded_counter和mutable_Decoded_Counter。
|
||||
* 2.解码消息的列表。消息以Ft8Message展示,列表用ArrayList泛型实现。ft8Messages,mutableFt8MessageList。
|
||||
* 3.解码和录音都需要时间同步,也就是以UTC时间的每15秒为一个周期。同步事件的触发由UtcTimer类来实现。
|
||||
* 4.当前的UTC时间。timerSec,更新频率(心跳频率)由UtcTimer确定,暂定100毫秒。
|
||||
* 5.通过类方法getInstance获取当前的MainViewModel的实例,确保有唯一的实例。
|
||||
* 6.用HamAudioRecorder类实现录音,目前只实现录音成文件,然后读取文件的数据给解码模块,后面要改成直接给数组的方式----TO DO---
|
||||
* 7.解码采用JNI接口调用原生C语言。调用接口名时ft8cn,由cpp文件夹下的CMakeLists.txt维护。各函数的调用接口在decode_ft8.cpp中。
|
||||
* -----2022.5.9-----
|
||||
* 如果系统没有发射信号,触发器会在每一个周期触发录音动作,因录音开始和结束要浪费一些时间,如果不干预上一个录音的动作,将出现
|
||||
* 连续的周期内录音动作重叠,造成第二个录音动作失败。所以,第二个周期的录音开始前,要停止前一个周期的录音,造成的结果就是每一次录音
|
||||
* 的开始时间要晚于周期开始300毫秒(模拟器的结果),实际录音的长度一般在14.77秒左右
|
||||
* <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;
|
||||
|
||||
/**
|
||||
* 获取MainViewModel的实例,确保存在唯一的MainViewModel实例,该实例在APP的全部生存周期中。
|
||||
*
|
||||
* @param owner ViewModelStoreOwner 所有者,一般为Activity或Fragment。
|
||||
* @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.创建与UTC同步的时钟,时钟是UtcTimer类,内核是用Timer和TimerTask实现的。回调函数是多线程的,要考虑线程安全的问题。
|
||||
* 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数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 "<...>";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 从assets目录中的cty.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找对应的国家和地区的中文名字,对应关系在countries二维数组中,一维的0列是英文,1列是中文。
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从assets目录中的country_en2cn.dat文件中读出国家和地区的英文与中文对应翻译,以冒号分割。
|
||||
* @param context 用于调用getAssets。
|
||||
* @return 返回一个对应关系的二维数组,一维的0列是英文,1列是中文。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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(" ", "");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.bg7yoz.ft8cn.callsign;
|
||||
|
||||
/**
|
||||
* 用于查询呼号归属地的回调接口,因为数据库操作采用异步方式
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*
|
||||
*/
|
||||
public interface OnAfterQueryCallsignLocation {
|
||||
void doOnAfterQueryCallsignLocation(CallsignInfo callsignInfo);
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* 用于连接电台的基础类,蓝牙、USB线、FLEX网络、ICOM网络都是继承于此
|
||||
*
|
||||
* @author BG7YOZ
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import com.bg7yoz.ft8cn.rigs.OnConnectReceiveData;
|
||||
import com.bg7yoz.ft8cn.rigs.OnRigStateChanged;
|
||||
|
||||
|
||||
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){};
|
||||
|
||||
/**
|
||||
* 设置PTT状态,ON OFF,如果是RTS和DTR,这个是在有线方式才有的,在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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 有线连接方式的Connector,这里是指USB方式的,继承于BaseRigConnector
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,374 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* 用于USB串口操作的类。USB串口驱动在serialport目录中,主要是CDC、CH34x、CP21xx、FTDI等。
|
||||
*
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
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 true:打开,false:关闭
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 "-";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 有线连接方式的Connector,这里是指USB方式的
|
||||
* @author BGY70Z
|
||||
* @date 2023-03-20
|
||||
*/
|
||||
public class FlexConnector extends BaseRigConnector {
|
||||
public MutableLiveData<FlexMeterList> mutableMeterList=new MutableLiveData<>();
|
||||
public FlexMeterInfos flexMeterInfos =new FlexMeterInfos("");
|
||||
public FlexMeterList meterList=new FlexMeterList();
|
||||
public interface OnWaveDataReceived{
|
||||
void OnDataReceived(int bufferLen,float[] buffer);
|
||||
}
|
||||
public int maxRfPower;
|
||||
public int maxTunePower;
|
||||
|
||||
private static final String TAG = "CableConnector";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单声道的数据,24000hz采样率改为12000采样率,把立体声改为单声道
|
||||
* @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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package com.bg7yoz.ft8cn.connector;
|
||||
/**
|
||||
* ICom网络方式的连接器。
|
||||
* 注:ICom网络方式的音频数据包是Int类型,需要转换成Float类型
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
Ładowanie…
Reference in New Issue