diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt index 88556ac..68830d5 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt @@ -11,14 +11,29 @@ const val PERMISSIONS_CODE = 999 const val PLAY_RTSP_CODE = 20241001 + /** + * Handler Request Code + * */ + const val OPEN_METHANE_CODE = 20240001 + const val CLOSE_METHANE_CODE = 20240002 + const val UPDATE_GAS_THRESHOLD_CODE = 20240003 + const val QUERY_CPU_TEMPERATURE_CODE = 20240004 + + /** + * Handler Response Code + * */ + const val QUERY_CPU_TEMPERATURE_RESPONSE_CODE = 20241004 + const val QUERY_METHANE_RESPONSE_CODE = 20241005 + /*** * SP Key * */ const val DEVICE_CONTROL_SERVER_CONFIG = "Key_1" const val OPEN_METHANE = "Key_2" - const val LOCALE_MODE = "Key_3" - const val OPEN_ALARM = "Key_4" - const val OPEN_VOICE = "Key_5" + const val LOCALE_MODE = "Key_4" + const val OPEN_ALARM = "Key_5" + const val OPEN_VOICE = "Key_6" + const val METHANE_DEFAULT_VALUE = "Key_7" //相机IP const val CAMERA_IP = "192.168.10.137" diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt index 88556ac..68830d5 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt @@ -11,14 +11,29 @@ const val PERMISSIONS_CODE = 999 const val PLAY_RTSP_CODE = 20241001 + /** + * Handler Request Code + * */ + const val OPEN_METHANE_CODE = 20240001 + const val CLOSE_METHANE_CODE = 20240002 + const val UPDATE_GAS_THRESHOLD_CODE = 20240003 + const val QUERY_CPU_TEMPERATURE_CODE = 20240004 + + /** + * Handler Response Code + * */ + const val QUERY_CPU_TEMPERATURE_RESPONSE_CODE = 20241004 + const val QUERY_METHANE_RESPONSE_CODE = 20241005 + /*** * SP Key * */ const val DEVICE_CONTROL_SERVER_CONFIG = "Key_1" const val OPEN_METHANE = "Key_2" - const val LOCALE_MODE = "Key_3" - const val OPEN_ALARM = "Key_4" - const val OPEN_VOICE = "Key_5" + const val LOCALE_MODE = "Key_4" + const val OPEN_ALARM = "Key_5" + const val OPEN_VOICE = "Key_6" + const val METHANE_DEFAULT_VALUE = "Key_7" //相机IP const val CAMERA_IP = "192.168.10.137" diff --git a/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt new file mode 100644 index 0000000..56f4571 --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt @@ -0,0 +1,35 @@ +package com.casic.app.safetreecontroller.widgets + +import android.content.Context +import android.widget.TextView +import com.casic.app.safetreecontroller.R +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF + +class LineChartMarkerView(context: Context) : MarkerView(context, R.layout.popu_line_chart_marker) { + + private val timeView: TextView = findViewById(R.id.timeView) + private val valueView: TextView = findViewById(R.id.valueView) + private var xAxisDate: MutableList = ArrayList() + + fun setXAxisDate(date: MutableList) { + this.xAxisDate = date + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + super.refreshContent(e, highlight) + try { + timeView.text = xAxisDate[(e.x).toInt()] + valueView.text = "${e.y}% VOL" + } catch (e1: Exception) { + e1.printStackTrace() + } + super.refreshContent(e, highlight) + } + + override fun getOffset(): MPPointF { + return MPPointF((-(width shr 1)).toFloat(), (-height).toFloat()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt index 88556ac..68830d5 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt @@ -11,14 +11,29 @@ const val PERMISSIONS_CODE = 999 const val PLAY_RTSP_CODE = 20241001 + /** + * Handler Request Code + * */ + const val OPEN_METHANE_CODE = 20240001 + const val CLOSE_METHANE_CODE = 20240002 + const val UPDATE_GAS_THRESHOLD_CODE = 20240003 + const val QUERY_CPU_TEMPERATURE_CODE = 20240004 + + /** + * Handler Response Code + * */ + const val QUERY_CPU_TEMPERATURE_RESPONSE_CODE = 20241004 + const val QUERY_METHANE_RESPONSE_CODE = 20241005 + /*** * SP Key * */ const val DEVICE_CONTROL_SERVER_CONFIG = "Key_1" const val OPEN_METHANE = "Key_2" - const val LOCALE_MODE = "Key_3" - const val OPEN_ALARM = "Key_4" - const val OPEN_VOICE = "Key_5" + const val LOCALE_MODE = "Key_4" + const val OPEN_ALARM = "Key_5" + const val OPEN_VOICE = "Key_6" + const val METHANE_DEFAULT_VALUE = "Key_7" //相机IP const val CAMERA_IP = "192.168.10.137" diff --git a/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt new file mode 100644 index 0000000..56f4571 --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt @@ -0,0 +1,35 @@ +package com.casic.app.safetreecontroller.widgets + +import android.content.Context +import android.widget.TextView +import com.casic.app.safetreecontroller.R +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF + +class LineChartMarkerView(context: Context) : MarkerView(context, R.layout.popu_line_chart_marker) { + + private val timeView: TextView = findViewById(R.id.timeView) + private val valueView: TextView = findViewById(R.id.valueView) + private var xAxisDate: MutableList = ArrayList() + + fun setXAxisDate(date: MutableList) { + this.xAxisDate = date + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + super.refreshContent(e, highlight) + try { + timeView.text = xAxisDate[(e.x).toInt()] + valueView.text = "${e.y}% VOL" + } catch (e1: Exception) { + e1.printStackTrace() + } + super.refreshContent(e, highlight) + } + + override fun getOffset(): MPPointF { + return MPPointF((-(width shr 1)).toFloat(), (-height).toFloat()) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml new file mode 100644 index 0000000..5efd97f --- /dev/null +++ b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt index 88556ac..68830d5 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt @@ -11,14 +11,29 @@ const val PERMISSIONS_CODE = 999 const val PLAY_RTSP_CODE = 20241001 + /** + * Handler Request Code + * */ + const val OPEN_METHANE_CODE = 20240001 + const val CLOSE_METHANE_CODE = 20240002 + const val UPDATE_GAS_THRESHOLD_CODE = 20240003 + const val QUERY_CPU_TEMPERATURE_CODE = 20240004 + + /** + * Handler Response Code + * */ + const val QUERY_CPU_TEMPERATURE_RESPONSE_CODE = 20241004 + const val QUERY_METHANE_RESPONSE_CODE = 20241005 + /*** * SP Key * */ const val DEVICE_CONTROL_SERVER_CONFIG = "Key_1" const val OPEN_METHANE = "Key_2" - const val LOCALE_MODE = "Key_3" - const val OPEN_ALARM = "Key_4" - const val OPEN_VOICE = "Key_5" + const val LOCALE_MODE = "Key_4" + const val OPEN_ALARM = "Key_5" + const val OPEN_VOICE = "Key_6" + const val METHANE_DEFAULT_VALUE = "Key_7" //相机IP const val CAMERA_IP = "192.168.10.137" diff --git a/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt new file mode 100644 index 0000000..56f4571 --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt @@ -0,0 +1,35 @@ +package com.casic.app.safetreecontroller.widgets + +import android.content.Context +import android.widget.TextView +import com.casic.app.safetreecontroller.R +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF + +class LineChartMarkerView(context: Context) : MarkerView(context, R.layout.popu_line_chart_marker) { + + private val timeView: TextView = findViewById(R.id.timeView) + private val valueView: TextView = findViewById(R.id.valueView) + private var xAxisDate: MutableList = ArrayList() + + fun setXAxisDate(date: MutableList) { + this.xAxisDate = date + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + super.refreshContent(e, highlight) + try { + timeView.text = xAxisDate[(e.x).toInt()] + valueView.text = "${e.y}% VOL" + } catch (e1: Exception) { + e1.printStackTrace() + } + super.refreshContent(e, highlight) + } + + override fun getOffset(): MPPointF { + return MPPointF((-(width shr 1)).toFloat(), (-height).toFloat()) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml new file mode 100644 index 0000000..5efd97f --- /dev/null +++ b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_text_tag_blue.xml b/app/src/main/res/drawable/bg_text_tag_blue.xml new file mode 100644 index 0000000..bb29dcb --- /dev/null +++ b/app/src/main/res/drawable/bg_text_tag_blue.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt index 4beefef..09edcf3 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/ByteArray.kt @@ -1,21 +1,38 @@ package com.casic.app.safetreecontroller.extensions +/** + * ByteArray转Hex + * */ +fun ByteArray.toHex(): String { + val hexArray = "0123456789ABCDEF".toCharArray() + val hexChars = CharArray(this.size * 2) + for (j in this.indices) { + val i = this[j].toInt() and 0xFF + hexChars[j * 2] = hexArray[i ushr 4] + hexChars[j * 2 + 1] = hexArray[i and 0x0F] + } + return String(hexChars) +} + fun ByteArray.handleGasConcentration(): Long { + /** + * [1, 0, 0, 0, 13, 14] + * */ //负值需要计算补码 - val x = if (this[2] < 0) { + val x = if (this[1] < 0) { + this[1].toInt() and 0xFF + } else { + this[1].toInt() + } + val y = if (this[2] < 0) { this[2].toInt() and 0xFF } else { this[2].toInt() } - val y = if (this[3] < 0) { + val z = if (this[3] < 0) { this[3].toInt() and 0xFF } else { this[3].toInt() } - val z = if (this[4] < 0) { - this[4].toInt() and 0xFF - } else { - this[4].toInt() - } return (x * 65536 + y * 256 + z).toLong() } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt new file mode 100644 index 0000000..2ab8cbd --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/extensions/LineChart.kt @@ -0,0 +1,46 @@ +package com.casic.app.safetreecontroller.extensions + +import android.graphics.Color +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.pengxh.kt.lite.extensions.dp2px + +fun LineChart.initConfig() { + this.setNoDataText("无数据,无法渲染...") + this.setNoDataTextColor(Color.RED) + this.getPaint(Chart.PAINT_INFO).textSize = 12f.dp2px(this.context) + this.setDrawGridBackground(false) + this.setDrawBorders(false) + this.animateY(1200, Easing.EaseInOutQuad) + //设置样式 + val rightAxis: YAxis = this.axisRight + //设置图表右边的y轴禁用 + rightAxis.isEnabled = false + val leftAxis: YAxis = this.axisLeft + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + leftAxis.setDrawZeroLine(true) + //只显示一条阈值线 + if (leftAxis.limitLines.size > 1) { + leftAxis.limitLines.removeAt(0) + } + this.isScaleXEnabled = true //X轴可缩放 + this.isScaleYEnabled = false //Y轴不可缩放 + //设置x轴 + val xAxis: XAxis = this.xAxis + xAxis.textSize = 10f + xAxis.setDrawLabels(true) //绘制标签 指x轴上的对应数值 + xAxis.setDrawAxisLine(true) //是否绘制轴线 + xAxis.setDrawGridLines(false) //设置x轴上每个点对应的线 + xAxis.granularity = 1f //禁止放大后x轴标签重绘 + xAxis.position = XAxis.XAxisPosition.BOTTOM + this.extraTopOffset = 20f + this.extraBottomOffset = 10f //解决X轴显示不完全问题 + //去掉描述 + this.description.isEnabled = false + //去掉图例 + this.legend.isEnabled = false +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt index 641df63..d62ab27 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/fragments/MethaneMonitorFragment.kt @@ -1,19 +1,46 @@ package com.casic.app.safetreecontroller.fragments import android.os.Bundle +import android.os.Handler +import android.os.Message import android.view.LayoutInflater import android.view.ViewGroup import com.casic.app.safetreecontroller.R import com.casic.app.safetreecontroller.databinding.FragmentMethaneMonitorBinding +import com.casic.app.safetreecontroller.extensions.initConfig +import com.casic.app.safetreecontroller.service.SocketCommunicationService import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.casic.app.safetreecontroller.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.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.dp2px import com.pengxh.kt.lite.extensions.getScreenWidth +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.utils.WeakReferenceHandler -class MethaneMonitorFragment : KotlinBaseFragment() { +class MethaneMonitorFragment : KotlinBaseFragment(), + Handler.Callback { + + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + + private val kTag = "MethaneMonitorFragment" + private val xAxisLabels: MutableList = ArrayList() + private val strengthEntries: MutableList = ArrayList() + private var lineDataSets: MutableList = ArrayList() + + //趋势线起点X坐标 + private var i = 0 override fun initOnCreate(savedInstanceState: Bundle?) { + weakReferenceHandler = WeakReferenceHandler(this) + //动态设置LineChart宽高 val rtspViewParams = binding.lineChart.layoutParams as ViewGroup.LayoutParams val videoWidth = requireContext().getScreenWidth() - 40.dp2px(requireContext()) @@ -21,6 +48,11 @@ rtspViewParams.width = videoWidth rtspViewParams.height = videoHeight.toInt() binding.lineChart.layoutParams = rtspViewParams + binding.lineChart.initConfig() + val markerView = LineChartMarkerView(requireContext()) + markerView.chartView = binding.lineChart + markerView.setXAxisDate(xAxisLabels) + binding.lineChart.marker = markerView } override fun initViewBinding( @@ -40,11 +72,64 @@ override fun initEvent() { binding.radioGroup.setOnCheckedChangeListener { _, checkedId -> if (checkedId == R.id.openRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.OPEN_METHANE_CODE) } else if (checkedId == R.id.closeRadioButton) { - SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.CLOSE_METHANE_CODE) } } + + binding.updateThresholdButton.setOnClickListener { + //更新甲烷阈值 + val threshold = binding.thresholdView.text.toString().toInt() + if (threshold > 9999) { + "报警阈值最大可设置9999".show(requireContext()) + return@setOnClickListener + } + + SaveKeyValues.putValue(LocaleConstant.METHANE_DEFAULT_VALUE, threshold) + val weakReferenceHandler = + SocketCommunicationService.weakReferenceHandler ?: return@setOnClickListener + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.UPDATE_GAS_THRESHOLD_CODE + message.obj = threshold + weakReferenceHandler.sendMessage(message) + } + + binding.queryCpuTemperatureButton.setOnClickListener { + SocketCommunicationService.weakReferenceHandler?.sendEmptyMessage(LocaleConstant.QUERY_CPU_TEMPERATURE_CODE) + } + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE -> { + binding.cpuTemperatureView.text = "${msg.obj} ℃" + } + + LocaleConstant.QUERY_METHANE_RESPONSE_CODE -> { + val ppm = msg.obj as Long + //LEL百分比 = (X / 10000) / 0.05 × 100% = X / 500 + val formatValue = "%.3f".format(ppm / 500f) + binding.currentGasValueView.text = "当前燃气浓度:${formatValue}% VOL" + + //折线图添加数据 + xAxisLabels.add(System.currentTimeMillis().timestampToTime()) + //浓度线 + strengthEntries.add( + Entry(i++.toFloat(), formatValue.toFloat(), "浓度") + ) + //设置数据 + val dataSet = LineDataSet(strengthEntries, "") + dataSet.setDrawCircles(false) + dataSet.mode = LineDataSet.Mode.CUBIC_BEZIER + lineDataSets.add(dataSet) + val lineData = LineData(lineDataSets) + lineData.setDrawValues(false) + binding.lineChart.data = lineData + binding.lineChart.invalidate() + } + } + return true } override fun onResume() { @@ -55,5 +140,9 @@ } else { binding.closeRadioButton.isChecked = true } + + //回显甲烷默认阈值 + val value = SaveKeyValues.getValue(LocaleConstant.METHANE_DEFAULT_VALUE, 1000) as Int + binding.thresholdView.setText(value.toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt index 40f35ec..3423a07 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/service/SocketCommunicationService.kt @@ -7,18 +7,44 @@ import android.os.Message import android.util.Log import com.casic.app.safetreecontroller.extensions.handleGasConcentration +import com.casic.app.safetreecontroller.extensions.toHex +import com.casic.app.safetreecontroller.fragments.MethaneMonitorFragment import com.casic.app.safetreecontroller.utils.CommandCreator +import com.casic.app.safetreecontroller.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues import com.pengxh.kt.lite.utils.WeakReferenceHandler import com.pengxh.kt.lite.utils.socket.tcp.OnTcpConnectStateListener import com.pengxh.kt.lite.utils.socket.tcp.TcpClient class SocketCommunicationService : Service(), OnTcpConnectStateListener, Handler.Callback { + companion object { + var weakReferenceHandler: WeakReferenceHandler? = null + } + private val kTag = "SocketService" - private val weakReferenceHandler by lazy { WeakReferenceHandler(this) } private val tcpClient by lazy { TcpClient(this) } override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LocaleConstant.OPEN_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createOpenLightCommand()) + } + + LocaleConstant.CLOSE_METHANE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCloseLightCommand()) + } + + LocaleConstant.UPDATE_GAS_THRESHOLD_CODE -> { + val threshold = msg.obj as Int + val thresholdCommand = CommandCreator.createThresholdCommand(threshold) + tcpClient.sendMessage(thresholdCommand) + } + + LocaleConstant.QUERY_CPU_TEMPERATURE_CODE -> { + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) + } + } return true } @@ -29,46 +55,102 @@ override fun run() { tcpClient.sendMessage(CommandCreator.createMethaneCommand()) // 每5s重复一次 - weakReferenceHandler.postDelayed(this, 5 * 1000L) + weakReferenceHandler?.postDelayed(this, 5 * 1000L) } } override fun onCreate() { super.onCreate() - Log.d(kTag, "onCreate: SocketCommunicationService") + weakReferenceHandler = WeakReferenceHandler(this) tcpClient.start("192.168.10.51", 333) -// tcpClient.start("192.168.161.200", 3000) + Log.d(kTag, "onCreate: SocketCommunicationService") } override fun onConnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110501) - weakReferenceHandler.post(methaneRunnable) + //定时查询甲烷浓度 + weakReferenceHandler?.post(methaneRunnable) + //查询一次激光芯片温度 + tcpClient.sendMessage(CommandCreator.createCpuTemperatureCommand()) } override fun onDisconnected() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110502) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onConnectFailed() { ForegroundRunningService.weakReferenceHandler?.sendEmptyMessage(2024110503) - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) } override fun onMessageReceived(bytes: ByteArray?) { - Log.d(kTag, bytes.contentToString()) if (bytes == null) { return } - if (bytes.size == 7) { - val concentration = bytes.handleGasConcentration() - Log.d(kTag, "onMessageResponse: $concentration") + Log.d(kTag, bytes.contentToString()) + + /** + * 激光开返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 1, 0, -109] + * 激光关返回:[7, 32, 0, 1, 0, 1, -69, 1, 0, -111, 0, 0, -110] + * 甲烷浓度返回:[7, 32, 0, 1, 0, 1, -86, 1, 0, 0, 0, 13, 14] + * 激光温度返回:[7, 32, 0, 1, 0, 1, -52, 1, 0, -106, 1, 96, -8] + * */ + if (bytes.size < 6) { + Log.d(kTag, "onMessageReceived: 数据异常,长度不够") + return + } + + //取前6位解析设备编号 + val deviceCode = bytes.take(6).toByteArray().toHex() + Log.d(kTag, "deviceCode: $deviceCode") + + //取数据类型标志位,根据标志位解析不同的数据 + val flag = (bytes[6].toInt() and 0xFF).toString(16).uppercase() + Log.d(kTag, "flag: $flag") + when (flag) { + "AA" -> { + //甲烷浓度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(7, bytes.size) + val concentration = dataArray.handleGasConcentration() + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_METHANE_RESPONSE_CODE + message.obj = concentration + weakReferenceHandler.sendMessage(message) + } + } + + "BB" -> { + //激光状态 + if (bytes.size == 13) { + if (bytes[10].toInt() == 1) { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, true) + } else { + SaveKeyValues.putValue(LocaleConstant.OPEN_METHANE, false) + } + } + } + + "CC" -> { + //激光温度 + if (bytes.size == 13) { + val dataArray = bytes.copyOfRange(10, bytes.size) + val temperature = (dataArray[0] * 256 + dataArray[1]) / 10f + val weakReferenceHandler = MethaneMonitorFragment.weakReferenceHandler ?: return + val message = weakReferenceHandler.obtainMessage() + message.what = LocaleConstant.QUERY_CPU_TEMPERATURE_RESPONSE_CODE + message.obj = temperature + weakReferenceHandler.sendMessage(message) + } + } } } override fun onDestroy() { super.onDestroy() - weakReferenceHandler.removeCallbacks(methaneRunnable) + weakReferenceHandler?.removeCallbacks(methaneRunnable) tcpClient.stop() } diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt index 35879e3..59eb314 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/CommandCreator.kt @@ -2,13 +2,6 @@ object CommandCreator { /** - * 查询甲烷浓度指令 - */ - fun createMethaneCommand(): ByteArray { - return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) - } - - /** * 打开激光 */ fun createOpenLightCommand(): ByteArray { @@ -23,40 +16,53 @@ } /** + * 查询甲烷浓度指令 + */ + fun createMethaneCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x95.toByte(), 0x00, 0x00, 0x96.toByte()) + } + + /** * 设置甲烷阈值 */ - fun setMethaneThresholdCommandCommand(threshold: Int): ByteArray? { + fun createThresholdCommand(threshold: Int): ByteArray { + //数据位和校验位先都设置0x00占位 + val bytes = byteArrayOf( + 0xAA.toByte(), + 0x01, + 0x00, + 0x94.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x00.toByte() + ) for (i in 0..255) { for (j in 0..255) { for (k in 0..255) { //浓度值 = 数据码1 * 65536 + 数据码2 * 256 + 数据码3 if (65536 * i + 256 * j + k == threshold) { - val result = ByteArray(3) - result[0] = i.toByte() - result[1] = j.toByte() - result[2] = k.toByte() + bytes[4] = i.toByte() + bytes[5] = j.toByte() + bytes[6] = k.toByte() - //计算校验位。校验位先置为0x00 - val bytes = byteArrayOf( - 0xAA.toByte(), - 0x01, - 0x00, - 0x94.toByte(), - result[0], - result[1], - result[2], - 0x00.toByte() - ) + //计算校验位。 var sum = 0 for (l in 1 until bytes.size - 1) { sum += bytes[l] } bytes[7] = sum.toByte() - return bytes } } } } - return null + return bytes + } + + /** + * 查询甲烷芯片温度指令 + * */ + fun createCpuTemperatureCommand(): ByteArray { + return byteArrayOf(0xAA.toByte(), 0x01, 0x00, 0x96.toByte(), 0x00, 0x00, 0x97.toByte()) } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt index 88556ac..68830d5 100644 --- a/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/app/safetreecontroller/utils/LocaleConstant.kt @@ -11,14 +11,29 @@ const val PERMISSIONS_CODE = 999 const val PLAY_RTSP_CODE = 20241001 + /** + * Handler Request Code + * */ + const val OPEN_METHANE_CODE = 20240001 + const val CLOSE_METHANE_CODE = 20240002 + const val UPDATE_GAS_THRESHOLD_CODE = 20240003 + const val QUERY_CPU_TEMPERATURE_CODE = 20240004 + + /** + * Handler Response Code + * */ + const val QUERY_CPU_TEMPERATURE_RESPONSE_CODE = 20241004 + const val QUERY_METHANE_RESPONSE_CODE = 20241005 + /*** * SP Key * */ const val DEVICE_CONTROL_SERVER_CONFIG = "Key_1" const val OPEN_METHANE = "Key_2" - const val LOCALE_MODE = "Key_3" - const val OPEN_ALARM = "Key_4" - const val OPEN_VOICE = "Key_5" + const val LOCALE_MODE = "Key_4" + const val OPEN_ALARM = "Key_5" + const val OPEN_VOICE = "Key_6" + const val METHANE_DEFAULT_VALUE = "Key_7" //相机IP const val CAMERA_IP = "192.168.10.137" diff --git a/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt new file mode 100644 index 0000000..56f4571 --- /dev/null +++ b/app/src/main/java/com/casic/app/safetreecontroller/widgets/LineChartMarkerView.kt @@ -0,0 +1,35 @@ +package com.casic.app.safetreecontroller.widgets + +import android.content.Context +import android.widget.TextView +import com.casic.app.safetreecontroller.R +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF + +class LineChartMarkerView(context: Context) : MarkerView(context, R.layout.popu_line_chart_marker) { + + private val timeView: TextView = findViewById(R.id.timeView) + private val valueView: TextView = findViewById(R.id.valueView) + private var xAxisDate: MutableList = ArrayList() + + fun setXAxisDate(date: MutableList) { + this.xAxisDate = date + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + super.refreshContent(e, highlight) + try { + timeView.text = xAxisDate[(e.x).toInt()] + valueView.text = "${e.y}% VOL" + } catch (e1: Exception) { + e1.printStackTrace() + } + super.refreshContent(e, highlight) + } + + override fun getOffset(): MPPointF { + return MPPointF((-(width shr 1)).toFloat(), (-height).toFloat()) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml new file mode 100644 index 0000000..5efd97f --- /dev/null +++ b/app/src/main/res/drawable/bg_stroke_layout_blue_5.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_text_tag_blue.xml b/app/src/main/res/drawable/bg_text_tag_blue.xml new file mode 100644 index 0000000..bb29dcb --- /dev/null +++ b/app/src/main/res/drawable/bg_text_tag_blue.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_methane_monitor.xml b/app/src/main/res/layout/fragment_methane_monitor.xml index 9b6015d..a52d491 100644 --- a/app/src/main/res/layout/fragment_methane_monitor.xml +++ b/app/src/main/res/layout/fragment_methane_monitor.xml @@ -66,16 +66,20 @@ android:orientation="horizontal">