From d2b7b833b64a1af8f66a2a67bc4fe49cc507518b Mon Sep 17 00:00:00 2001 From: flower <737666956@qq.com> Date: Mon, 2 Feb 2026 17:06:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=8D=E7=85=A7version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PROTOCOL.md | 2 +- .../java/com/skylink/app/network/PacketDef.kt | 2 + .../com/skylink/app/network/UdpReceiver.kt | 8 +- .../java/com/skylink/app/ui/HomeFragment.kt | 272 +- .../java/com/skylink/app/utils/BitmapUtils.kt | 2 +- .../res/layout/dialog_trigger_capture.xml | 66 + .../app/src/main/res/layout/fragment_home.xml | 36 + .../app/src/main/res/values/arrays.xml | 7 + .../index-2026-01-21T03-24-06-0278.json | 0 .../index-2026-02-02T08-41-05-0382.json} | 0 .../.ninja_deps | Bin 58892 -> 77764 bytes .../.ninja_log | 59 +- .../index/DataLogger.h.0B9D391C2B214BD0.idx | Bin 1794 -> 1794 bytes .../GroundReceiver.cpp.19539F226766321F.idx | Bin 16706 -> 16840 bytes .../GroundReceiver.h.883FC9D800C84637.idx | Bin 8946 -> 7102 bytes .../index/MainWindow.cpp.FDAE15B7B4192D00.idx | Bin 12650 -> 13710 bytes .../index/MainWindow.h.6361A896F7004072.idx | Bin 3744 -> 3948 bytes .../clangd/index/QAction.B114352EB51C7F13.idx | Bin 220 -> 220 bytes .../clangd/index/QFile.5F11D7EC55C99159.idx | Bin 218 -> 218 bytes .../index/QFileDialog.AE937E9BD03E1F6D.idx | Bin 236 -> 236 bytes .../index/QGroupBox.3E763DE9ABBA76C2.idx | Bin 232 -> 232 bytes .../index/QHBoxLayout.3456B889A1B6D1B2.idx | Bin 234 -> 234 bytes .../clangd/index/QLabel.A231B008CE1E984D.idx | Bin 226 -> 226 bytes .../index/QMainWindow.C30B4017231D6F3A.idx | Bin 236 -> 236 bytes .../clangd/index/QMenu.4B883ACF03F1ABCD.idx | Bin 224 -> 224 bytes .../index/QMenuBar.A658430955BD0771.idx | Bin 230 -> 230 bytes .../index/QMessageBox.71A747378B8A95DE.idx | Bin 236 -> 236 bytes .../index/QPainter.FC79C55FCC5C6495.idx | Bin 0 -> 222 bytes .../clangd/index/QPixmap.26CC87E3DC9DAD7A.idx | Bin 220 -> 220 bytes .../index/QPushButton.7AAEC88FD1937FC1.idx | Bin 236 -> 236 bytes .../index/QStatusBar.A0FC6A0535AE1B64.idx | Bin 234 -> 234 bytes .../index/QTcpSocket.872D6E55E3EDDE91.idx | Bin 234 -> 234 bytes .../index/QTextStream.64A9B2B0995F64B2.idx | Bin 230 -> 230 bytes .../clangd/index/QTimer.93AC5F67F105775D.idx | Bin 220 -> 220 bytes .../index/QVBoxLayout.442C1E2DFD7E8599.idx | Bin 234 -> 234 bytes .../clangd/index/codecvt.EDDA3AB8747A636F.idx | Bin 5052 -> 5052 bytes .../index/filesystem.B17CF9446BFA0F4F.idx | Bin 398 -> 398 bytes .../index/fs_dir.h.EF81D2B92BCB6003.idx | Bin 16074 -> 16074 bytes .../index/fs_fwd.h.9D7F2BCAC49CA6E2.idx | Bin 14488 -> 14488 bytes .../index/fs_ops.h.E5AB999DF3F3D770.idx | Bin 13194 -> 13194 bytes .../index/fs_path.h.141D93665336AE94.idx | Bin 26474 -> 26474 bytes .../qabstractbutton.h.398AB316525E7E19.idx | Bin 5618 -> 5618 bytes .../index/qaction.h.CD4BBF741EA2A7F7.idx | Bin 10388 -> 10388 bytes .../index/qbasictimer.h.A1A62F3884A64EDD.idx | Bin 2680 -> 2680 bytes .../index/qbitmap.h.086B378D282BD23F.idx | Bin 2714 -> 2714 bytes .../index/qboxlayout.h.DB3C2F57796949EF.idx | Bin 6448 -> 6448 bytes .../index/qbrush.h.D5D48BDF2EBD4B43.idx | Bin 31394 -> 31394 bytes .../index/qcursor.h.CDED07B97E4E0B0F.idx | Bin 4508 -> 4508 bytes .../index/qdialog.h.3706A4198E9C1EEC.idx | Bin 3978 -> 3978 bytes .../clangd/index/qdir.h.0696C1A62AFA8A91.idx | Bin 14296 -> 14296 bytes .../clangd/index/qfile.h.4ED6A10B662DFF8F.idx | Bin 8620 -> 8620 bytes .../index/qfiledevice.h.781E2DAC2C6E8BDB.idx | Bin 8594 -> 8594 bytes .../index/qfiledialog.h.E12C29E3DC35974D.idx | Bin 14766 -> 14766 bytes .../index/qfileinfo.h.245DAFA951EEFA2F.idx | Bin 9638 -> 9638 bytes .../clangd/index/qfont.h.CD21047C617858E0.idx | Bin 18184 -> 18184 bytes .../index/qfontinfo.h.B45E27F59B55D146.idx | Bin 2778 -> 2778 bytes .../index/qfontmetrics.h.73485B0EDFA10753.idx | Bin 8592 -> 8548 bytes .../index/qframe.h.757C1E20DF42F71A.idx | Bin 5118 -> 5118 bytes .../index/qgridlayout.h.CE504AAD148571E1.idx | Bin 5112 -> 5112 bytes .../index/qgroupbox.h.8367577241249DB2.idx | Bin 3658 -> 3658 bytes .../clangd/index/qicon.h.D82ED6126A6201B7.idx | Bin 7804 -> 7804 bytes .../index/qkeysequence.h.5B5ADA791B686DFE.idx | Bin 11828 -> 11828 bytes .../index/qlabel.h.8AB0716902333ABD.idx | Bin 6608 -> 6608 bytes .../index/qlayout.h.6F80003CDDEF4AEA.idx | Bin 7770 -> 7770 bytes .../index/qlayoutitem.h.C86D9B98778C7236.idx | Bin 7936 -> 7936 bytes .../index/qmainwindow.h.8ED093DCD11CF43A.idx | Bin 9132 -> 9132 bytes .../clangd/index/qmenu.h.BE6E96D0832BC654.idx | Bin 9868 -> 9868 bytes .../index/qmenubar.h.18B917E3540116E3.idx | Bin 5806 -> 5806 bytes .../index/qmessagebox.h.241415CCAD80C0E0.idx | Bin 14788 -> 14788 bytes .../index/qpainter.h.EEC166BD080AAAAE.idx | Bin 0 -> 37184 bytes .../index/qpalette.h.4D079E041BD953AB.idx | Bin 11426 -> 11426 bytes .../clangd/index/qpen.h.A02EFA6EAE11D4B0.idx | Bin 0 -> 5240 bytes .../index/qpicture.h.064608D4D4D53E93.idx | Bin 4134 -> 4134 bytes .../index/qpixmap.h.7DE1B74900C7A428.idx | Bin 9558 -> 9558 bytes .../index/qpushbutton.h.2188113680DF6FF9.idx | Bin 3602 -> 3602 bytes .../index/qsizepolicy.h.2C5E28082B22C9B0.idx | Bin 10134 -> 10134 bytes .../index/qstatusbar.h.49A7AC27109A0B33.idx | Bin 2888 -> 2888 bytes .../index/qtabwidget.h.7188E5B57D38F1AE.idx | Bin 8050 -> 8050 bytes .../index/qtcpsocket.h.9774E323EE0EB932.idx | Bin 2264 -> 2264 bytes .../qtextdocument.h.603EF1B443EEC206.idx | Bin 16558 -> 16558 bytes .../index/qtextoption.h.9EDBB2ABEBA526EB.idx | Bin 0 -> 7298 bytes .../index/qtimer.h.8892BE22B82DDA0E.idx | Bin 6850 -> 6850 bytes .../qtwidgets-config.h.A4A2EA56C8FA2E21.idx | Bin 1830 -> 1830 bytes .../qtwidgetsexports.h.E451928770724099.idx | Bin 374 -> 374 bytes .../qtwidgetsglobal.h.3847DA1C62C3DE65.idx | Bin 440 -> 440 bytes .../clangd/index/qurl.h.BD896A9A39879A26.idx | Bin 26974 -> 26974 bytes .../index/qwidget.h.6DA7790AA97F0231.idx | Bin 44340 -> 44340 bytes .../mocs_compilation.cpp.o | Bin 1337960 -> 1340904 bytes .../src/GroundReceiver.cpp.o | Bin 1926264 -> 1926712 bytes .../skylink_station.dir/src/MainWindow.cpp.o | Bin 1618024 -> 2210312 bytes .../skylink_station.dir/src/main.cpp.o | Bin 1039840 -> 1039840 bytes .../ParseCache.txt | 128 +- .../Testing/Temporary/LastTest.log | 4 +- .../skylink_station | Bin 3051696 -> 3344624 bytes .../UVLADIE3JM/moc_GroundReceiver.cpp | 69 +- .../UVLADIE3JM/moc_MainWindow.cpp | 94 +- skylink_qt_station/src/GroundReceiver.cpp | 9 +- skylink_qt_station/src/GroundReceiver.h | 4 +- skylink_qt_station/src/MainWindow.cpp | 227 +- skylink_qt_station/src/MainWindow.h | 30 +- skylink_ros2_ws/build.sh | 8 +- skylink_ros2_ws/sent_log.txt | 72 + .../src/skylink_bridge/CMakeLists.txt | 32 +- .../src/skylink_bridge/config/params.yaml | 49 +- .../skylink_bridge/camera_driver_node.h | 260 + .../include/skylink_bridge/httplib.h | 16993 ++++++++++++++++ .../include/skylink_bridge/udp_sender_node.h | 171 +- .../src/skylink_bridge/package.xml | 5 + .../skylink_bridge/src/camera_driver_node.cpp | 1338 +- .../skylink_bridge/src/udp_sender_node.cpp | 410 +- .../src/skylink_bridge/srv/TriggerCapture.srv | 11 + 111 files changed, 19371 insertions(+), 997 deletions(-) create mode 100644 skylink_android_app/app/src/main/res/layout/dialog_trigger_capture.xml create mode 100644 skylink_android_app/app/src/main/res/values/arrays.xml rename skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.cmake/api/v1/{reply => reply.prev}/index-2026-01-21T03-24-06-0278.json (100%) rename skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.cmake/api/v1/{reply.prev/index-2026-01-21T03-23-38-0501.json => reply/index-2026-02-02T08-41-05-0382.json} (100%) create mode 100644 skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.qtc_clangd/.cache/clangd/index/QPainter.FC79C55FCC5C6495.idx create mode 100644 skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.qtc_clangd/.cache/clangd/index/qpainter.h.EEC166BD080AAAAE.idx create mode 100644 skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.qtc_clangd/.cache/clangd/index/qpen.h.A02EFA6EAE11D4B0.idx create mode 100644 skylink_qt_station/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/.qtc_clangd/.cache/clangd/index/qtextoption.h.9EDBB2ABEBA526EB.idx create mode 100644 skylink_ros2_ws/sent_log.txt create mode 100644 skylink_ros2_ws/src/skylink_bridge/include/skylink_bridge/camera_driver_node.h create mode 100644 skylink_ros2_ws/src/skylink_bridge/include/skylink_bridge/httplib.h create mode 100644 skylink_ros2_ws/src/skylink_bridge/srv/TriggerCapture.srv diff --git a/PROTOCOL.md b/PROTOCOL.md index 400b90d..bcff5eb 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -98,7 +98,7 @@ SkyLink 是一个多平台实时视频传输系统的通信协议,设计用于 ↓ 3. 检查魔数是否为 0xAA55 ✗ 否 → 丢弃该包 - ✓ 是 → 继续 + 是 → 继续 ↓ 4. 根据 frame_id 找到对应的帧缓冲区 如果不存在 → 创建新的缓冲区 diff --git a/skylink_android_app/app/src/main/java/com/skylink/app/network/PacketDef.kt b/skylink_android_app/app/src/main/java/com/skylink/app/network/PacketDef.kt index 34a032c..85f0f10 100644 --- a/skylink_android_app/app/src/main/java/com/skylink/app/network/PacketDef.kt +++ b/skylink_android_app/app/src/main/java/com/skylink/app/network/PacketDef.kt @@ -165,6 +165,7 @@ data class DecodedFrame( val lat: Double, val lon: Double, val alt: Double, + val photoNumber: Int = 0, // 照片编号(从reserve字段提取) val receivedTime: Long = System.currentTimeMillis() ) { override fun equals(other: Any?): Boolean { @@ -207,6 +208,7 @@ data class FrameBuffer( val lat: Double = 0.0, val lon: Double = 0.0, val alt: Double = 0.0, + val photoNumber: Int = 0, // 照片编号(从reserve字段提取) val receiveTimestamp: Long = System.currentTimeMillis() ) { /** diff --git a/skylink_android_app/app/src/main/java/com/skylink/app/network/UdpReceiver.kt b/skylink_android_app/app/src/main/java/com/skylink/app/network/UdpReceiver.kt index 2793215..c1e3b39 100644 --- a/skylink_android_app/app/src/main/java/com/skylink/app/network/UdpReceiver.kt +++ b/skylink_android_app/app/src/main/java/com/skylink/app/network/UdpReceiver.kt @@ -89,7 +89,7 @@ class UdpReceiver( Log.i(TAG, "UDP 接收缓冲区大小已设置为: ${socket!!.receiveBufferSize}") } - Log.i(TAG, "✓ UDP 接收器启动,监听端口 $listenPort") + Log.i(TAG, " UDP 接收器启动,监听端口 $listenPort") startReceiving() } catch (e: Exception) { @@ -185,7 +185,8 @@ class UdpReceiver( timestamp = header.timestamp, lat = header.lat, lon = header.lon, - alt = header.alt + alt = header.alt, + photoNumber = header.reserve // 从reserve字段提取照片编号 ) } @@ -228,7 +229,8 @@ class UdpReceiver( timestamp = frame.timestamp, lat = frame.lat, lon = frame.lon, - alt = frame.alt + alt = frame.alt, + photoNumber = frame.photoNumber // 传递照片编号 ) totalFrames++ diff --git a/skylink_android_app/app/src/main/java/com/skylink/app/ui/HomeFragment.kt b/skylink_android_app/app/src/main/java/com/skylink/app/ui/HomeFragment.kt index 5fccd17..f9d1c5e 100644 --- a/skylink_android_app/app/src/main/java/com/skylink/app/ui/HomeFragment.kt +++ b/skylink_android_app/app/src/main/java/com/skylink/app/ui/HomeFragment.kt @@ -11,6 +11,9 @@ package com.skylink.app.ui import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -19,6 +22,9 @@ import android.widget.Button import android.widget.ImageView import android.widget.TextView import android.widget.Toast +import android.widget.EditText +import android.widget.ArrayAdapter +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.skylink.app.R @@ -31,6 +37,10 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.* +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject class HomeFragment : Fragment(), UdpReceiverListener { @@ -48,6 +58,16 @@ class HomeFragment : Fragment(), UdpReceiverListener { private var statusTextView: TextView? = null private var saveButton: Button? = null private var recordButton: Button? = null + private var triggerCaptureButton: Button? = null + private var triggerCaptureOriginalButton: Button? = null + private var triggerCaptureThumbnailButton: Button? = null + + // HTTP客户端(用于调用ROS2服务) + private val httpClient = OkHttpClient() + + // ROS2节点配置 + private var ros2NodeIp = "192.168.1.100" // 默认IP,需要配置 + private var ros2HttpPort = 8080 // ======================================================================== // 业务逻辑 @@ -56,6 +76,7 @@ class HomeFragment : Fragment(), UdpReceiverListener { private var udpReceiver: UdpReceiver? = null private var currentFrame: DecodedFrame? = null private var isRecording = false + private var totalPhotosReceived = 0 // 接收到的总照片数 // ======================================================================== // 生命周期方法 @@ -92,6 +113,9 @@ class HomeFragment : Fragment(), UdpReceiverListener { statusTextView = view.findViewById(R.id.status_text) saveButton = view.findViewById(R.id.save_button) recordButton = view.findViewById(R.id.record_button) + triggerCaptureButton = view.findViewById(R.id.trigger_capture_button) + triggerCaptureOriginalButton = view.findViewById(R.id.trigger_capture_original_button) + triggerCaptureThumbnailButton = view.findViewById(R.id.trigger_capture_thumbnail_button) // 设置按钮点击监听 saveButton?.setOnClickListener { @@ -101,6 +125,18 @@ class HomeFragment : Fragment(), UdpReceiverListener { recordButton?.setOnClickListener { onRecordToggled() } + + triggerCaptureButton?.setOnClickListener { + showTriggerCaptureDialog() + } + + triggerCaptureOriginalButton?.setOnClickListener { + triggerCapture(0) // 原图模式 + } + + triggerCaptureThumbnailButton?.setOnClickListener { + triggerCapture(1) // 缩略图模式 + } } // ======================================================================== @@ -126,6 +162,11 @@ class HomeFragment : Fragment(), UdpReceiverListener { override fun onFrameDecoded(frame: DecodedFrame) { currentFrame = frame + + // 更新照片计数 + if (frame.photoNumber > 0) { + totalPhotosReceived++ + } // 移到后台线程执行解码,避免阻塞 UI 线程,进而影响 UDP 接收协程(如果是单核或高负载) lifecycleScope.launch(Dispatchers.Default) { @@ -133,7 +174,13 @@ class HomeFragment : Fragment(), UdpReceiverListener { if (bitmap != null) { withContext(Dispatchers.Main) { view?.let { - videoImageView?.setImageBitmap(bitmap) + // 在图像上叠加显示照片编号 + val bitmapWithText = if (frame.photoNumber > 0) { + addPhotoNumberToBitmap(bitmap, frame.photoNumber) + } else { + bitmap + } + videoImageView?.setImageBitmap(bitmapWithText) updateGpsInfo(frame) } } @@ -142,10 +189,12 @@ class HomeFragment : Fragment(), UdpReceiverListener { } override fun onStatsUpdated(fps: Int, kbps: Int, packetLoss: Double) { - statsTextView?.text = String.format( - "FPS: %d\nSpeed: %d KB/s\nPacket Loss: %.1f%%", - fps, kbps, packetLoss - ) + val statsText = StringBuilder() + statsText.append(String.format("FPS: %d\nSpeed: %d KB/s\nPacket Loss: %.1f%%", fps, kbps, packetLoss)) + if (totalPhotosReceived > 0) { + statsText.append(String.format("\nTotal Photos: %d", totalPhotosReceived)) + } + statsTextView?.text = statsText.toString() } override fun onError(message: String, exception: Exception?) { @@ -201,4 +250,217 @@ class HomeFragment : Fragment(), UdpReceiverListener { val formatter = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) return formatter.format(Date(millis)) } + + /** + * 在图像上叠加显示照片编号 + */ + private fun addPhotoNumberToBitmap(bitmap: Bitmap, photoNumber: Int): Bitmap { + val mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) + val canvas = Canvas(mutableBitmap) + + val text = "Photo #$photoNumber" + val paint = Paint().apply { + color = Color.WHITE + textSize = 48f + isAntiAlias = true + style = Paint.Style.FILL + } + + val textPaint = Paint(paint) + val textBounds = android.graphics.Rect() + textPaint.getTextBounds(text, 0, text.length, textBounds) + + // 绘制半透明背景 + val bgPaint = Paint().apply { + color = Color.argb(180, 0, 0, 0) + style = Paint.Style.FILL + } + val padding = 10 + canvas.drawRect( + 10f, 10f, + (textBounds.width() + padding * 2).toFloat(), + (textBounds.height() + padding * 2).toFloat(), + bgPaint + ) + + // 绘制文字 + canvas.drawText(text, (10 + padding).toFloat(), (10 + padding + textBounds.height()).toFloat(), textPaint) + + return mutableBitmap + } + + /** + * 显示触发拍照参数输入对话框 + */ + private fun showTriggerCaptureDialog() { + val view = layoutInflater.inflate(R.layout.dialog_trigger_capture, null) + val durationEdit = view.findViewById(R.id.duration_edit) + val countEdit = view.findViewById(R.id.count_edit) + val intervalEdit = view.findViewById(R.id.interval_edit) + val modeSpinner = view.findViewById(R.id.mode_spinner) + + // 设置Spinner适配器 + val modes = arrayOf("原图", "缩略图") + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, modes) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + modeSpinner.adapter = adapter + + // 设置默认值 + durationEdit.setText("5.0") + countEdit.setText("10") + intervalEdit.setText("0.5") + + AlertDialog.Builder(requireContext()) + .setTitle("触发拍照") + .setView(view) + .setPositiveButton("确定") { _, _ -> + val duration = durationEdit.text.toString().toDoubleOrNull() ?: 5.0 + val count = countEdit.text.toString().toIntOrNull() ?: 10 + val interval = intervalEdit.text.toString().toDoubleOrNull() ?: 0.5 + val mode = modeSpinner.selectedItemPosition // 0=原图, 1=缩略图 + + triggerCaptureWithParams(duration, count, interval, mode) + } + .setNegativeButton("取消", null) + .setNeutralButton("设置") { _, _ -> + // 显示IP和端口配置对话框 + showConfigDialog() + } + .show() + } + + /** + * 显示配置对话框(IP和端口) + */ + private fun showConfigDialog() { + val view = layoutInflater.inflate(android.R.layout.simple_list_item_2, null) + val ipEdit = EditText(requireContext()) + ipEdit.hint = "ROS2节点IP地址" + ipEdit.setText(ros2NodeIp) + + val portEdit = EditText(requireContext()) + portEdit.hint = "HTTP端口" + portEdit.inputType = android.text.InputType.TYPE_CLASS_NUMBER + portEdit.setText(ros2HttpPort.toString()) + + val layout = android.widget.LinearLayout(requireContext()).apply { + orientation = android.widget.LinearLayout.VERTICAL + setPadding(50, 40, 50, 10) + addView(ipEdit) + addView(portEdit) + } + + AlertDialog.Builder(requireContext()) + .setTitle("ROS2节点配置") + .setView(layout) + .setPositiveButton("确定") { _, _ -> + ros2NodeIp = ipEdit.text.toString().ifEmpty { "192.168.1.100" } + ros2HttpPort = portEdit.text.toString().toIntOrNull() ?: 8080 + Toast.makeText(context, "配置已保存: $ros2NodeIp:$ros2HttpPort", Toast.LENGTH_SHORT).show() + } + .setNegativeButton("取消", null) + .show() + } + + /** + * 快速触发拍照(使用默认参数) + */ + private fun triggerCapture(mode: Int) { + triggerCaptureWithParams(5.0, 10, 0.5, mode) + } + + /** + * 使用参数触发拍照 + */ + private fun triggerCaptureWithParams(duration: Double, count: Int, interval: Double, mode: Int) { + val modeStr = if (mode == 0) "原图" else "缩略图" + + lifecycleScope.launch(Dispatchers.IO) { + try { + // 构建JSON请求 + val json = JSONObject().apply { + put("duration", duration) + put("count", count) + put("interval", interval) + put("transmission_mode", mode) + } + + val requestBody = json.toString().toRequestBody("application/json".toMediaType()) + val url = "http://$ros2NodeIp:$ros2HttpPort/api/v1/trigger_capture" + + val request = Request.Builder() + .url(url) + .post(requestBody) + .addHeader("User-Agent", "SkyLink-Android/1.0") + .build() + + val response = httpClient.newCall(request).execute() + + withContext(Dispatchers.Main) { + if (response.isSuccessful) { + val responseBody = response.body?.string() + if (responseBody != null) { + try { + val responseJson = JSONObject(responseBody) + val success = responseJson.optBoolean("success", false) + val message = responseJson.optString("message", "") + val totalPhotos = responseJson.optInt("total_photos", 0) + + if (success) { + Toast.makeText(context, + "拍照任务已启动!\n$message\n总照片数: $totalPhotos", + Toast.LENGTH_LONG).show() + updateStatus("拍照任务已启动 ($modeStr)") + } else { + Toast.makeText(context, + "请求失败: $message", + Toast.LENGTH_LONG).show() + updateStatus("请求失败: $message") + } + } catch (e: Exception) { + Toast.makeText(context, + "响应解析失败: ${e.message}", + Toast.LENGTH_SHORT).show() + updateStatus("响应解析失败") + } + } else { + Toast.makeText(context, "服务器返回空响应", Toast.LENGTH_SHORT).show() + updateStatus("服务器返回空响应") + } + } else { + val errorMsg = when (response.code) { + 400 -> "请求参数错误" + 404 -> "API端点不存在" + 500 -> "服务器内部错误" + else -> "HTTP错误: ${response.code}" + } + Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show() + updateStatus(errorMsg) + } + } + + } catch (e: java.net.SocketTimeoutException) { + withContext(Dispatchers.Main) { + Toast.makeText(context, + "请求超时,请检查网络连接和ROS2节点IP地址 ($ros2NodeIp:$ros2HttpPort)", + Toast.LENGTH_LONG).show() + updateStatus("请求超时") + } + } catch (e: java.net.ConnectException) { + withContext(Dispatchers.Main) { + Toast.makeText(context, + "无法连接到ROS2节点 ($ros2NodeIp:$ros2HttpPort)\n请确认HTTP服务器正在运行", + Toast.LENGTH_LONG).show() + updateStatus("连接失败") + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, + "调用失败: ${e.message}", + Toast.LENGTH_LONG).show() + updateStatus("调用失败: ${e.message}") + } + } + } + } } diff --git a/skylink_android_app/app/src/main/java/com/skylink/app/utils/BitmapUtils.kt b/skylink_android_app/app/src/main/java/com/skylink/app/utils/BitmapUtils.kt index 9afda34..3ca96c5 100644 --- a/skylink_android_app/app/src/main/java/com/skylink/app/utils/BitmapUtils.kt +++ b/skylink_android_app/app/src/main/java/com/skylink/app/utils/BitmapUtils.kt @@ -44,7 +44,7 @@ object BitmapUtils { // 实际解码 val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size, options) - // Log.i(TAG, "✓ Bitmap 解码成功: ${bitmap?.width}x${bitmap?.height}") // 移除高频日志 + // Log.i(TAG, " Bitmap 解码成功: ${bitmap?.width}x${bitmap?.height}") // 移除高频日志 bitmap } catch (e: Exception) { Log.e(TAG, "Bitmap 解码失败", e) diff --git a/skylink_android_app/app/src/main/res/layout/dialog_trigger_capture.xml b/skylink_android_app/app/src/main/res/layout/dialog_trigger_capture.xml new file mode 100644 index 0000000..ea72cc8 --- /dev/null +++ b/skylink_android_app/app/src/main/res/layout/dialog_trigger_capture.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/skylink_android_app/app/src/main/res/layout/fragment_home.xml b/skylink_android_app/app/src/main/res/layout/fragment_home.xml index 284b3c5..dfae238 100644 --- a/skylink_android_app/app/src/main/res/layout/fragment_home.xml +++ b/skylink_android_app/app/src/main/res/layout/fragment_home.xml @@ -68,8 +68,44 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" android:text="⏺ 开始录制" /> +