package com.casic.endoscope.view import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt import android.content.Intent import android.graphics.Color import android.graphics.PixelFormat import android.os.Bundle import android.os.Handler import android.os.Message import android.util.Log import android.view.KeyEvent import android.view.MotionEvent import android.view.SurfaceHolder import android.view.View import androidx.lifecycle.lifecycleScope import com.casic.endoscope.adapter.CameraPointAdapter import com.casic.endoscope.bean.CameraPointBean import com.casic.endoscope.databinding.ActivityMainBinding import com.casic.endoscope.extensions.convertValue import com.casic.endoscope.extensions.createHorizontalCommand import com.casic.endoscope.extensions.createImageFileDir import com.casic.endoscope.extensions.createVerticalCommand import com.casic.endoscope.extensions.createVideoFileDir import com.casic.endoscope.extensions.getChannel import com.casic.endoscope.extensions.init import com.casic.endoscope.extensions.toTime import com.casic.endoscope.service.VideoTranscodeService import com.casic.endoscope.utils.DataBaseManager import com.casic.endoscope.utils.ProjectConstant import com.casic.endoscope.utils.ble.BleDeviceManager import com.casic.endoscope.utils.ble.OnDeviceConnectListener import com.casic.endoscope.utils.ble.OnDeviceDiscoveredListener import com.casic.endoscope.utils.hk.MessageCodeHub import com.casic.endoscope.utils.hk.SDKGuider import com.casic.endoscope.widgets.AddCameraPointDialog import com.casic.endoscope.widgets.BluetoothDeviceDialog import com.casic.endoscope.widgets.LineChartMarkerView import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import com.gyf.immersionbar.ImmersionBar import com.hikvision.netsdk.HCNetSDK import com.hikvision.netsdk.NET_DVR_JPEGPARA import com.hikvision.netsdk.NET_DVR_PREVIEWINFO import com.hikvision.netsdk.NET_DVR_SERIALSTART_V40 import com.hikvision.netsdk.PTZCommand import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.getStatusBarHeight import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.LoadingDialogHub import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.widget.SteeringWheelView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import java.util.Timer import java.util.TimerTask @SuppressLint("all") class MainActivity : KotlinBaseActivity<ActivityMainBinding>(), SurfaceHolder.Callback, Handler.Callback { private val kTag = "MainActivity" private val context = this@MainActivity private val hkSDK by lazy { HCNetSDK.getInstance() } private val bleDeviceManager by lazy { BleDeviceManager(this) } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val selectedSpeed = 7 private val messageCode = 2024021901 private val bluetoothDevices = ArrayList<BluetoothDevice>() private val xAxisLabels = ArrayList<String>() private val densityEntries = ArrayList<Entry>() private val lineDataSets = ArrayList<ILineDataSet>() //趋势线起点X坐标 private var i = 0 private var isConnected = false private var clickTime = 0L private var previewHandle = -1 private var selectChannel = -1 private var returnUserId = -1 private var aChannelNum = 0 private var startAChannel = 0 private var dChannelNum = 0 private var startDChannel = 0 private var isPreviewSuccess = false //手指是否已经从方向控制盘抬起 private var isActionUp = true //焦距按钮是否已松开 private var isScaleButtonUp = true //是否拍照成功 private var isCaptureSuccess = true private var timer: Timer? = null private var timerTask: TimerTask? = null private var seconds = 0L private var dataBeans: MutableList<CameraPointBean> = ArrayList() private var selectedItems: MutableList<CameraPointBean> = ArrayList() private var inputVideoPath = "" private lateinit var weakReferenceHandler: WeakReferenceHandler private lateinit var dataAdapter: CameraPointAdapter private lateinit var serviceIntent: Intent private lateinit var dataSet: LineDataSet private lateinit var lineData: LineData override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) } override fun initOnCreate(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) dataBeans = DataBaseManager.get.loadAllCameraPoint() //显示数据 weakReferenceHandler.sendEmptyMessage(messageCode) serviceIntent = Intent(this, VideoTranscodeService::class.java) //初始化浓度趋势折线图和Marker binding.lineChart.init(this) val markerView = LineChartMarkerView(this) markerView.chartView = binding.lineChart markerView.setXAxisDate(xAxisLabels) binding.lineChart.marker = markerView } override fun onResume() { super.onResume() //启动视频转码服务 startService(serviceIntent) } override fun handleMessage(msg: Message): Boolean { if (msg.what == messageCode) { //绑定数据 dataAdapter = CameraPointAdapter(this, dataBeans) binding.recyclerView.adapter = dataAdapter dataAdapter.setOnItemCheckedListener(object : CameraPointAdapter.OnItemCheckedListener { override fun onItemChecked(position: Int, items: ArrayList<CameraPointBean>) { selectedItems = items } }) } return true } override fun initEvent() { binding.openAlbumButton.setOnClickListener { if (isPreviewSuccess) { "设备预览画面中,无法切换".show(this) return@setOnClickListener } navigatePageTo<AlbumActivity>() } binding.searchBleButton.setOnClickListener { if (!bleDeviceManager.isBluetoothEnabled()) { bleDeviceManager.openBluetooth() } if (bleDeviceManager.isDiscovering()) { bleDeviceManager.stopDiscoverDevice() } if (isConnected) { bleDeviceManager.disConnectDevice() } else { LoadingDialogHub.show(this, "设备搜索中...") bleDeviceManager.startScanDevice(object : OnDeviceDiscoveredListener { override fun onDeviceFound(device: BluetoothDevice) { if (bluetoothDevices.isEmpty()) { bluetoothDevices.add(device) } else { //0表示未添加到list的新设备,1表示已经扫描并添加到list的设备 var judge = 0 for (temp in bluetoothDevices) { if (temp.address == device.address) { judge = 1 break } } if (judge == 0) { bluetoothDevices.add(device) } } } override fun onDeviceDiscoveryEnd() { LoadingDialogHub.dismiss() //显示搜索到设备列表 showScanResult() } }, 10 * 1000) } } //单张拍照 binding.imageButton.setOnClickListener { if (isPreviewSuccess) { binding.imageButton.isEnabled = false val strJpeg = NET_DVR_JPEGPARA() strJpeg.wPicQuality = 1 strJpeg.wPicSize = 2 val imagePath = "/${createImageFileDir()}/${timeFormat.format(Date())}.png" hkSDK.NET_DVR_CaptureJPEGPicture( returnUserId, selectChannel, strJpeg, imagePath ) if (MessageCodeHub.getErrorCode() == 0) { "画面抓取成功".show(this) binding.imageButton.isEnabled = true } } else { "摄像头预览未打开,无法拍照".show(this) } } //视频录制 binding.videoButton.setOnClickListener { if (isPreviewSuccess) { inputVideoPath = "/${createVideoFileDir()}/${timeFormat.format(Date())}.mp4" hkSDK.NET_DVR_SaveRealData(returnUserId, inputVideoPath) binding.videoStateView.visibility = View.VISIBLE } else { "摄像头预览未打开,无法录制视频".show(this) } } binding.videoButton.setOnLongClickListener { //停止视频抓取 hkSDK.NET_DVR_StopSaveRealData(returnUserId) binding.videoStateView.visibility = View.INVISIBLE ProjectConstant.VIDEO_PATH_STACK.push(inputVideoPath) "视频录制成功".show(this) true } //连续拍照 binding.multipleImageButton.setOnClickListener { if (dataBeans.isEmpty()) { "请先设置连续拍照步骤".show(this) return@setOnClickListener } if (isPreviewSuccess) { lifecycleScope.launch(Dispatchers.IO) { dataBeans.forEach { //再拍照 if (isCaptureSuccess) { isCaptureSuccess = false //先执行角度 executeAngle(it.hAngle, it.vAngle) withContext(Dispatchers.Main) { "抓取步骤${it.step}画面".show(context) } val strJpeg = NET_DVR_JPEGPARA() strJpeg.wPicQuality = 1 strJpeg.wPicSize = 2 val imagePath = "/${createImageFileDir()}/${timeFormat.format(Date())}.png" hkSDK.NET_DVR_CaptureJPEGPicture( returnUserId, selectChannel, strJpeg, imagePath ) isCaptureSuccess = MessageCodeHub.getErrorCode() == 0 } } withContext(Dispatchers.Main) { "连续抓取画面完成".show(context) } } } else { "摄像头预览未打开,无法拍照".show(this) } } binding.addButton.setOnClickListener { val step = if (dataBeans.isEmpty()) { 0 } else { dataBeans.last().step } AddCameraPointDialog.Builder().setContext(this).setLastStep(step) .setNegativeButton("取消").setPositiveButton("添加") .setOnDialogButtonClickListener(object : AddCameraPointDialog.OnDialogButtonClickListener { override fun onConfirmClick(step: Int, hAngle: Int, vAngle: Int) { DataBaseManager.get.cacheCameraPoint(step, hAngle, vAngle) dataBeans = DataBaseManager.get.loadAllCameraPoint() dataAdapter.setRefreshData(dataBeans) } override fun onCancelClick() { } }).build().show() } binding.deleteButton.setOnClickListener { if (selectedItems.isEmpty()) { "请先选择要删除的步骤".show(this) return@setOnClickListener } DataBaseManager.get.deleteCameraPoints(selectedItems) dataBeans = DataBaseManager.get.loadAllCameraPoint() dataAdapter.setRefreshData(dataBeans) selectedItems.clear() } binding.steeringWheelView.setOnWheelTouchListener(object : SteeringWheelView.OnWheelTouchListener { override fun onCenterClicked() { if (!isPreviewSuccess) { val deviceItem = SDKGuider.sdkGuider.devManageGuider.DeviceItem() deviceItem.szDevName = "" deviceItem.devNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( ProjectConstant.HK_NET_IP, ProjectConstant.HK_NET_PORT, ProjectConstant.HK_NET_USERNAME, ProjectConstant.HK_NET_PASSWORD ) if (deviceItem.szDevName.isEmpty()) { deviceItem.szDevName = deviceItem.devNetInfo.szIp } val loginV40Jna = SDKGuider.sdkGuider.devManageGuider.login_v40_jna( deviceItem.szDevName, deviceItem.devNetInfo ) if (loginV40Jna) { //配置设备通道 try { val deviceInfo = SDKGuider.sdkGuider.devManageGuider.devList[0] returnUserId = deviceInfo.szUserId aChannelNum = deviceInfo.deviceInfoV40_jna.struDeviceV30.byChanNum.toInt() startAChannel = deviceInfo.deviceInfoV40_jna.struDeviceV30.byStartChan.toInt() dChannelNum = deviceInfo.deviceInfoV40_jna.struDeviceV30.byIPChanNum + deviceInfo.deviceInfoV40_jna.struDeviceV30.byHighDChanNum * 256 startDChannel = deviceInfo.deviceInfoV40_jna.struDeviceV30.byStartChan.toInt() var iAnalogStartChan = startAChannel var iDigitalStartChan = startDChannel val channelList = ArrayList<String>() for (i in 0 until aChannelNum) { channelList.add("ACamera_$iAnalogStartChan") iAnalogStartChan++ } for (i in 0 until dChannelNum) { channelList.add("DCamera_$iDigitalStartChan") iDigitalStartChan++ } selectChannel = Integer.valueOf(channelList[0].getChannel()) val streamList = ArrayList<String>() streamList.add("main_stream") streamList.add("sub_stream") streamList.add("third_stream") //开始预览 if (previewHandle != -1) { SDKGuider.sdkGuider.devPreviewGuider.RealPlay_Stop_jni(previewHandle) } val strutPlayInfo = NET_DVR_PREVIEWINFO() strutPlayInfo.lChannel = selectChannel strutPlayInfo.dwStreamType = 1 strutPlayInfo.bBlocked = 1 strutPlayInfo.hHwnd = binding.surfaceView.holder previewHandle = SDKGuider.sdkGuider.devPreviewGuider.RealPlay_V40_jni( returnUserId, strutPlayInfo, null ) if (previewHandle < 0) { Log.d(kTag, "initEvent: Err:${MessageCodeHub.getErrorCode()}") return } isPreviewSuccess = true "预览开启成功".show(context) //开始计时 timer = Timer() timerTask = object : TimerTask() { override fun run() { seconds++ lifecycleScope.launch(Dispatchers.Main) { binding.runningTimeView.text = seconds.toTime() } } } timer?.schedule(timerTask, 0, 1000) } catch (e: IndexOutOfBoundsException) { e.printStackTrace() "设备未正常连接,无法开启预览".show(context) } } } else { if (!SDKGuider.sdkGuider.devPreviewGuider.RealPlay_Stop_jni(previewHandle)) { return } previewHandle = -1 isPreviewSuccess = false "预览关闭成功".show(context) //停止计时 timerTask?.cancel() timer?.cancel() } } override fun onLeftTurn() { if (isPreviewSuccess && isActionUp) { hkSDK.NET_DVR_PTZControlWithSpeed( previewHandle, PTZCommand.PAN_LEFT, 0, selectedSpeed ) isActionUp = false } } override fun onTopTurn() { if (isPreviewSuccess && isActionUp) { hkSDK.NET_DVR_PTZControlWithSpeed( previewHandle, PTZCommand.TILT_UP, 0, selectedSpeed ) isActionUp = false } } override fun onRightTurn() { if (isPreviewSuccess && isActionUp) { hkSDK.NET_DVR_PTZControlWithSpeed( previewHandle, PTZCommand.PAN_RIGHT, 0, selectedSpeed ) isActionUp = false } } override fun onBottomTurn() { if (isPreviewSuccess && isActionUp) { hkSDK.NET_DVR_PTZControlWithSpeed( previewHandle, PTZCommand.TILT_DOWN, 0, selectedSpeed ) isActionUp = false } } override fun onActionTurnUp(dir: SteeringWheelView.Direction) { when (dir) { SteeringWheelView.Direction.LEFT -> { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.PAN_LEFT, 1) isActionUp = true } SteeringWheelView.Direction.TOP -> { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.TILT_UP, 1) isActionUp = true } SteeringWheelView.Direction.RIGHT -> { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.PAN_RIGHT, 1) isActionUp = true } SteeringWheelView.Direction.BOTTOM -> { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.TILT_DOWN, 1) isActionUp = true } } } }) binding.upScaleButton.setOnTouchListener { _, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { if (isPreviewSuccess && isScaleButtonUp) { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.ZOOM_IN, 0) isScaleButtonUp = false } } MotionEvent.ACTION_UP -> { isScaleButtonUp = true hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.ZOOM_IN, 1) } } false } binding.downScaleButton.setOnTouchListener { _, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { if (isPreviewSuccess && isScaleButtonUp) { hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.ZOOM_OUT, 0) isScaleButtonUp = false } } MotionEvent.ACTION_UP -> { isScaleButtonUp = true hkSDK.NET_DVR_PTZControl(previewHandle, PTZCommand.ZOOM_OUT, 1) } } false } } private fun showScanResult() { BluetoothDeviceDialog.Builder().setContext(this).setDeviceArray(bluetoothDevices) .setNegativeButton("取消").setOnDialogButtonClickListener(object : BluetoothDeviceDialog.OnDialogButtonClickListener { override fun onItemClick(position: Int) { //连接点击的设备 startConnectDevice(bluetoothDevices[position]) } override fun onCancelClick() { } }).build().show() } private fun startConnectDevice(device: BluetoothDevice) { // 当前蓝牙设备 if (!isConnected) { LoadingDialogHub.show(this, "正在连接...") bleDeviceManager.connectBleDevice( device, ProjectConstant.SERVICE_UUID, 10 * 1000, bleConnectListener ) } else { bleDeviceManager.disConnectDevice() } } /** * 角度执行 * */ private fun executeAngle(horizontal: Int, vertical: Int) { //建立透明通道 val serialStart = NET_DVR_SERIALSTART_V40() //串口类型:1- 232 串口,2- 485 串口 serialStart.dwSerialPort = 2 //串口编号 serialStart.wPort = 0 val serialHandle = hkSDK.NET_DVR_SerialStart_V40( returnUserId, serialStart ) { _, _, _, _ -> } //向透明通道发送数据,水平和垂直需要分开发送 hkSDK.NET_DVR_SerialSend( serialHandle, 1, horizontal.createHorizontalCommand(), horizontal.createHorizontalCommand().size ) try { Thread.sleep(50) } catch (e: InterruptedException) { e.printStackTrace() } hkSDK.NET_DVR_SerialSend( serialHandle, 1, vertical.createVerticalCommand(), vertical.createVerticalCommand().size ) try { Thread.sleep(50) } catch (e: InterruptedException) { e.printStackTrace() } //关闭通道 hkSDK.NET_DVR_SerialStop(serialHandle) } override fun observeRequestState() { } override fun surfaceCreated(holder: SurfaceHolder) { binding.surfaceView.holder.setFormat(PixelFormat.TRANSLUCENT) if (-1 == previewHandle) { return } val surface = holder.surface if (surface.isValid) { if (-1 == SDKGuider.sdkGuider.devPreviewGuider.RealPlaySurfaceChanged_jni( previewHandle, 0, holder ) ) { Log.d(kTag, "surfaceCreated: ${MessageCodeHub.getErrorCode()}") } } } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { } override fun surfaceDestroyed(holder: SurfaceHolder) { if (-1 == previewHandle) { return } if (holder.surface.isValid) { if (-1 == SDKGuider.sdkGuider.devPreviewGuider.RealPlaySurfaceChanged_jni( previewHandle, 0, null ) ) { Log.d(kTag, "surfaceDestroyed: ${MessageCodeHub.getErrorCode()}") } } } override fun setupTopBarLayout() { ImmersionBar.with(this).statusBarDarkFont(false).init() //根据不同设备状态栏高度设置statusBarView高度 val statusBarHeight = getStatusBarHeight() binding.rootView.setPadding(0, statusBarHeight, 0, 0) binding.rootView.requestLayout() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (System.currentTimeMillis() - clickTime > 2000) { stopService(serviceIntent) "再按一次退出应用".show(this) clickTime = System.currentTimeMillis() return true } else { super.onKeyDown(keyCode, event) } } return super.onKeyDown(keyCode, event) } /***低功耗蓝牙********************************************/ private val bleConnectListener = object : OnDeviceConnectListener { override fun onConnecting(bluetoothGatt: BluetoothGatt?) { Log.d(kTag, "onConnecting => ") } override fun onConnectSuccess(bluetoothGatt: BluetoothGatt?, status: Int) { Log.d(kTag, "onConnectSuccess => $status") } override fun onConnectFailure( bluetoothGatt: BluetoothGatt?, exception: String, status: Int ) { Log.d(kTag, "onConnectFailure => $status") isConnected = false } override fun onDisConnecting(bluetoothGatt: BluetoothGatt?) { Log.d(kTag, "onDisConnecting => ") } override fun onDisConnectSuccess(bluetoothGatt: BluetoothGatt?, status: Int) { lifecycleScope.launch(Dispatchers.Main) { Log.d(kTag, "onDisConnectSuccess => $status") isConnected = false binding.searchBleButton.text = "搜索蓝牙" binding.stateView.text = "未连接" binding.stateView.setTextColor(Color.RED) } } override fun onServiceDiscoverySuccess(bluetoothGatt: BluetoothGatt?, status: Int) { lifecycleScope.launch(Dispatchers.Main) { Log.d(kTag, "onServiceDiscoverySuccess => $status") LoadingDialogHub.dismiss() "连接成功".show(context) isConnected = true binding.searchBleButton.text = "断开连接" binding.stateView.text = "已连接" binding.stateView.setTextColor(Color.GREEN) } } override fun onServiceDiscoveryFailed(bluetoothGatt: BluetoothGatt?, msg: String) { Log.d(kTag, "onServiceDiscoveryFailed => ") isConnected = false } override fun onReceiveMessage(bluetoothGatt: BluetoothGatt?, value: ByteArray) { //解析返回值 lifecycleScope.launch(Dispatchers.Main) { val density = value.convertValue() /***将值渲染到曲线图****************************************************************/ //时间作为X轴 xAxisLabels.add(System.currentTimeMillis().timestampToTime()) //浓度作为Y轴 val entry = Entry(i++.toFloat(), density.toFloat(), "浓度") densityEntries.add(entry) //转化为控件需要的Data dataSet = LineDataSet(densityEntries, "") dataSet.setDrawCircles(false) dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER //线条颜色 dataSet.color = Color.RED lineDataSets.add(dataSet) lineData = LineData(lineDataSets) lineData.setDrawValues(false) //解决折线点太多导致卡顿问题 if (lineData.entryCount > 50) { lineData.removeDataSet(0) lineData.notifyDataChanged() } binding.lineChart.data = lineData binding.lineChart.invalidate() } } override fun onReceiveError(errorMsg: String) { Log.d(kTag, "onReceiveError => $errorMsg") } override fun onWriteSuccess(bluetoothGatt: BluetoothGatt?, msg: ByteArray?) { Log.d(kTag, "onWriteSuccess => ${msg.contentToString()}") } override fun onWriteFailed(bluetoothGatt: BluetoothGatt?, errorMsg: String) { Log.d(kTag, "onWriteFailed => $errorMsg") } override fun onReadRssi(bluetoothGatt: BluetoothGatt?, rssi: Int, status: Int) { Log.d(kTag, "onReadRssi => $rssi") } } }