diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt new file mode 100644 index 0000000..090f604 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt @@ -0,0 +1,104 @@ +package com.casic.detector.common.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import com.casic.detector.common.base.BaseApplication +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.utils.GpioManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class SerialPortService : Service(), LifecycleOwner { + + private val kTag = "SerialPortService" + private val registry = LifecycleRegistry(this) + private val gpioManager by lazy { GpioManager() } + private val serialPorts by lazy { BaseApplication.get().getSerialPorts() } + private var gpioState = "" + + override fun getLifecycle(): Lifecycle { + return registry + } + + override fun onBind(intent: Intent?): IBinder { + return ServiceBinder() + } + + inner class ServiceBinder : Binder() { + fun getSerialPortService(): SerialPortService { + return this@SerialPortService + } + } + + fun openSerialPort(listener: OnSerialPortDataListener) { + //调高串口电位 + gpioManager.setGpioHigh("18") + gpioState = "1" + Log.d(kTag, "openSerialPort: 调高串口电位") + + Thread.sleep(100) + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + //写 + listener.write(this[0].outputStream) + val stream = this[0].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + val stream = this[1].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + } + + fun closeSerialPort() { + //降低串口电位 + gpioManager.setGpioLow("18") + gpioState = "0" + Log.d(kTag, "closeSerialPort: 降低串口电位") + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt new file mode 100644 index 0000000..090f604 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt @@ -0,0 +1,104 @@ +package com.casic.detector.common.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import com.casic.detector.common.base.BaseApplication +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.utils.GpioManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class SerialPortService : Service(), LifecycleOwner { + + private val kTag = "SerialPortService" + private val registry = LifecycleRegistry(this) + private val gpioManager by lazy { GpioManager() } + private val serialPorts by lazy { BaseApplication.get().getSerialPorts() } + private var gpioState = "" + + override fun getLifecycle(): Lifecycle { + return registry + } + + override fun onBind(intent: Intent?): IBinder { + return ServiceBinder() + } + + inner class ServiceBinder : Binder() { + fun getSerialPortService(): SerialPortService { + return this@SerialPortService + } + } + + fun openSerialPort(listener: OnSerialPortDataListener) { + //调高串口电位 + gpioManager.setGpioHigh("18") + gpioState = "1" + Log.d(kTag, "openSerialPort: 调高串口电位") + + Thread.sleep(100) + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + //写 + listener.write(this[0].outputStream) + val stream = this[0].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + val stream = this[1].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + } + + fun closeSerialPort() { + //降低串口电位 + gpioManager.setGpioLow("18") + gpioState = "0" + Log.d(kTag, "closeSerialPort: 降低串口电位") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt new file mode 100644 index 0000000..b50aaaf --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt @@ -0,0 +1,548 @@ +package com.casic.detector.common.view + +import android.app.DatePickerDialog +import android.content.ComponentName +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.widget.AdapterView +import androidx.lifecycle.ViewModelProvider +import com.amap.api.location.AMapLocation +import com.casic.detector.common.R +import com.casic.detector.common.adapter.EditableImageAdapter +import com.casic.detector.common.bean.MarkerLocalBean +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivityInstallMarkerBinding +import com.casic.detector.common.extensions.compressImage +import com.casic.detector.common.extensions.getDefaultValue +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.setDefaultValue +import com.casic.detector.common.extensions.show +import com.casic.detector.common.extensions.toColor +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.extensions.toObjectType +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.interfaces.OnResultCallbackListener +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dateToTimestamp +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToCompleteDate +import com.pengxh.kt.lite.extensions.timestampToTime +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.LoadState +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.io.File +import java.io.OutputStream +import java.util.Calendar +import java.util.Date + +class InstallMarkerActivity : KotlinBaseActivity() { + + private val kTag = "InstallMarkerActivity" + private val context = this + private val calendar by lazy { Calendar.getInstance() } + private val locationTool by lazy { LocationTool(this) } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val realPaths = ArrayList() //真实图片路径 + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var soundResourceId = 0 + private var serialPortService: SerialPortService? = null + private lateinit var imageAdapter: EditableImageAdapter + + override fun initEvent() { + //返回 + binding.titleInclude.leftBackView.setOnClickListener { + soundPool.autoPause() + finish() + } + + binding.objectInclude.objectTypeSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, view: View?, position: Int, id: Long + ) { + when (position) { + 0 -> { + //显示管线属性 + binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 1 -> { + //显示管线附属物属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 2 -> { + //显示管线特征点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 3 -> { + //显示交叉穿越点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.VISIBLE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + + } + } + + //安装 + binding.installButton.setOnClickListener { + val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { + when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { + "管线" -> "请输入管线种类!".show(this) + "管线附属物" -> "请输入附属物名称!".show(this) + "管线特征管点" -> "请输入特征管点!".show(this) + "交叉穿越点" -> "请输入上层管种类!".show(this) + } + return@setOnClickListener + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { + if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { + "请输入管径".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { + "请输入埋深".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { + if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { + "请输入下层管管径".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.areaView.text.isNullOrBlank()) { + "请输入所属区域".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.lineView.text.isNullOrBlank()) { + "请输入所属线路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.roadView.text.isNullOrBlank()) { + "请输入所属道路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { + "请选择建设年代".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.ownerView.text.isNullOrBlank()) { + "请输入权属单位".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { + "请先读取标识器获取ID".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { + "请输入标识器埋深".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { + "请输入标识器安装部门".show(this) + return@setOnClickListener + } + + //先存本地再上传服务器 + saveMarkerInLocal() + + taskViewModel.installLabel( + this, companyId, + binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), + binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", + "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), + binding.objectInclude.areaView.text.toString(), + binding.objectInclude.lineView.text.toString(), + binding.objectInclude.roadView.text.toString(), + binding.objectInclude.constructDateView.text.toString(), + binding.objectInclude.ownerView.text.toString(), + objectId, + binding.identifierInclude.identifierIdView.text.toString(), + binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), + "${binding.identifierInclude.identifierDeepView.text}mm", + binding.identifierInclude.personDeptView.text.toString(), + binding.identifierInclude.installTimeView.text.toString(), + binding.identifierInclude.lngView.text.toString(), + binding.identifierInclude.latView.text.toString(), + binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), + binding.remarkView.text.toString(), + realPaths + ) + + //保存默认值 + "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) + "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) + "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) + "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) + "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) + "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) + "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) + "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) + "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) + "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) + "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) + } + + //读标识器 + binding.readMarkerButton.setOnClickListener { + LoadingDialogHub.show(this, "标识器读取中,请稍后...") + countDownTimer.start() + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + outStream.write('2'.code) + outStream.flush() + + soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) + binding.readMarkerButton.isEnabled = false + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + Log.d(kTag, hex) + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + countDownTimer.cancel() + cancelLoadingView() + binding.readMarkerButton.isEnabled = true + binding.identifierInclude.identifierIdView.text = markerId + } + } + }) + } + } + + /** + * 搜索标识器超时倒计时 + * */ + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + cancelLoadingView() + "读取此标识器ID超时,请重试".show(context) + binding.readMarkerButton.isEnabled = true + } + } + + private fun cancelLoadingView() { + LoadingDialogHub.dismiss() + soundPool.autoPause() + serialPortService?.closeSerialPort() + } + + private fun saveMarkerInLocal() { + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + + val marker = MarkerLocalBean() + marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() + marker.pipelineType = binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() + marker.pipelineMaterial = + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() + marker.pipelineDiameter = "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" + marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" + marker.underlyingPipelineType = + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() + marker.underlyingPipelineMaterial = + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() + marker.underlyingPipelineDiameter = + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" + marker.underlyingPipelineDepth = + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" + marker.buryMethod = + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() + marker.area = binding.objectInclude.areaView.text.toString() + marker.line = binding.objectInclude.lineView.text.toString() + marker.road = binding.objectInclude.roadView.text.toString() + marker.constructTime = binding.objectInclude.constructDateView.text.toString() + marker.owner = binding.objectInclude.ownerView.text.toString() + marker.objectId = objectId + marker.markerId = binding.identifierInclude.identifierIdView.text.toString() + marker.markerType = binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() + marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" + marker.installationDept = binding.identifierInclude.personDeptView.text.toString() + marker.updateTime = binding.identifierInclude.installTimeView.text.toString() + marker.lng = binding.identifierInclude.lngView.text.toString() + marker.lat = binding.identifierInclude.latView.text.toString() + marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() + marker.remark = binding.remarkView.text.toString() + marker.imagePath = realPaths.toJson() + + DataBaseManager.get.saveMarkerInLocale(marker) + } + + private fun takePicture() { + PictureSelector.create(this).openCamera(SelectMimeType.ofImage()) + .forResult(object : OnResultCallbackListener { + override fun onResult(result: ArrayList?) { + if (result == null) { + "拍照失败,请重试".show(context) + return + } + analyticalSelectResults(result[0]) + } + + override fun onCancel() { + + } + }) + } + + private fun analyticalSelectResults(result: LocalMedia) { + //压缩图片 + result.realPath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + realPaths.add(file.absolutePath) + imageAdapter.setupImage(realPaths) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + //在连接正常关闭的情况下不会被调用, 只在Service被破坏了或者被杀死的时候调用 + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + locationTool.getCurrentLocation(true, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + if (location != null) { + binding.identifierInclude.lngView.text = location.longitude.toString() + binding.identifierInclude.latView.text = location.latitude.toString() + } else { + "当前位置信号差,无法获取定位".show(context) + } + } + }) + soundResourceId = soundPool.load(this, R.raw.ring3, 1) + + //初始化数据 + initDefaultData() + } + + override fun initViewBinding(): ActivityInstallMarkerBinding { + return ActivityInstallMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + taskViewModel.loadState.observe(this) { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "标识器安装中,请稍后...") + + LoadState.Success -> { + LoadingDialogHub.dismiss() + clearDefaultData() + "安装成功".show(this) + } + + else -> LoadingDialogHub.dismiss() + } + } + } + + override fun setupTopBarLayout() { + + } + + private fun initDefaultData() { + binding.titleInclude.titleView.text = "安装新标识器" + binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) + + imageAdapter = EditableImageAdapter(this, 3, 3) + binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter + + //设置默认值 + binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) + binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) + binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) + binding.objectInclude.areaView.setText("areaView".getDefaultValue()) + binding.objectInclude.lineView.setText("lineView".getDefaultValue()) + binding.objectInclude.roadView.setText("roadView".getDefaultValue()) + binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) + binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) + binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) + + /**************************************************************************************/ + binding.objectInclude.objectTypeSpinner.show(this, LocaleConstant.POINT_TYPE_ARRAY, 0) + binding.objectInclude.pipeInclude.materialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeAttachInclude.attachSpinner.show( + this, LocaleConstant.ATTACH_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeFeatureInclude.featureSpinner.show( + this, LocaleConstant.FEATURE_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeInclude.buryTypeSpinner.show( + this, LocaleConstant.BURY_METHOD_ARRAY, 0 + ) + binding.objectInclude.constructDateView.setOnClickListener { + val datePicker = DatePickerDialog( + this, null, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ) + datePicker.show() + + datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { + val year = datePicker.datePicker.year + val month = datePicker.datePicker.month + 1 + val day = datePicker.datePicker.dayOfMonth + val selectedDate = String.format( + "%s-%s-%s", year, month.appendZero(), day.appendZero() + ) + + //当前时间 + val current = System.currentTimeMillis().timestampToTime() + val today = "$selectedDate $current".dateToTimestamp() + if (Date(today).after(Date())) { + "建设年代不能早于当前日期".show(context) + } else { + datePicker.dismiss() + binding.objectInclude.constructDateView.text = selectedDate + } + } + } + binding.identifierInclude.identifierTypeSpinner.show( + this, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 + ) + binding.identifierInclude.installTimeView.text = + System.currentTimeMillis().timestampToCompleteDate() + binding.identifierInclude.colorSpinner.show(this, LocaleConstant.COLOR_ARRAY, 0) + + imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { + override fun onAddImageClick() { + takePicture() + } + + override fun onItemClick(position: Int) { + if (realPaths[position].isEmpty()) { + "图片加载失败,无法查看大图".show(context) + } else { + navigatePageTo(position, realPaths) + } + } + + override fun onItemLongClick(view: View?, position: Int) { + imageAdapter.deleteImage(position) + } + }) + } + + //清除默认数据 + private fun clearDefaultData() { + "markerObjectTypeView".setDefaultValue("") + "pipelineDiameterView".setDefaultValue("") + "buryDeepView".setDefaultValue("") + "bottomPipeDiameterView".setDefaultValue("") + "bottomPointDeepView".setDefaultValue("") + "areaView".setDefaultValue("") + "lineView".setDefaultValue("") + "roadView".setDefaultValue("") + "ownerView".setDefaultValue("") + "identifierDeepView".setDefaultValue("") + "personDeptView".setDefaultValue("") + } + + override fun onDestroy() { + super.onDestroy() + soundPool.autoPause() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt new file mode 100644 index 0000000..090f604 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt @@ -0,0 +1,104 @@ +package com.casic.detector.common.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import com.casic.detector.common.base.BaseApplication +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.utils.GpioManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class SerialPortService : Service(), LifecycleOwner { + + private val kTag = "SerialPortService" + private val registry = LifecycleRegistry(this) + private val gpioManager by lazy { GpioManager() } + private val serialPorts by lazy { BaseApplication.get().getSerialPorts() } + private var gpioState = "" + + override fun getLifecycle(): Lifecycle { + return registry + } + + override fun onBind(intent: Intent?): IBinder { + return ServiceBinder() + } + + inner class ServiceBinder : Binder() { + fun getSerialPortService(): SerialPortService { + return this@SerialPortService + } + } + + fun openSerialPort(listener: OnSerialPortDataListener) { + //调高串口电位 + gpioManager.setGpioHigh("18") + gpioState = "1" + Log.d(kTag, "openSerialPort: 调高串口电位") + + Thread.sleep(100) + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + //写 + listener.write(this[0].outputStream) + val stream = this[0].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + val stream = this[1].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + } + + fun closeSerialPort() { + //降低串口电位 + gpioManager.setGpioLow("18") + gpioState = "0" + Log.d(kTag, "closeSerialPort: 降低串口电位") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt new file mode 100644 index 0000000..b50aaaf --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt @@ -0,0 +1,548 @@ +package com.casic.detector.common.view + +import android.app.DatePickerDialog +import android.content.ComponentName +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.widget.AdapterView +import androidx.lifecycle.ViewModelProvider +import com.amap.api.location.AMapLocation +import com.casic.detector.common.R +import com.casic.detector.common.adapter.EditableImageAdapter +import com.casic.detector.common.bean.MarkerLocalBean +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivityInstallMarkerBinding +import com.casic.detector.common.extensions.compressImage +import com.casic.detector.common.extensions.getDefaultValue +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.setDefaultValue +import com.casic.detector.common.extensions.show +import com.casic.detector.common.extensions.toColor +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.extensions.toObjectType +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.interfaces.OnResultCallbackListener +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dateToTimestamp +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToCompleteDate +import com.pengxh.kt.lite.extensions.timestampToTime +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.LoadState +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.io.File +import java.io.OutputStream +import java.util.Calendar +import java.util.Date + +class InstallMarkerActivity : KotlinBaseActivity() { + + private val kTag = "InstallMarkerActivity" + private val context = this + private val calendar by lazy { Calendar.getInstance() } + private val locationTool by lazy { LocationTool(this) } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val realPaths = ArrayList() //真实图片路径 + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var soundResourceId = 0 + private var serialPortService: SerialPortService? = null + private lateinit var imageAdapter: EditableImageAdapter + + override fun initEvent() { + //返回 + binding.titleInclude.leftBackView.setOnClickListener { + soundPool.autoPause() + finish() + } + + binding.objectInclude.objectTypeSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, view: View?, position: Int, id: Long + ) { + when (position) { + 0 -> { + //显示管线属性 + binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 1 -> { + //显示管线附属物属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 2 -> { + //显示管线特征点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 3 -> { + //显示交叉穿越点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.VISIBLE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + + } + } + + //安装 + binding.installButton.setOnClickListener { + val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { + when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { + "管线" -> "请输入管线种类!".show(this) + "管线附属物" -> "请输入附属物名称!".show(this) + "管线特征管点" -> "请输入特征管点!".show(this) + "交叉穿越点" -> "请输入上层管种类!".show(this) + } + return@setOnClickListener + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { + if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { + "请输入管径".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { + "请输入埋深".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { + if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { + "请输入下层管管径".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.areaView.text.isNullOrBlank()) { + "请输入所属区域".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.lineView.text.isNullOrBlank()) { + "请输入所属线路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.roadView.text.isNullOrBlank()) { + "请输入所属道路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { + "请选择建设年代".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.ownerView.text.isNullOrBlank()) { + "请输入权属单位".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { + "请先读取标识器获取ID".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { + "请输入标识器埋深".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { + "请输入标识器安装部门".show(this) + return@setOnClickListener + } + + //先存本地再上传服务器 + saveMarkerInLocal() + + taskViewModel.installLabel( + this, companyId, + binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), + binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", + "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), + binding.objectInclude.areaView.text.toString(), + binding.objectInclude.lineView.text.toString(), + binding.objectInclude.roadView.text.toString(), + binding.objectInclude.constructDateView.text.toString(), + binding.objectInclude.ownerView.text.toString(), + objectId, + binding.identifierInclude.identifierIdView.text.toString(), + binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), + "${binding.identifierInclude.identifierDeepView.text}mm", + binding.identifierInclude.personDeptView.text.toString(), + binding.identifierInclude.installTimeView.text.toString(), + binding.identifierInclude.lngView.text.toString(), + binding.identifierInclude.latView.text.toString(), + binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), + binding.remarkView.text.toString(), + realPaths + ) + + //保存默认值 + "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) + "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) + "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) + "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) + "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) + "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) + "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) + "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) + "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) + "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) + "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) + } + + //读标识器 + binding.readMarkerButton.setOnClickListener { + LoadingDialogHub.show(this, "标识器读取中,请稍后...") + countDownTimer.start() + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + outStream.write('2'.code) + outStream.flush() + + soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) + binding.readMarkerButton.isEnabled = false + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + Log.d(kTag, hex) + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + countDownTimer.cancel() + cancelLoadingView() + binding.readMarkerButton.isEnabled = true + binding.identifierInclude.identifierIdView.text = markerId + } + } + }) + } + } + + /** + * 搜索标识器超时倒计时 + * */ + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + cancelLoadingView() + "读取此标识器ID超时,请重试".show(context) + binding.readMarkerButton.isEnabled = true + } + } + + private fun cancelLoadingView() { + LoadingDialogHub.dismiss() + soundPool.autoPause() + serialPortService?.closeSerialPort() + } + + private fun saveMarkerInLocal() { + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + + val marker = MarkerLocalBean() + marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() + marker.pipelineType = binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() + marker.pipelineMaterial = + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() + marker.pipelineDiameter = "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" + marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" + marker.underlyingPipelineType = + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() + marker.underlyingPipelineMaterial = + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() + marker.underlyingPipelineDiameter = + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" + marker.underlyingPipelineDepth = + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" + marker.buryMethod = + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() + marker.area = binding.objectInclude.areaView.text.toString() + marker.line = binding.objectInclude.lineView.text.toString() + marker.road = binding.objectInclude.roadView.text.toString() + marker.constructTime = binding.objectInclude.constructDateView.text.toString() + marker.owner = binding.objectInclude.ownerView.text.toString() + marker.objectId = objectId + marker.markerId = binding.identifierInclude.identifierIdView.text.toString() + marker.markerType = binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() + marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" + marker.installationDept = binding.identifierInclude.personDeptView.text.toString() + marker.updateTime = binding.identifierInclude.installTimeView.text.toString() + marker.lng = binding.identifierInclude.lngView.text.toString() + marker.lat = binding.identifierInclude.latView.text.toString() + marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() + marker.remark = binding.remarkView.text.toString() + marker.imagePath = realPaths.toJson() + + DataBaseManager.get.saveMarkerInLocale(marker) + } + + private fun takePicture() { + PictureSelector.create(this).openCamera(SelectMimeType.ofImage()) + .forResult(object : OnResultCallbackListener { + override fun onResult(result: ArrayList?) { + if (result == null) { + "拍照失败,请重试".show(context) + return + } + analyticalSelectResults(result[0]) + } + + override fun onCancel() { + + } + }) + } + + private fun analyticalSelectResults(result: LocalMedia) { + //压缩图片 + result.realPath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + realPaths.add(file.absolutePath) + imageAdapter.setupImage(realPaths) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + //在连接正常关闭的情况下不会被调用, 只在Service被破坏了或者被杀死的时候调用 + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + locationTool.getCurrentLocation(true, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + if (location != null) { + binding.identifierInclude.lngView.text = location.longitude.toString() + binding.identifierInclude.latView.text = location.latitude.toString() + } else { + "当前位置信号差,无法获取定位".show(context) + } + } + }) + soundResourceId = soundPool.load(this, R.raw.ring3, 1) + + //初始化数据 + initDefaultData() + } + + override fun initViewBinding(): ActivityInstallMarkerBinding { + return ActivityInstallMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + taskViewModel.loadState.observe(this) { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "标识器安装中,请稍后...") + + LoadState.Success -> { + LoadingDialogHub.dismiss() + clearDefaultData() + "安装成功".show(this) + } + + else -> LoadingDialogHub.dismiss() + } + } + } + + override fun setupTopBarLayout() { + + } + + private fun initDefaultData() { + binding.titleInclude.titleView.text = "安装新标识器" + binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) + + imageAdapter = EditableImageAdapter(this, 3, 3) + binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter + + //设置默认值 + binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) + binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) + binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) + binding.objectInclude.areaView.setText("areaView".getDefaultValue()) + binding.objectInclude.lineView.setText("lineView".getDefaultValue()) + binding.objectInclude.roadView.setText("roadView".getDefaultValue()) + binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) + binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) + binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) + + /**************************************************************************************/ + binding.objectInclude.objectTypeSpinner.show(this, LocaleConstant.POINT_TYPE_ARRAY, 0) + binding.objectInclude.pipeInclude.materialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeAttachInclude.attachSpinner.show( + this, LocaleConstant.ATTACH_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeFeatureInclude.featureSpinner.show( + this, LocaleConstant.FEATURE_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeInclude.buryTypeSpinner.show( + this, LocaleConstant.BURY_METHOD_ARRAY, 0 + ) + binding.objectInclude.constructDateView.setOnClickListener { + val datePicker = DatePickerDialog( + this, null, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ) + datePicker.show() + + datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { + val year = datePicker.datePicker.year + val month = datePicker.datePicker.month + 1 + val day = datePicker.datePicker.dayOfMonth + val selectedDate = String.format( + "%s-%s-%s", year, month.appendZero(), day.appendZero() + ) + + //当前时间 + val current = System.currentTimeMillis().timestampToTime() + val today = "$selectedDate $current".dateToTimestamp() + if (Date(today).after(Date())) { + "建设年代不能早于当前日期".show(context) + } else { + datePicker.dismiss() + binding.objectInclude.constructDateView.text = selectedDate + } + } + } + binding.identifierInclude.identifierTypeSpinner.show( + this, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 + ) + binding.identifierInclude.installTimeView.text = + System.currentTimeMillis().timestampToCompleteDate() + binding.identifierInclude.colorSpinner.show(this, LocaleConstant.COLOR_ARRAY, 0) + + imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { + override fun onAddImageClick() { + takePicture() + } + + override fun onItemClick(position: Int) { + if (realPaths[position].isEmpty()) { + "图片加载失败,无法查看大图".show(context) + } else { + navigatePageTo(position, realPaths) + } + } + + override fun onItemLongClick(view: View?, position: Int) { + imageAdapter.deleteImage(position) + } + }) + } + + //清除默认数据 + private fun clearDefaultData() { + "markerObjectTypeView".setDefaultValue("") + "pipelineDiameterView".setDefaultValue("") + "buryDeepView".setDefaultValue("") + "bottomPipeDiameterView".setDefaultValue("") + "bottomPointDeepView".setDefaultValue("") + "areaView".setDefaultValue("") + "lineView".setDefaultValue("") + "roadView".setDefaultValue("") + "ownerView".setDefaultValue("") + "identifierDeepView".setDefaultValue("") + "personDeptView".setDefaultValue("") + } + + override fun onDestroy() { + super.onDestroy() + soundPool.autoPause() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt index 1dac658..f949269 100644 --- a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt +++ b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt @@ -1,31 +1,25 @@ package com.casic.detector.common.view -import android.app.DatePickerDialog -import android.app.Dialog +import android.content.ComponentName import android.content.Context -import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager import android.media.AudioAttributes import android.media.SoundPool import android.os.Bundle -import android.os.CountDownTimer +import android.os.IBinder import android.util.Log import android.view.KeyEvent import android.view.View -import android.widget.AdapterView import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions -import com.amap.api.maps.AMapUtils import com.amap.api.maps.CameraUpdateFactory import com.amap.api.maps.CoordinateConverter import com.amap.api.maps.model.BitmapDescriptorFactory @@ -34,38 +28,29 @@ import com.amap.api.maps.model.MarkerOptions import com.amap.api.maps.model.MyLocationStyle import com.casic.detector.common.R -import com.casic.detector.common.adapter.EditableImageAdapter -import com.casic.detector.common.base.SerialPortActivity +import com.casic.detector.common.base.BaseApplication import com.casic.detector.common.bean.MarkerLocalBean import com.casic.detector.common.bean.TaskLocalBean import com.casic.detector.common.callback.OnGetLocationListener -import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener import com.casic.detector.common.cluster.ClusterItem import com.casic.detector.common.cluster.ClusterOverlay import com.casic.detector.common.cluster.RegionItem import com.casic.detector.common.databinding.ActivityMainBinding -import com.casic.detector.common.databinding.DialogInstallMarkerBinding -import com.casic.detector.common.databinding.DialogSearchMarkerNewBinding import com.casic.detector.common.extensions.appendDownloadUrl -import com.casic.detector.common.extensions.compressImage import com.casic.detector.common.extensions.convertToGPGGA import com.casic.detector.common.extensions.createTaskCode import com.casic.detector.common.extensions.drawCircle -import com.casic.detector.common.extensions.getDefaultValue import com.casic.detector.common.extensions.hexToString import com.casic.detector.common.extensions.initImmersionBar import com.casic.detector.common.extensions.isNumber -import com.casic.detector.common.extensions.setDefaultValue -import com.casic.detector.common.extensions.show -import com.casic.detector.common.extensions.toColor import com.casic.detector.common.extensions.toHex -import com.casic.detector.common.extensions.toObjectType import com.casic.detector.common.model.TaskDetailLocalModel import com.casic.detector.common.model.TaskModel +import com.casic.detector.common.service.SerialPortService import com.casic.detector.common.utils.DataBaseManager import com.casic.detector.common.utils.ExcelTool import com.casic.detector.common.utils.FileType -import com.casic.detector.common.utils.GpioManager import com.casic.detector.common.utils.LocaleConstant import com.casic.detector.common.utils.LocationTool import com.casic.detector.common.utils.NtripAuthorizationCreator @@ -77,26 +62,15 @@ import com.casic.detector.common.vm.TaskViewModel import com.casic.detector.common.widgets.MarkerDetailDialog import com.casic.detector.common.widgets.QueryMarkerDialog -import com.casic.detector.common.widgets.RadarScanView import com.casic.detector.common.widgets.SamplePopupWindow -import com.luck.picture.lib.basic.PictureSelector -import com.luck.picture.lib.config.SelectMimeType -import com.luck.picture.lib.entity.LocalMedia -import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.extensions.appendZero -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertDrawable import com.pengxh.kt.lite.extensions.createDownloadFileDir -import com.pengxh.kt.lite.extensions.dateToTimestamp import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.getSystemService -import com.pengxh.kt.lite.extensions.initDialogLayoutParams import com.pengxh.kt.lite.extensions.isNetworkConnected import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToCompleteDate -import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.FileDownloadManager import com.pengxh.kt.lite.utils.LoadState @@ -108,60 +82,41 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import io.netty.buffer.Unpooled import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.io.OutputStream import java.nio.charset.StandardCharsets -import java.util.Calendar -import java.util.Date import java.util.Timer import java.util.TimerTask -import kotlin.math.atan2 -class MainActivity : SerialPortActivity(), SensorEventListener, - OnSocketConnectListener { +class MainActivity : KotlinBaseActivity(), OnSocketConnectListener { private val kTag = "MainActivity" private val context = this private val samplePopupWindow by lazy { SamplePopupWindow(this) } private val regionRadius by lazy { LocaleConstant.RADIUS_SIZE.dp2px(this) } private val backDrawables by lazy { HashMap() } - private val installDialog by lazy { InstallMarkerDialog(this) } - private val searchNewDialog by lazy { SearchMarkerNewDialog(this) } private val detailDialog by lazy { MarkerDetailDialog(this) } private val locationTool by lazy { LocationTool(this) } - private val rotationMatrix = FloatArray(9)//旋转矩阵缓存 - private val valueArray = FloatArray(3)//方位角数值 + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() private var clickTime: Long = 0 private var markers = ArrayList() private var clusterOverlay: ClusterOverlay? = null private var isFreeTask = false private var freeTaskTitle = "" private var ids = HashSet() - private var signalTask: TimerTask? = null - private var energyTask: TimerTask? = null - private var searchMarkerTimer: Timer? = null private var freeTaskId: String? = null - private var gravity: FloatArray? = null - private var geomagnetic: FloatArray? = null private var connectState = ConnectState.CLOSED private var socketClient: SocketClient? = null - private lateinit var aMap: AMap - private lateinit var sensorManager: SensorManager - - /***inner class 需要用到*****start*/ - private val gpioManager by lazy { GpioManager() } - private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } - private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() - private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var serialPortService: SerialPortService? = null + private var searchMarkerTimer: Timer? = null private var soundResourceId = 0 - private var slowSoundResourceId = 0 - private var fastSoundResourceId = 0 private var isExecuteTask = false - - /***inner class 需要用到*****end*/ + private lateinit var aMap: AMap override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) @@ -171,10 +126,26 @@ binding.rootView.initImmersionBar(this, false, R.color.themeColor) } + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + + } + } + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + soundResourceId = soundPool.load(this, R.raw.ring3, 1) - slowSoundResourceId = soundPool.load(this, R.raw.ring4, 1) - fastSoundResourceId = soundPool.load(this, R.raw.ring2, 1) RtkLocationTool.getCurrentLocation(this) { if (connectState == ConnectState.SUCCESS) { @@ -192,8 +163,6 @@ samplePopupWindow.setPopupMenuItem(LocaleConstant.POPUP_TITLES) samplePopupWindow.setBackgroundDrawable(null) - sensorManager = getSystemService()!! - //task网络请求监听 taskViewModel.markerFileResult.observe(this) { if (it.isSuccess) { @@ -316,9 +285,6 @@ //安装。上传,然后存入本地库 binding.installButton.setOnClickListener { - /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * */ if (isFreeTask) { AlertMessageDialog.Builder().setContext(this).setTitle("温馨提示") .setMessage("正在自由巡检任务中,请完成此次任务之后再使用此功能") @@ -329,7 +295,7 @@ } }).build().show() } else { - installDialog.show() + navigatePageTo() } } @@ -449,8 +415,6 @@ //探测 binding.detectionButton.setOnClickListener { /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * * 如果开启自由巡检就不让探测 * */ if (isFreeTask) { @@ -463,7 +427,12 @@ } }).build().show() } else { - searchNewDialog.show() + val flag = if (isExecuteTask) { + "1" + } else { + "0" + } + navigatePageTo(flag) } } @@ -476,14 +445,8 @@ override fun onConfirmClick() { isFreeTask = false soundPool.autoPause() - - //停止信号和ID搜索定时器 - signalTask?.cancel() - energyTask?.cancel() searchMarkerTimer?.cancel() - - //降低串口电位 - gpioManager.setGpioLow("18") + serialPortService?.closeSerialPort() binding.stopFreeTaskButton.visibility = View.GONE if (freeTaskId.isNullOrBlank()) { @@ -527,110 +490,79 @@ } private fun openSerialPort() { - //调高串口电位 - gpioManager.setGpioHigh("18") - - isFreeTask = true - //自由巡检 - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - searchMarkerTimer = Timer() - searchMarkerTimer?.apply { - schedule(signalTask, 0, 200) - schedule(energyTask, 0, 251) - } - binding.stopFreeTaskButton.visibility = View.VISIBLE - } + isFreeTask = true + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + searchMarkerTimer = Timer() + searchMarkerTimer?.schedule(object : TimerTask() { + override fun run() { + outStream.write('2'.code) + outStream.flush() - override fun onDataReceived(buffer: ByteArray) { - val hex = buffer.toHex() -// Log.d(kTag, "$kTag => $hex") - if (searchNewDialog.isDetectMarker) { - searchNewDialog.bindingValue(hex) - } else if (installDialog.isReadMarker) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - installDialog.bindingValue(markerId) + Thread.sleep(50) + + outStream.write('6'.code) + outStream.flush() + } + }, 0, 200) } - } else if (isFreeTask) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - //只响一次,因为探测频率高,所以依旧是连续的报警声 - soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) - //添加地图Marker - if (!ids.contains(markerId)) { - //根据markerId查询标识器经纬度 - val labels = DataBaseManager.get.queryMarkerById(markerId) - if (labels.isNotEmpty()) { - val bean = labels.first() - aMap.addMarker( - MarkerOptions().position( - LatLng(bean.lat.toDouble(), bean.lng.toDouble()) - ).icon(BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1)) - ) + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("4E")) { + //只响一次,因为探测频率高,所以依旧是连续的报警声 + soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) + + try { + val energyResponse = hex.take(10).hexToString() + val energy = energyResponse.substring(1).toInt() + if (energy <= 1500 && detailDialog.isShowing) { + detailDialog.dismiss() + } + }catch (e:NumberFormatException){ + e.printStackTrace() } } - ids.add(markerId) - //显示标识器详细信息 - if (!detailDialog.isShowing) { - val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() - if (markerBean == null) { - "无法查询到此ID【${markerId}】的信息".show(context) - } else { - detailDialog.setMarker(markerBean) - detailDialog.show() + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + //添加地图Marker + if (!ids.contains(markerId)) { + //根据markerId查询标识器经纬度 + val labels = DataBaseManager.get.queryMarkerById(markerId) + if (labels.isNotEmpty()) { + val bean = labels.first() + aMap.addMarker( + MarkerOptions().position( + LatLng(bean.lat.toDouble(), bean.lng.toDouble()) + ).icon( + BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1) + ) + ) + } + } + ids.add(markerId) + + //显示标识器详细信息 + if (!detailDialog.isShowing) { + val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() + if (markerBean == null) { + "无法查询到此ID【${markerId}】的信息".show(context) + } else { + detailDialog.setMarker(markerBean) + detailDialog.show() + } } } } - - if (hex.startsWith("4E")) { - val energyResponse = hex.take(10).hexToString() - try { - val energy = energyResponse.substring(1).toInt() - if (energy <= 500 && detailDialog.isShowing) { - detailDialog.dismiss() - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } - } + }) } override fun observeRequestState() { taskViewModel.loadState.observe(this) { when (it) { - LoadState.Loading -> { - if (installDialog.isInstallMarker) { - LoadingDialogHub.show(this, "标识器安装中,请稍后...") - } else { - LoadingDialogHub.show(this, "提交工单中,请稍后") - } - } - - LoadState.Success -> { - if (installDialog.isInstallMarker) { - installDialog.clearDefaultData() - installDialog.dismiss() - "${installDialog.markerId}安装成功".show(this) - } - LoadingDialogHub.dismiss() - } + LoadState.Loading -> LoadingDialogHub.show(this, "提交工单中,请稍后") else -> LoadingDialogHub.dismiss() } @@ -939,32 +871,6 @@ } else super.onKeyDown(keyCode, event) } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - //精度发生变化时触发 - } - - override fun onSensorChanged(event: SensorEvent?) { - //值发生变化时触发 - val type = event?.sensor?.type - - if (type == Sensor.TYPE_ACCELEROMETER) { - gravity = event.values - } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { - geomagnetic = event.values - } - - if (gravity == null || geomagnetic == null) { - return - } - - if (SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)) { - SensorManager.getOrientation(rotationMatrix, valueArray) - - val degree = ((360f + valueArray[0] * 180f / Math.PI) % 360).toInt() - searchNewDialog.updateDegreeValue(degree) - } - } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { @@ -978,14 +884,6 @@ showLabelsOnMap() } - //注册加速度传感器监听 - val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL) - - //注册磁场传感器监听 - val magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) - sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL) - //取缓存 val remoteHost = SaveKeyValues.getValue( LocaleConstant.RTK_SERVER, "203.107.45.154" @@ -995,20 +893,20 @@ ) as String //连接千寻RTK服务器 - lifecycleScope.launch(Dispatchers.IO) { - if (socketClient != null) { - socketClient?.disconnect() - delay(3000) - } - - socketClient = SocketClient.Builder() - .setHostname(remoteHost) - .setPort(remotePort.toInt()) - .setTimeout(5000) - .setOnSocketListener(this@MainActivity) - .build() - socketClient?.connect() - } +// lifecycleScope.launch(Dispatchers.IO) { +// if (socketClient != null) { +// socketClient?.disconnect() +// delay(3000) +// } +// +// socketClient = SocketClient.Builder() +// .setHostname(remoteHost) +// .setPort(remotePort.toInt()) +// .setTimeout(5000) +// .setOnSocketListener(this@MainActivity) +// .build() +// socketClient?.connect() +// } } override fun onMessageResponse(data: ByteArray) { @@ -1021,12 +919,12 @@ val result = String(data, StandardCharsets.UTF_8) Log.d(kTag, "onMessageResponse: $result") if (result.contains("ICY 200 OK")) { - "高精度定位服务器连接成功".show(this) + "高精度定位服务连接成功".show(this) } } else { "收到千寻数据返回,长度:${data.size}".show(this) - out.write(data) - out.flush() +// out.write(data) +// out.flush() } } @@ -1061,7 +959,6 @@ override fun onPause() { super.onPause() binding.mapView.onPause() - sensorManager.unregisterListener(this) } override fun onSaveInstanceState(outState: Bundle) { @@ -1072,794 +969,11 @@ override fun onDestroy() { super.onDestroy() binding.mapView.onDestroy() - soundPool.release() + soundPool.autoPause() + searchMarkerTimer?.cancel() + serialPortService?.closeSerialPort() locationTool.stopLocation() - //降低串口电位 - gpioManager.setGpioLow("18") + unbindService(serviceConnection) + BaseApplication.get().closeSerialPort() } - - /**安装标识器对话框******************************************************************************/ - inner class InstallMarkerDialog(private val context: Context) : Dialog(context) { - - private val binding: DialogInstallMarkerBinding by binding() - private val calendar by lazy { Calendar.getInstance() } - private val realPaths = ArrayList() //真实图片路径 - private lateinit var imageAdapter: EditableImageAdapter - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - - var isReadMarker = false - var isInstallMarker = false - var markerId = "" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - - //初始化数据 - initDefaultData() - - //返回 - binding.titleInclude.leftBackView.setOnClickListener { - soundPool.autoPause() - dismiss() - } - - binding.objectInclude.objectTypeSpinner.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, view: View?, position: Int, id: Long - ) { - when (position) { - 0 -> { - //显示管线属性 - binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 1 -> { - //显示管线附属物属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 2 -> { - //显示管线特征点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 3 -> { - //显示交叉穿越点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = - View.VISIBLE - } - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - - } - } - - //安装 - binding.installButton.setOnClickListener { - val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { - when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { - "管线" -> "请输入管线种类!".show(context) - "管线附属物" -> "请输入附属物名称!".show(context) - "管线特征管点" -> "请输入特征管点!".show(context) - "交叉穿越点" -> "请输入上层管种类!".show(context) - } - return@setOnClickListener - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { - if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { - "请输入管径".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { - "请输入埋深".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { - if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { - "请输入下层管管径".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.areaView.text.isNullOrBlank()) { - "请输入所属区域".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.lineView.text.isNullOrBlank()) { - "请输入所属线路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.roadView.text.isNullOrBlank()) { - "请输入所属道路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { - "请选择建设年代".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.ownerView.text.isNullOrBlank()) { - "请输入权属单位".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { - "请先读取标识器获取ID".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { - "请输入标识器埋深".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { - "请输入标识器安装部门".show(context) - return@setOnClickListener - } - - //先存本地再上传服务器 - saveMarkerInLocal() - - isInstallMarker = true - taskViewModel.installLabel( - context, - companyId, - binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", - "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), - binding.objectInclude.areaView.text.toString(), - binding.objectInclude.lineView.text.toString(), - binding.objectInclude.roadView.text.toString(), - binding.objectInclude.constructDateView.text.toString(), - binding.objectInclude.ownerView.text.toString(), - objectId, - binding.identifierInclude.identifierIdView.text.toString(), - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), - "${binding.identifierInclude.identifierDeepView.text}mm", - binding.identifierInclude.personDeptView.text.toString(), - binding.identifierInclude.installTimeView.text.toString(), - binding.identifierInclude.lngView.text.toString(), - binding.identifierInclude.latView.text.toString(), - binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), - binding.remarkView.text.toString(), - realPaths - ) - - //保存默认值 - "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) - "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) - "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) - "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) - "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) - "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) - "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) - "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) - "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) - "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) - "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) - } - - //读标识器 - binding.readMarkerButton.setOnClickListener { - LoadingDialogHub.show(this@MainActivity, "标识器读取中,请稍后...") - binding.readMarkerButton.isEnabled = false - - isReadMarker = true - - //调高串口电位 - gpioManager.setGpioHigh("18") - - soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) - - out.write('2'.code) - out.flush() - - countDownTimer = object : CountDownTimer(10 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - soundPool.autoPause() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - "读取此标识器ID超时,请退出应用再试".show(context) - } - } - countDownTimer.start() - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - locationTool.getCurrentLocation(true, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - if (location != null) { - binding.identifierInclude.lngView.text = location.longitude.toString() - binding.identifierInclude.latView.text = location.latitude.toString() - } else { - "当前位置信号差,无法获取定位".show(context) - } - } - }) - } - - fun bindingValue(markerId: String) { - this.markerId = markerId - LoadingDialogHub.dismiss() - soundPool.autoPause() - countDownTimer.cancel() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - binding.identifierInclude.identifierIdView.text = markerId - } - - override fun dismiss() { - //降低串口电位 - gpioManager.setGpioLow("18") - soundPool.autoPause() - isInstallMarker = false - locationTool.stopLocation() - super.dismiss() - } - - private fun initDefaultData() { - binding.titleInclude.titleView.text = "安装新标识器" - binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) - - imageAdapter = EditableImageAdapter(context, 3, 3) - binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter - - //设置默认值 - binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) - binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) - binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) - binding.objectInclude.areaView.setText("areaView".getDefaultValue()) - binding.objectInclude.lineView.setText("lineView".getDefaultValue()) - binding.objectInclude.roadView.setText("roadView".getDefaultValue()) - binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) - binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) - binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) - - /**************************************************************************************/ - binding.objectInclude.objectTypeSpinner.show( - this@MainActivity, LocaleConstant.POINT_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.materialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeAttachInclude.attachSpinner.show( - this@MainActivity, LocaleConstant.ATTACH_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeFeatureInclude.featureSpinner.show( - this@MainActivity, LocaleConstant.FEATURE_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.buryTypeSpinner.show( - this@MainActivity, LocaleConstant.BURY_METHOD_ARRAY, 0 - ) - - binding.objectInclude.constructDateView.setOnClickListener { - val datePicker = DatePickerDialog( - context, - null, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH) - ) - datePicker.show() - - datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { - val year = datePicker.datePicker.year - val month = datePicker.datePicker.month + 1 - val day = datePicker.datePicker.dayOfMonth - val selectedDate = String.format( - "%s-%s-%s", year, month.appendZero(), day.appendZero() - ) - - //当前时间 - val current = System.currentTimeMillis().timestampToTime() - val today = "$selectedDate $current".dateToTimestamp() - if (Date(today).after(Date())) { - "建设年代不能早于当前日期".show(context) - } else { - datePicker.dismiss() - binding.objectInclude.constructDateView.text = selectedDate - } - } - } - - binding.identifierInclude.identifierTypeSpinner.show( - this@MainActivity, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 - ) - - binding.identifierInclude.installTimeView.text = - System.currentTimeMillis().timestampToCompleteDate() - - binding.identifierInclude.colorSpinner.show( - this@MainActivity, LocaleConstant.COLOR_ARRAY, 0 - ) - - imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { - override fun onAddImageClick() { - takePicture() - } - - override fun onItemClick(position: Int) { - if (realPaths[position].isEmpty()) { - "图片加载失败,无法查看大图".show(context) - } else { - context.navigatePageTo(position, realPaths) - } - } - - override fun onItemLongClick(view: View?, position: Int) { - imageAdapter.deleteImage(position) - } - }) - /**************************************************************************************/ - } - - //清除默认数据 - fun clearDefaultData() { - "markerObjectTypeView".setDefaultValue("") - "pipelineDiameterView".setDefaultValue("") - "buryDeepView".setDefaultValue("") - "bottomPipeDiameterView".setDefaultValue("") - "bottomPointDeepView".setDefaultValue("") - "areaView".setDefaultValue("") - "lineView".setDefaultValue("") - "roadView".setDefaultValue("") - "ownerView".setDefaultValue("") - "identifierDeepView".setDefaultValue("") - "personDeptView".setDefaultValue("") - } - - private fun saveMarkerInLocal() { - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - - val marker = MarkerLocalBean() - marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() - marker.pipelineType = - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() - marker.pipelineMaterial = - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() - marker.pipelineDiameter = - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" - marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" - marker.underlyingPipelineType = - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() - marker.underlyingPipelineMaterial = - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() - marker.underlyingPipelineDiameter = - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" - marker.underlyingPipelineDepth = - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" - marker.buryMethod = - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() - marker.area = binding.objectInclude.areaView.text.toString() - marker.line = binding.objectInclude.lineView.text.toString() - marker.road = binding.objectInclude.roadView.text.toString() - marker.constructTime = binding.objectInclude.constructDateView.text.toString() - marker.owner = binding.objectInclude.ownerView.text.toString() - marker.objectId = objectId - marker.markerId = binding.identifierInclude.identifierIdView.text.toString() - marker.markerType = - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() - marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" - marker.installationDept = binding.identifierInclude.personDeptView.text.toString() - marker.updateTime = binding.identifierInclude.installTimeView.text.toString() - marker.lng = binding.identifierInclude.lngView.text.toString() - marker.lat = binding.identifierInclude.latView.text.toString() - marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() - marker.remark = binding.remarkView.text.toString() - marker.imagePath = realPaths.toJson() - - DataBaseManager.get.saveMarkerInLocale(marker) - } - - private fun takePicture() { - PictureSelector.create(this@MainActivity).openCamera(SelectMimeType.ofImage()) - .forResult(object : OnResultCallbackListener { - override fun onResult(result: java.util.ArrayList?) { - if (result == null) { - "拍照失败,请重试".show(context) - return - } - analyticalSelectResults(result[0]) - } - - override fun onCancel() { - - } - }) - } - - private fun analyticalSelectResults(result: LocalMedia) { - //压缩图片 - result.realPath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - realPaths.add(file.absolutePath) - imageAdapter.setupImage(realPaths) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - /**探测标识器新对话框****************************************************************************/ - inner class SearchMarkerNewDialog(private val context: Context) : Dialog(context) { - - private val taskId by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_ID, "") as String - } - private val taskCode by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_CODE, "") as String - } - private val markerPoints by lazy { ArrayList() } - private val binding: DialogSearchMarkerNewBinding by binding() - private var markerId = ""//实际探测出来的标识器ID - private var nearestMarkerId = ""//探测不到标识器的时候计算出来的最近的标识器ID - private lateinit var searchMarkerTimer: Timer - private lateinit var signalTask: TimerTask - private lateinit var energyTask: TimerTask - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - var isDetectMarker = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - binding.depthButton.setOnClickListener { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - - out.write('3'.code) - val result = DataBaseManager.get.queryMarkerById(markerId) - if (result.isNotEmpty()) { - val tag = when (result.first().markerType) { - "EM30" -> '7' - "EM50" -> '8' - "EM14" -> '9' - else -> '1' - } - if (tag == '1') { - "此标识器无法读取埋深!".show(context) - initTimer() - } else { - // 发送读取标识器埋设深度指令 - LoadingDialogHub.show(this@MainActivity, "正在测距,请稍后...") - out.write(tag.code) - out.flush() - countDownTimer = object : CountDownTimer(15 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - "探测此标识器深度超时,请重试".show(context) - initTimer() - } - } - countDownTimer.start() - } - } else { - "标识器未安装,安装成功后才可读取埋深!".show(context) - initTimer() - } - } - - binding.markerInfoButton.setOnClickListener { - val id = if (markerId == "") { - nearestMarkerId - } else { - markerId - } - //查库 - val result = DataBaseManager.get.queryMarkerById(id) - if (result.isNotEmpty()) { - context.navigatePageTo(result.first().toJson()) - } else { - context.navigatePageTo(id) - } - //查看完就把ID置空,便于下次查看最新的ID - markerId = "" - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - //点位渲染,每次定位都计算当前位置与符合条件的点距离 - locationTool.getCurrentLocation(false, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - location?.apply { - renderDataPoint(this) - } - } - }) - } - - /** - * 计算并渲染数据点。点太多采用协程计算,不然会有点卡顿 - * @param location 定位点(RTK获取) - * */ - private fun renderDataPoint(location: AMapLocation) { - val longitude = location.longitude - val latitude = location.latitude - lifecycleScope.launch(Dispatchers.IO) { - val dataPoints = ArrayList() - DataBaseManager.get.loadMarkers().forEach { - val distance = AMapUtils.calculateLineDistance( - LatLng(it.lat.toDouble(), it.lng.toDouble()), LatLng(latitude, longitude) - ) - val formatDistance = "%.2f".format(distance).toFloat() - - markerPoints.add(MarkerDistanceData(it.markerId, formatDistance)) - - if (formatDistance <= LocaleConstant.MAX_DISTANCE) { - val angle = atan2( - (it.lat.toDouble() - latitude), (it.lng.toDouble() - longitude) - ) + Math.PI - val formatAngle = "%.2f".format(angle).toDouble() - dataPoints.add( - RadarScanView.DataPoint(formatAngle, formatDistance) - ) - } - } - withContext(Dispatchers.Main) { - binding.radarScanView.renderPointData(dataPoints, - object : RadarScanView.OnGetNearestPointCallback { - override fun getNearestPoint(point: RadarScanView.DataPoint?) { - if (point == null) { - binding.distanceValueView.text = "大于5.5m" - binding.distancePgBar.progress = 100 - } else { - binding.distanceValueView.text = "${point.distance}m" - val progress = - if (point.distance > LocaleConstant.MAX_DISTANCE) { - 100 - } else { - (point.distance / LocaleConstant.MAX_DISTANCE) * 100 - } - binding.distancePgBar.progress = progress.toInt() - } - } - }) - } - } - } - - override fun onStart() { - super.onStart() - //调高串口电位 - gpioManager.setGpioHigh("18") - - initTimer() - - isDetectMarker = true - - if (isExecuteTask) { - binding.taskStateView.visibility = View.GONE - binding.taskStateView.isSelected = false - } else { - binding.taskStateView.visibility = View.VISIBLE - binding.taskStateView.isSelected = true - } - } - - fun bindingValue(hex: String) { - if (hex.startsWith("4E")) { - try { - //4E转为String为N,代表能量值 - //用能量值转动表盘 - val energyResponse = hex.take(10).hexToString() - val energy = energyResponse.substring(1).toInt() - if (energy >= 4000) { - soundPool.play(fastSoundResourceId, 1f, 1f, 0, 0, 1f) - } else { - soundPool.play(slowSoundResourceId, 1f, 1f, 0, 0, 1f) - } - - //通过设置进度条表示能量值 - binding.energyPgBar.progress = energy - binding.energyValueView.text = "${energy}dB" - - if (energy <= 700) {//18° - binding.energyTipsView.text = "信号较弱,可能距离较远" - binding.energyTipsView.setTextColor(Color.parseColor("#8D1717")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_red) - - binding.depthButton.isEnabled = false - binding.depthButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_disable) - binding.markerInfoButton.isEnabled = false - binding.markerInfoButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_disable) - - binding.searchResultView.text = "未检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#8D1717")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_red) - } else if (energy >= 4100) { - binding.energyTipsView.text = "信号极强,接近标识器正上方" - binding.energyTipsView.setTextColor(Color.parseColor("#428d00")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_green) - - if (markerPoints.isEmpty()) { - Log.d(kTag, "bindingValue: markerPoints.isEmpty()") - return - } - - //需要转一下,不然会有并发问题 - val temp = ArrayList() - temp.addAll(markerPoints) - temp.sortBy(MarkerDistanceData::distance) - val nearestPoint = temp.first() - nearestMarkerId = nearestPoint.markerId - handleMarker(nearestMarkerId) - } else { - binding.energyTipsView.text = "已靠近,请继续移动位置" - binding.energyTipsView.setTextColor(Color.parseColor("#8C5700")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_yellow) - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else if (hex.startsWith("53")) { - LoadingDialogHub.dismiss() - countDownTimer.cancel() - try { - val depthResponse = hex.take(10).hexToString() - val depth = depthResponse.drop(2).toInt() - AlertMessageDialog.Builder().setContext(context).setTitle("温馨提示") - .setMessage("标识器埋深:${depth}厘米").setPositiveButton("知道了") - .setOnDialogButtonClickListener(object : - AlertMessageDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - initTimer() - } - }).build().show() - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else { - val id = hex.take(20).hexToString() - if (id.isNumber()) { - markerId = id - handleMarker(markerId) - } - } - } - - private fun handleMarker(id: String) { - binding.depthButton.isEnabled = true - binding.depthButton.setTextColor(Color.WHITE) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_enable) - binding.markerInfoButton.isEnabled = true - binding.markerInfoButton.setTextColor(Color.WHITE) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_enable) - - binding.searchResultView.text = "已检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#428d00")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_green) - - //自动上传标识器 - if (isExecuteTask) { - val taskMarkerLocalBean = DataBaseManager.get.queryTaskMarkerById( - taskId, taskCode, id, "0" - ) - taskMarkerLocalBean?.apply { - taskViewModel.uploadMarker(context, this) - } - } - } - - //更新罗盘角度 - fun updateDegreeValue(degree: Int) { - binding.radarScanView.setDegreeValue(degree) - } - - private fun initTimer() { - searchMarkerTimer = Timer() - - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - //错开信号和能量定时器 - searchMarkerTimer.schedule(signalTask, 0, 201) - searchMarkerTimer.schedule(energyTask, 0, 251) - } - - override fun dismiss() { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - //降低串口电位 - gpioManager.setGpioLow("18") - isDetectMarker = false - soundPool.autoPause() - locationTool.stopLocation() - super.dismiss() - } - } - - /** - * 标识器与当前定位的数据 - * */ - data class MarkerDistanceData(var markerId: String, var distance: Float) } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt new file mode 100644 index 0000000..090f604 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt @@ -0,0 +1,104 @@ +package com.casic.detector.common.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import com.casic.detector.common.base.BaseApplication +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.utils.GpioManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class SerialPortService : Service(), LifecycleOwner { + + private val kTag = "SerialPortService" + private val registry = LifecycleRegistry(this) + private val gpioManager by lazy { GpioManager() } + private val serialPorts by lazy { BaseApplication.get().getSerialPorts() } + private var gpioState = "" + + override fun getLifecycle(): Lifecycle { + return registry + } + + override fun onBind(intent: Intent?): IBinder { + return ServiceBinder() + } + + inner class ServiceBinder : Binder() { + fun getSerialPortService(): SerialPortService { + return this@SerialPortService + } + } + + fun openSerialPort(listener: OnSerialPortDataListener) { + //调高串口电位 + gpioManager.setGpioHigh("18") + gpioState = "1" + Log.d(kTag, "openSerialPort: 调高串口电位") + + Thread.sleep(100) + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + //写 + listener.write(this[0].outputStream) + val stream = this[0].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + val stream = this[1].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + } + + fun closeSerialPort() { + //降低串口电位 + gpioManager.setGpioLow("18") + gpioState = "0" + Log.d(kTag, "closeSerialPort: 降低串口电位") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt new file mode 100644 index 0000000..b50aaaf --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt @@ -0,0 +1,548 @@ +package com.casic.detector.common.view + +import android.app.DatePickerDialog +import android.content.ComponentName +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.widget.AdapterView +import androidx.lifecycle.ViewModelProvider +import com.amap.api.location.AMapLocation +import com.casic.detector.common.R +import com.casic.detector.common.adapter.EditableImageAdapter +import com.casic.detector.common.bean.MarkerLocalBean +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivityInstallMarkerBinding +import com.casic.detector.common.extensions.compressImage +import com.casic.detector.common.extensions.getDefaultValue +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.setDefaultValue +import com.casic.detector.common.extensions.show +import com.casic.detector.common.extensions.toColor +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.extensions.toObjectType +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.interfaces.OnResultCallbackListener +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dateToTimestamp +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToCompleteDate +import com.pengxh.kt.lite.extensions.timestampToTime +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.LoadState +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.io.File +import java.io.OutputStream +import java.util.Calendar +import java.util.Date + +class InstallMarkerActivity : KotlinBaseActivity() { + + private val kTag = "InstallMarkerActivity" + private val context = this + private val calendar by lazy { Calendar.getInstance() } + private val locationTool by lazy { LocationTool(this) } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val realPaths = ArrayList() //真实图片路径 + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var soundResourceId = 0 + private var serialPortService: SerialPortService? = null + private lateinit var imageAdapter: EditableImageAdapter + + override fun initEvent() { + //返回 + binding.titleInclude.leftBackView.setOnClickListener { + soundPool.autoPause() + finish() + } + + binding.objectInclude.objectTypeSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, view: View?, position: Int, id: Long + ) { + when (position) { + 0 -> { + //显示管线属性 + binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 1 -> { + //显示管线附属物属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 2 -> { + //显示管线特征点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 3 -> { + //显示交叉穿越点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.VISIBLE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + + } + } + + //安装 + binding.installButton.setOnClickListener { + val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { + when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { + "管线" -> "请输入管线种类!".show(this) + "管线附属物" -> "请输入附属物名称!".show(this) + "管线特征管点" -> "请输入特征管点!".show(this) + "交叉穿越点" -> "请输入上层管种类!".show(this) + } + return@setOnClickListener + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { + if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { + "请输入管径".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { + "请输入埋深".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { + if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { + "请输入下层管管径".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.areaView.text.isNullOrBlank()) { + "请输入所属区域".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.lineView.text.isNullOrBlank()) { + "请输入所属线路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.roadView.text.isNullOrBlank()) { + "请输入所属道路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { + "请选择建设年代".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.ownerView.text.isNullOrBlank()) { + "请输入权属单位".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { + "请先读取标识器获取ID".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { + "请输入标识器埋深".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { + "请输入标识器安装部门".show(this) + return@setOnClickListener + } + + //先存本地再上传服务器 + saveMarkerInLocal() + + taskViewModel.installLabel( + this, companyId, + binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), + binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", + "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), + binding.objectInclude.areaView.text.toString(), + binding.objectInclude.lineView.text.toString(), + binding.objectInclude.roadView.text.toString(), + binding.objectInclude.constructDateView.text.toString(), + binding.objectInclude.ownerView.text.toString(), + objectId, + binding.identifierInclude.identifierIdView.text.toString(), + binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), + "${binding.identifierInclude.identifierDeepView.text}mm", + binding.identifierInclude.personDeptView.text.toString(), + binding.identifierInclude.installTimeView.text.toString(), + binding.identifierInclude.lngView.text.toString(), + binding.identifierInclude.latView.text.toString(), + binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), + binding.remarkView.text.toString(), + realPaths + ) + + //保存默认值 + "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) + "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) + "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) + "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) + "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) + "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) + "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) + "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) + "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) + "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) + "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) + } + + //读标识器 + binding.readMarkerButton.setOnClickListener { + LoadingDialogHub.show(this, "标识器读取中,请稍后...") + countDownTimer.start() + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + outStream.write('2'.code) + outStream.flush() + + soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) + binding.readMarkerButton.isEnabled = false + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + Log.d(kTag, hex) + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + countDownTimer.cancel() + cancelLoadingView() + binding.readMarkerButton.isEnabled = true + binding.identifierInclude.identifierIdView.text = markerId + } + } + }) + } + } + + /** + * 搜索标识器超时倒计时 + * */ + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + cancelLoadingView() + "读取此标识器ID超时,请重试".show(context) + binding.readMarkerButton.isEnabled = true + } + } + + private fun cancelLoadingView() { + LoadingDialogHub.dismiss() + soundPool.autoPause() + serialPortService?.closeSerialPort() + } + + private fun saveMarkerInLocal() { + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + + val marker = MarkerLocalBean() + marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() + marker.pipelineType = binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() + marker.pipelineMaterial = + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() + marker.pipelineDiameter = "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" + marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" + marker.underlyingPipelineType = + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() + marker.underlyingPipelineMaterial = + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() + marker.underlyingPipelineDiameter = + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" + marker.underlyingPipelineDepth = + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" + marker.buryMethod = + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() + marker.area = binding.objectInclude.areaView.text.toString() + marker.line = binding.objectInclude.lineView.text.toString() + marker.road = binding.objectInclude.roadView.text.toString() + marker.constructTime = binding.objectInclude.constructDateView.text.toString() + marker.owner = binding.objectInclude.ownerView.text.toString() + marker.objectId = objectId + marker.markerId = binding.identifierInclude.identifierIdView.text.toString() + marker.markerType = binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() + marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" + marker.installationDept = binding.identifierInclude.personDeptView.text.toString() + marker.updateTime = binding.identifierInclude.installTimeView.text.toString() + marker.lng = binding.identifierInclude.lngView.text.toString() + marker.lat = binding.identifierInclude.latView.text.toString() + marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() + marker.remark = binding.remarkView.text.toString() + marker.imagePath = realPaths.toJson() + + DataBaseManager.get.saveMarkerInLocale(marker) + } + + private fun takePicture() { + PictureSelector.create(this).openCamera(SelectMimeType.ofImage()) + .forResult(object : OnResultCallbackListener { + override fun onResult(result: ArrayList?) { + if (result == null) { + "拍照失败,请重试".show(context) + return + } + analyticalSelectResults(result[0]) + } + + override fun onCancel() { + + } + }) + } + + private fun analyticalSelectResults(result: LocalMedia) { + //压缩图片 + result.realPath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + realPaths.add(file.absolutePath) + imageAdapter.setupImage(realPaths) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + //在连接正常关闭的情况下不会被调用, 只在Service被破坏了或者被杀死的时候调用 + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + locationTool.getCurrentLocation(true, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + if (location != null) { + binding.identifierInclude.lngView.text = location.longitude.toString() + binding.identifierInclude.latView.text = location.latitude.toString() + } else { + "当前位置信号差,无法获取定位".show(context) + } + } + }) + soundResourceId = soundPool.load(this, R.raw.ring3, 1) + + //初始化数据 + initDefaultData() + } + + override fun initViewBinding(): ActivityInstallMarkerBinding { + return ActivityInstallMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + taskViewModel.loadState.observe(this) { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "标识器安装中,请稍后...") + + LoadState.Success -> { + LoadingDialogHub.dismiss() + clearDefaultData() + "安装成功".show(this) + } + + else -> LoadingDialogHub.dismiss() + } + } + } + + override fun setupTopBarLayout() { + + } + + private fun initDefaultData() { + binding.titleInclude.titleView.text = "安装新标识器" + binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) + + imageAdapter = EditableImageAdapter(this, 3, 3) + binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter + + //设置默认值 + binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) + binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) + binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) + binding.objectInclude.areaView.setText("areaView".getDefaultValue()) + binding.objectInclude.lineView.setText("lineView".getDefaultValue()) + binding.objectInclude.roadView.setText("roadView".getDefaultValue()) + binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) + binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) + binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) + + /**************************************************************************************/ + binding.objectInclude.objectTypeSpinner.show(this, LocaleConstant.POINT_TYPE_ARRAY, 0) + binding.objectInclude.pipeInclude.materialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeAttachInclude.attachSpinner.show( + this, LocaleConstant.ATTACH_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeFeatureInclude.featureSpinner.show( + this, LocaleConstant.FEATURE_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeInclude.buryTypeSpinner.show( + this, LocaleConstant.BURY_METHOD_ARRAY, 0 + ) + binding.objectInclude.constructDateView.setOnClickListener { + val datePicker = DatePickerDialog( + this, null, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ) + datePicker.show() + + datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { + val year = datePicker.datePicker.year + val month = datePicker.datePicker.month + 1 + val day = datePicker.datePicker.dayOfMonth + val selectedDate = String.format( + "%s-%s-%s", year, month.appendZero(), day.appendZero() + ) + + //当前时间 + val current = System.currentTimeMillis().timestampToTime() + val today = "$selectedDate $current".dateToTimestamp() + if (Date(today).after(Date())) { + "建设年代不能早于当前日期".show(context) + } else { + datePicker.dismiss() + binding.objectInclude.constructDateView.text = selectedDate + } + } + } + binding.identifierInclude.identifierTypeSpinner.show( + this, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 + ) + binding.identifierInclude.installTimeView.text = + System.currentTimeMillis().timestampToCompleteDate() + binding.identifierInclude.colorSpinner.show(this, LocaleConstant.COLOR_ARRAY, 0) + + imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { + override fun onAddImageClick() { + takePicture() + } + + override fun onItemClick(position: Int) { + if (realPaths[position].isEmpty()) { + "图片加载失败,无法查看大图".show(context) + } else { + navigatePageTo(position, realPaths) + } + } + + override fun onItemLongClick(view: View?, position: Int) { + imageAdapter.deleteImage(position) + } + }) + } + + //清除默认数据 + private fun clearDefaultData() { + "markerObjectTypeView".setDefaultValue("") + "pipelineDiameterView".setDefaultValue("") + "buryDeepView".setDefaultValue("") + "bottomPipeDiameterView".setDefaultValue("") + "bottomPointDeepView".setDefaultValue("") + "areaView".setDefaultValue("") + "lineView".setDefaultValue("") + "roadView".setDefaultValue("") + "ownerView".setDefaultValue("") + "identifierDeepView".setDefaultValue("") + "personDeptView".setDefaultValue("") + } + + override fun onDestroy() { + super.onDestroy() + soundPool.autoPause() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt index 1dac658..f949269 100644 --- a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt +++ b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt @@ -1,31 +1,25 @@ package com.casic.detector.common.view -import android.app.DatePickerDialog -import android.app.Dialog +import android.content.ComponentName import android.content.Context -import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager import android.media.AudioAttributes import android.media.SoundPool import android.os.Bundle -import android.os.CountDownTimer +import android.os.IBinder import android.util.Log import android.view.KeyEvent import android.view.View -import android.widget.AdapterView import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions -import com.amap.api.maps.AMapUtils import com.amap.api.maps.CameraUpdateFactory import com.amap.api.maps.CoordinateConverter import com.amap.api.maps.model.BitmapDescriptorFactory @@ -34,38 +28,29 @@ import com.amap.api.maps.model.MarkerOptions import com.amap.api.maps.model.MyLocationStyle import com.casic.detector.common.R -import com.casic.detector.common.adapter.EditableImageAdapter -import com.casic.detector.common.base.SerialPortActivity +import com.casic.detector.common.base.BaseApplication import com.casic.detector.common.bean.MarkerLocalBean import com.casic.detector.common.bean.TaskLocalBean import com.casic.detector.common.callback.OnGetLocationListener -import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener import com.casic.detector.common.cluster.ClusterItem import com.casic.detector.common.cluster.ClusterOverlay import com.casic.detector.common.cluster.RegionItem import com.casic.detector.common.databinding.ActivityMainBinding -import com.casic.detector.common.databinding.DialogInstallMarkerBinding -import com.casic.detector.common.databinding.DialogSearchMarkerNewBinding import com.casic.detector.common.extensions.appendDownloadUrl -import com.casic.detector.common.extensions.compressImage import com.casic.detector.common.extensions.convertToGPGGA import com.casic.detector.common.extensions.createTaskCode import com.casic.detector.common.extensions.drawCircle -import com.casic.detector.common.extensions.getDefaultValue import com.casic.detector.common.extensions.hexToString import com.casic.detector.common.extensions.initImmersionBar import com.casic.detector.common.extensions.isNumber -import com.casic.detector.common.extensions.setDefaultValue -import com.casic.detector.common.extensions.show -import com.casic.detector.common.extensions.toColor import com.casic.detector.common.extensions.toHex -import com.casic.detector.common.extensions.toObjectType import com.casic.detector.common.model.TaskDetailLocalModel import com.casic.detector.common.model.TaskModel +import com.casic.detector.common.service.SerialPortService import com.casic.detector.common.utils.DataBaseManager import com.casic.detector.common.utils.ExcelTool import com.casic.detector.common.utils.FileType -import com.casic.detector.common.utils.GpioManager import com.casic.detector.common.utils.LocaleConstant import com.casic.detector.common.utils.LocationTool import com.casic.detector.common.utils.NtripAuthorizationCreator @@ -77,26 +62,15 @@ import com.casic.detector.common.vm.TaskViewModel import com.casic.detector.common.widgets.MarkerDetailDialog import com.casic.detector.common.widgets.QueryMarkerDialog -import com.casic.detector.common.widgets.RadarScanView import com.casic.detector.common.widgets.SamplePopupWindow -import com.luck.picture.lib.basic.PictureSelector -import com.luck.picture.lib.config.SelectMimeType -import com.luck.picture.lib.entity.LocalMedia -import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.extensions.appendZero -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertDrawable import com.pengxh.kt.lite.extensions.createDownloadFileDir -import com.pengxh.kt.lite.extensions.dateToTimestamp import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.getSystemService -import com.pengxh.kt.lite.extensions.initDialogLayoutParams import com.pengxh.kt.lite.extensions.isNetworkConnected import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToCompleteDate -import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.FileDownloadManager import com.pengxh.kt.lite.utils.LoadState @@ -108,60 +82,41 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import io.netty.buffer.Unpooled import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.io.OutputStream import java.nio.charset.StandardCharsets -import java.util.Calendar -import java.util.Date import java.util.Timer import java.util.TimerTask -import kotlin.math.atan2 -class MainActivity : SerialPortActivity(), SensorEventListener, - OnSocketConnectListener { +class MainActivity : KotlinBaseActivity(), OnSocketConnectListener { private val kTag = "MainActivity" private val context = this private val samplePopupWindow by lazy { SamplePopupWindow(this) } private val regionRadius by lazy { LocaleConstant.RADIUS_SIZE.dp2px(this) } private val backDrawables by lazy { HashMap() } - private val installDialog by lazy { InstallMarkerDialog(this) } - private val searchNewDialog by lazy { SearchMarkerNewDialog(this) } private val detailDialog by lazy { MarkerDetailDialog(this) } private val locationTool by lazy { LocationTool(this) } - private val rotationMatrix = FloatArray(9)//旋转矩阵缓存 - private val valueArray = FloatArray(3)//方位角数值 + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() private var clickTime: Long = 0 private var markers = ArrayList() private var clusterOverlay: ClusterOverlay? = null private var isFreeTask = false private var freeTaskTitle = "" private var ids = HashSet() - private var signalTask: TimerTask? = null - private var energyTask: TimerTask? = null - private var searchMarkerTimer: Timer? = null private var freeTaskId: String? = null - private var gravity: FloatArray? = null - private var geomagnetic: FloatArray? = null private var connectState = ConnectState.CLOSED private var socketClient: SocketClient? = null - private lateinit var aMap: AMap - private lateinit var sensorManager: SensorManager - - /***inner class 需要用到*****start*/ - private val gpioManager by lazy { GpioManager() } - private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } - private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() - private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var serialPortService: SerialPortService? = null + private var searchMarkerTimer: Timer? = null private var soundResourceId = 0 - private var slowSoundResourceId = 0 - private var fastSoundResourceId = 0 private var isExecuteTask = false - - /***inner class 需要用到*****end*/ + private lateinit var aMap: AMap override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) @@ -171,10 +126,26 @@ binding.rootView.initImmersionBar(this, false, R.color.themeColor) } + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + + } + } + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + soundResourceId = soundPool.load(this, R.raw.ring3, 1) - slowSoundResourceId = soundPool.load(this, R.raw.ring4, 1) - fastSoundResourceId = soundPool.load(this, R.raw.ring2, 1) RtkLocationTool.getCurrentLocation(this) { if (connectState == ConnectState.SUCCESS) { @@ -192,8 +163,6 @@ samplePopupWindow.setPopupMenuItem(LocaleConstant.POPUP_TITLES) samplePopupWindow.setBackgroundDrawable(null) - sensorManager = getSystemService()!! - //task网络请求监听 taskViewModel.markerFileResult.observe(this) { if (it.isSuccess) { @@ -316,9 +285,6 @@ //安装。上传,然后存入本地库 binding.installButton.setOnClickListener { - /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * */ if (isFreeTask) { AlertMessageDialog.Builder().setContext(this).setTitle("温馨提示") .setMessage("正在自由巡检任务中,请完成此次任务之后再使用此功能") @@ -329,7 +295,7 @@ } }).build().show() } else { - installDialog.show() + navigatePageTo() } } @@ -449,8 +415,6 @@ //探测 binding.detectionButton.setOnClickListener { /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * * 如果开启自由巡检就不让探测 * */ if (isFreeTask) { @@ -463,7 +427,12 @@ } }).build().show() } else { - searchNewDialog.show() + val flag = if (isExecuteTask) { + "1" + } else { + "0" + } + navigatePageTo(flag) } } @@ -476,14 +445,8 @@ override fun onConfirmClick() { isFreeTask = false soundPool.autoPause() - - //停止信号和ID搜索定时器 - signalTask?.cancel() - energyTask?.cancel() searchMarkerTimer?.cancel() - - //降低串口电位 - gpioManager.setGpioLow("18") + serialPortService?.closeSerialPort() binding.stopFreeTaskButton.visibility = View.GONE if (freeTaskId.isNullOrBlank()) { @@ -527,110 +490,79 @@ } private fun openSerialPort() { - //调高串口电位 - gpioManager.setGpioHigh("18") - - isFreeTask = true - //自由巡检 - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - searchMarkerTimer = Timer() - searchMarkerTimer?.apply { - schedule(signalTask, 0, 200) - schedule(energyTask, 0, 251) - } - binding.stopFreeTaskButton.visibility = View.VISIBLE - } + isFreeTask = true + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + searchMarkerTimer = Timer() + searchMarkerTimer?.schedule(object : TimerTask() { + override fun run() { + outStream.write('2'.code) + outStream.flush() - override fun onDataReceived(buffer: ByteArray) { - val hex = buffer.toHex() -// Log.d(kTag, "$kTag => $hex") - if (searchNewDialog.isDetectMarker) { - searchNewDialog.bindingValue(hex) - } else if (installDialog.isReadMarker) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - installDialog.bindingValue(markerId) + Thread.sleep(50) + + outStream.write('6'.code) + outStream.flush() + } + }, 0, 200) } - } else if (isFreeTask) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - //只响一次,因为探测频率高,所以依旧是连续的报警声 - soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) - //添加地图Marker - if (!ids.contains(markerId)) { - //根据markerId查询标识器经纬度 - val labels = DataBaseManager.get.queryMarkerById(markerId) - if (labels.isNotEmpty()) { - val bean = labels.first() - aMap.addMarker( - MarkerOptions().position( - LatLng(bean.lat.toDouble(), bean.lng.toDouble()) - ).icon(BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1)) - ) + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("4E")) { + //只响一次,因为探测频率高,所以依旧是连续的报警声 + soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) + + try { + val energyResponse = hex.take(10).hexToString() + val energy = energyResponse.substring(1).toInt() + if (energy <= 1500 && detailDialog.isShowing) { + detailDialog.dismiss() + } + }catch (e:NumberFormatException){ + e.printStackTrace() } } - ids.add(markerId) - //显示标识器详细信息 - if (!detailDialog.isShowing) { - val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() - if (markerBean == null) { - "无法查询到此ID【${markerId}】的信息".show(context) - } else { - detailDialog.setMarker(markerBean) - detailDialog.show() + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + //添加地图Marker + if (!ids.contains(markerId)) { + //根据markerId查询标识器经纬度 + val labels = DataBaseManager.get.queryMarkerById(markerId) + if (labels.isNotEmpty()) { + val bean = labels.first() + aMap.addMarker( + MarkerOptions().position( + LatLng(bean.lat.toDouble(), bean.lng.toDouble()) + ).icon( + BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1) + ) + ) + } + } + ids.add(markerId) + + //显示标识器详细信息 + if (!detailDialog.isShowing) { + val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() + if (markerBean == null) { + "无法查询到此ID【${markerId}】的信息".show(context) + } else { + detailDialog.setMarker(markerBean) + detailDialog.show() + } } } } - - if (hex.startsWith("4E")) { - val energyResponse = hex.take(10).hexToString() - try { - val energy = energyResponse.substring(1).toInt() - if (energy <= 500 && detailDialog.isShowing) { - detailDialog.dismiss() - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } - } + }) } override fun observeRequestState() { taskViewModel.loadState.observe(this) { when (it) { - LoadState.Loading -> { - if (installDialog.isInstallMarker) { - LoadingDialogHub.show(this, "标识器安装中,请稍后...") - } else { - LoadingDialogHub.show(this, "提交工单中,请稍后") - } - } - - LoadState.Success -> { - if (installDialog.isInstallMarker) { - installDialog.clearDefaultData() - installDialog.dismiss() - "${installDialog.markerId}安装成功".show(this) - } - LoadingDialogHub.dismiss() - } + LoadState.Loading -> LoadingDialogHub.show(this, "提交工单中,请稍后") else -> LoadingDialogHub.dismiss() } @@ -939,32 +871,6 @@ } else super.onKeyDown(keyCode, event) } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - //精度发生变化时触发 - } - - override fun onSensorChanged(event: SensorEvent?) { - //值发生变化时触发 - val type = event?.sensor?.type - - if (type == Sensor.TYPE_ACCELEROMETER) { - gravity = event.values - } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { - geomagnetic = event.values - } - - if (gravity == null || geomagnetic == null) { - return - } - - if (SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)) { - SensorManager.getOrientation(rotationMatrix, valueArray) - - val degree = ((360f + valueArray[0] * 180f / Math.PI) % 360).toInt() - searchNewDialog.updateDegreeValue(degree) - } - } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { @@ -978,14 +884,6 @@ showLabelsOnMap() } - //注册加速度传感器监听 - val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL) - - //注册磁场传感器监听 - val magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) - sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL) - //取缓存 val remoteHost = SaveKeyValues.getValue( LocaleConstant.RTK_SERVER, "203.107.45.154" @@ -995,20 +893,20 @@ ) as String //连接千寻RTK服务器 - lifecycleScope.launch(Dispatchers.IO) { - if (socketClient != null) { - socketClient?.disconnect() - delay(3000) - } - - socketClient = SocketClient.Builder() - .setHostname(remoteHost) - .setPort(remotePort.toInt()) - .setTimeout(5000) - .setOnSocketListener(this@MainActivity) - .build() - socketClient?.connect() - } +// lifecycleScope.launch(Dispatchers.IO) { +// if (socketClient != null) { +// socketClient?.disconnect() +// delay(3000) +// } +// +// socketClient = SocketClient.Builder() +// .setHostname(remoteHost) +// .setPort(remotePort.toInt()) +// .setTimeout(5000) +// .setOnSocketListener(this@MainActivity) +// .build() +// socketClient?.connect() +// } } override fun onMessageResponse(data: ByteArray) { @@ -1021,12 +919,12 @@ val result = String(data, StandardCharsets.UTF_8) Log.d(kTag, "onMessageResponse: $result") if (result.contains("ICY 200 OK")) { - "高精度定位服务器连接成功".show(this) + "高精度定位服务连接成功".show(this) } } else { "收到千寻数据返回,长度:${data.size}".show(this) - out.write(data) - out.flush() +// out.write(data) +// out.flush() } } @@ -1061,7 +959,6 @@ override fun onPause() { super.onPause() binding.mapView.onPause() - sensorManager.unregisterListener(this) } override fun onSaveInstanceState(outState: Bundle) { @@ -1072,794 +969,11 @@ override fun onDestroy() { super.onDestroy() binding.mapView.onDestroy() - soundPool.release() + soundPool.autoPause() + searchMarkerTimer?.cancel() + serialPortService?.closeSerialPort() locationTool.stopLocation() - //降低串口电位 - gpioManager.setGpioLow("18") + unbindService(serviceConnection) + BaseApplication.get().closeSerialPort() } - - /**安装标识器对话框******************************************************************************/ - inner class InstallMarkerDialog(private val context: Context) : Dialog(context) { - - private val binding: DialogInstallMarkerBinding by binding() - private val calendar by lazy { Calendar.getInstance() } - private val realPaths = ArrayList() //真实图片路径 - private lateinit var imageAdapter: EditableImageAdapter - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - - var isReadMarker = false - var isInstallMarker = false - var markerId = "" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - - //初始化数据 - initDefaultData() - - //返回 - binding.titleInclude.leftBackView.setOnClickListener { - soundPool.autoPause() - dismiss() - } - - binding.objectInclude.objectTypeSpinner.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, view: View?, position: Int, id: Long - ) { - when (position) { - 0 -> { - //显示管线属性 - binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 1 -> { - //显示管线附属物属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 2 -> { - //显示管线特征点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 3 -> { - //显示交叉穿越点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = - View.VISIBLE - } - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - - } - } - - //安装 - binding.installButton.setOnClickListener { - val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { - when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { - "管线" -> "请输入管线种类!".show(context) - "管线附属物" -> "请输入附属物名称!".show(context) - "管线特征管点" -> "请输入特征管点!".show(context) - "交叉穿越点" -> "请输入上层管种类!".show(context) - } - return@setOnClickListener - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { - if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { - "请输入管径".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { - "请输入埋深".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { - if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { - "请输入下层管管径".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.areaView.text.isNullOrBlank()) { - "请输入所属区域".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.lineView.text.isNullOrBlank()) { - "请输入所属线路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.roadView.text.isNullOrBlank()) { - "请输入所属道路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { - "请选择建设年代".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.ownerView.text.isNullOrBlank()) { - "请输入权属单位".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { - "请先读取标识器获取ID".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { - "请输入标识器埋深".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { - "请输入标识器安装部门".show(context) - return@setOnClickListener - } - - //先存本地再上传服务器 - saveMarkerInLocal() - - isInstallMarker = true - taskViewModel.installLabel( - context, - companyId, - binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", - "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), - binding.objectInclude.areaView.text.toString(), - binding.objectInclude.lineView.text.toString(), - binding.objectInclude.roadView.text.toString(), - binding.objectInclude.constructDateView.text.toString(), - binding.objectInclude.ownerView.text.toString(), - objectId, - binding.identifierInclude.identifierIdView.text.toString(), - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), - "${binding.identifierInclude.identifierDeepView.text}mm", - binding.identifierInclude.personDeptView.text.toString(), - binding.identifierInclude.installTimeView.text.toString(), - binding.identifierInclude.lngView.text.toString(), - binding.identifierInclude.latView.text.toString(), - binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), - binding.remarkView.text.toString(), - realPaths - ) - - //保存默认值 - "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) - "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) - "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) - "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) - "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) - "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) - "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) - "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) - "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) - "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) - "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) - } - - //读标识器 - binding.readMarkerButton.setOnClickListener { - LoadingDialogHub.show(this@MainActivity, "标识器读取中,请稍后...") - binding.readMarkerButton.isEnabled = false - - isReadMarker = true - - //调高串口电位 - gpioManager.setGpioHigh("18") - - soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) - - out.write('2'.code) - out.flush() - - countDownTimer = object : CountDownTimer(10 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - soundPool.autoPause() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - "读取此标识器ID超时,请退出应用再试".show(context) - } - } - countDownTimer.start() - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - locationTool.getCurrentLocation(true, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - if (location != null) { - binding.identifierInclude.lngView.text = location.longitude.toString() - binding.identifierInclude.latView.text = location.latitude.toString() - } else { - "当前位置信号差,无法获取定位".show(context) - } - } - }) - } - - fun bindingValue(markerId: String) { - this.markerId = markerId - LoadingDialogHub.dismiss() - soundPool.autoPause() - countDownTimer.cancel() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - binding.identifierInclude.identifierIdView.text = markerId - } - - override fun dismiss() { - //降低串口电位 - gpioManager.setGpioLow("18") - soundPool.autoPause() - isInstallMarker = false - locationTool.stopLocation() - super.dismiss() - } - - private fun initDefaultData() { - binding.titleInclude.titleView.text = "安装新标识器" - binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) - - imageAdapter = EditableImageAdapter(context, 3, 3) - binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter - - //设置默认值 - binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) - binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) - binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) - binding.objectInclude.areaView.setText("areaView".getDefaultValue()) - binding.objectInclude.lineView.setText("lineView".getDefaultValue()) - binding.objectInclude.roadView.setText("roadView".getDefaultValue()) - binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) - binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) - binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) - - /**************************************************************************************/ - binding.objectInclude.objectTypeSpinner.show( - this@MainActivity, LocaleConstant.POINT_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.materialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeAttachInclude.attachSpinner.show( - this@MainActivity, LocaleConstant.ATTACH_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeFeatureInclude.featureSpinner.show( - this@MainActivity, LocaleConstant.FEATURE_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.buryTypeSpinner.show( - this@MainActivity, LocaleConstant.BURY_METHOD_ARRAY, 0 - ) - - binding.objectInclude.constructDateView.setOnClickListener { - val datePicker = DatePickerDialog( - context, - null, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH) - ) - datePicker.show() - - datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { - val year = datePicker.datePicker.year - val month = datePicker.datePicker.month + 1 - val day = datePicker.datePicker.dayOfMonth - val selectedDate = String.format( - "%s-%s-%s", year, month.appendZero(), day.appendZero() - ) - - //当前时间 - val current = System.currentTimeMillis().timestampToTime() - val today = "$selectedDate $current".dateToTimestamp() - if (Date(today).after(Date())) { - "建设年代不能早于当前日期".show(context) - } else { - datePicker.dismiss() - binding.objectInclude.constructDateView.text = selectedDate - } - } - } - - binding.identifierInclude.identifierTypeSpinner.show( - this@MainActivity, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 - ) - - binding.identifierInclude.installTimeView.text = - System.currentTimeMillis().timestampToCompleteDate() - - binding.identifierInclude.colorSpinner.show( - this@MainActivity, LocaleConstant.COLOR_ARRAY, 0 - ) - - imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { - override fun onAddImageClick() { - takePicture() - } - - override fun onItemClick(position: Int) { - if (realPaths[position].isEmpty()) { - "图片加载失败,无法查看大图".show(context) - } else { - context.navigatePageTo(position, realPaths) - } - } - - override fun onItemLongClick(view: View?, position: Int) { - imageAdapter.deleteImage(position) - } - }) - /**************************************************************************************/ - } - - //清除默认数据 - fun clearDefaultData() { - "markerObjectTypeView".setDefaultValue("") - "pipelineDiameterView".setDefaultValue("") - "buryDeepView".setDefaultValue("") - "bottomPipeDiameterView".setDefaultValue("") - "bottomPointDeepView".setDefaultValue("") - "areaView".setDefaultValue("") - "lineView".setDefaultValue("") - "roadView".setDefaultValue("") - "ownerView".setDefaultValue("") - "identifierDeepView".setDefaultValue("") - "personDeptView".setDefaultValue("") - } - - private fun saveMarkerInLocal() { - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - - val marker = MarkerLocalBean() - marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() - marker.pipelineType = - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() - marker.pipelineMaterial = - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() - marker.pipelineDiameter = - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" - marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" - marker.underlyingPipelineType = - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() - marker.underlyingPipelineMaterial = - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() - marker.underlyingPipelineDiameter = - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" - marker.underlyingPipelineDepth = - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" - marker.buryMethod = - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() - marker.area = binding.objectInclude.areaView.text.toString() - marker.line = binding.objectInclude.lineView.text.toString() - marker.road = binding.objectInclude.roadView.text.toString() - marker.constructTime = binding.objectInclude.constructDateView.text.toString() - marker.owner = binding.objectInclude.ownerView.text.toString() - marker.objectId = objectId - marker.markerId = binding.identifierInclude.identifierIdView.text.toString() - marker.markerType = - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() - marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" - marker.installationDept = binding.identifierInclude.personDeptView.text.toString() - marker.updateTime = binding.identifierInclude.installTimeView.text.toString() - marker.lng = binding.identifierInclude.lngView.text.toString() - marker.lat = binding.identifierInclude.latView.text.toString() - marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() - marker.remark = binding.remarkView.text.toString() - marker.imagePath = realPaths.toJson() - - DataBaseManager.get.saveMarkerInLocale(marker) - } - - private fun takePicture() { - PictureSelector.create(this@MainActivity).openCamera(SelectMimeType.ofImage()) - .forResult(object : OnResultCallbackListener { - override fun onResult(result: java.util.ArrayList?) { - if (result == null) { - "拍照失败,请重试".show(context) - return - } - analyticalSelectResults(result[0]) - } - - override fun onCancel() { - - } - }) - } - - private fun analyticalSelectResults(result: LocalMedia) { - //压缩图片 - result.realPath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - realPaths.add(file.absolutePath) - imageAdapter.setupImage(realPaths) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - /**探测标识器新对话框****************************************************************************/ - inner class SearchMarkerNewDialog(private val context: Context) : Dialog(context) { - - private val taskId by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_ID, "") as String - } - private val taskCode by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_CODE, "") as String - } - private val markerPoints by lazy { ArrayList() } - private val binding: DialogSearchMarkerNewBinding by binding() - private var markerId = ""//实际探测出来的标识器ID - private var nearestMarkerId = ""//探测不到标识器的时候计算出来的最近的标识器ID - private lateinit var searchMarkerTimer: Timer - private lateinit var signalTask: TimerTask - private lateinit var energyTask: TimerTask - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - var isDetectMarker = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - binding.depthButton.setOnClickListener { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - - out.write('3'.code) - val result = DataBaseManager.get.queryMarkerById(markerId) - if (result.isNotEmpty()) { - val tag = when (result.first().markerType) { - "EM30" -> '7' - "EM50" -> '8' - "EM14" -> '9' - else -> '1' - } - if (tag == '1') { - "此标识器无法读取埋深!".show(context) - initTimer() - } else { - // 发送读取标识器埋设深度指令 - LoadingDialogHub.show(this@MainActivity, "正在测距,请稍后...") - out.write(tag.code) - out.flush() - countDownTimer = object : CountDownTimer(15 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - "探测此标识器深度超时,请重试".show(context) - initTimer() - } - } - countDownTimer.start() - } - } else { - "标识器未安装,安装成功后才可读取埋深!".show(context) - initTimer() - } - } - - binding.markerInfoButton.setOnClickListener { - val id = if (markerId == "") { - nearestMarkerId - } else { - markerId - } - //查库 - val result = DataBaseManager.get.queryMarkerById(id) - if (result.isNotEmpty()) { - context.navigatePageTo(result.first().toJson()) - } else { - context.navigatePageTo(id) - } - //查看完就把ID置空,便于下次查看最新的ID - markerId = "" - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - //点位渲染,每次定位都计算当前位置与符合条件的点距离 - locationTool.getCurrentLocation(false, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - location?.apply { - renderDataPoint(this) - } - } - }) - } - - /** - * 计算并渲染数据点。点太多采用协程计算,不然会有点卡顿 - * @param location 定位点(RTK获取) - * */ - private fun renderDataPoint(location: AMapLocation) { - val longitude = location.longitude - val latitude = location.latitude - lifecycleScope.launch(Dispatchers.IO) { - val dataPoints = ArrayList() - DataBaseManager.get.loadMarkers().forEach { - val distance = AMapUtils.calculateLineDistance( - LatLng(it.lat.toDouble(), it.lng.toDouble()), LatLng(latitude, longitude) - ) - val formatDistance = "%.2f".format(distance).toFloat() - - markerPoints.add(MarkerDistanceData(it.markerId, formatDistance)) - - if (formatDistance <= LocaleConstant.MAX_DISTANCE) { - val angle = atan2( - (it.lat.toDouble() - latitude), (it.lng.toDouble() - longitude) - ) + Math.PI - val formatAngle = "%.2f".format(angle).toDouble() - dataPoints.add( - RadarScanView.DataPoint(formatAngle, formatDistance) - ) - } - } - withContext(Dispatchers.Main) { - binding.radarScanView.renderPointData(dataPoints, - object : RadarScanView.OnGetNearestPointCallback { - override fun getNearestPoint(point: RadarScanView.DataPoint?) { - if (point == null) { - binding.distanceValueView.text = "大于5.5m" - binding.distancePgBar.progress = 100 - } else { - binding.distanceValueView.text = "${point.distance}m" - val progress = - if (point.distance > LocaleConstant.MAX_DISTANCE) { - 100 - } else { - (point.distance / LocaleConstant.MAX_DISTANCE) * 100 - } - binding.distancePgBar.progress = progress.toInt() - } - } - }) - } - } - } - - override fun onStart() { - super.onStart() - //调高串口电位 - gpioManager.setGpioHigh("18") - - initTimer() - - isDetectMarker = true - - if (isExecuteTask) { - binding.taskStateView.visibility = View.GONE - binding.taskStateView.isSelected = false - } else { - binding.taskStateView.visibility = View.VISIBLE - binding.taskStateView.isSelected = true - } - } - - fun bindingValue(hex: String) { - if (hex.startsWith("4E")) { - try { - //4E转为String为N,代表能量值 - //用能量值转动表盘 - val energyResponse = hex.take(10).hexToString() - val energy = energyResponse.substring(1).toInt() - if (energy >= 4000) { - soundPool.play(fastSoundResourceId, 1f, 1f, 0, 0, 1f) - } else { - soundPool.play(slowSoundResourceId, 1f, 1f, 0, 0, 1f) - } - - //通过设置进度条表示能量值 - binding.energyPgBar.progress = energy - binding.energyValueView.text = "${energy}dB" - - if (energy <= 700) {//18° - binding.energyTipsView.text = "信号较弱,可能距离较远" - binding.energyTipsView.setTextColor(Color.parseColor("#8D1717")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_red) - - binding.depthButton.isEnabled = false - binding.depthButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_disable) - binding.markerInfoButton.isEnabled = false - binding.markerInfoButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_disable) - - binding.searchResultView.text = "未检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#8D1717")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_red) - } else if (energy >= 4100) { - binding.energyTipsView.text = "信号极强,接近标识器正上方" - binding.energyTipsView.setTextColor(Color.parseColor("#428d00")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_green) - - if (markerPoints.isEmpty()) { - Log.d(kTag, "bindingValue: markerPoints.isEmpty()") - return - } - - //需要转一下,不然会有并发问题 - val temp = ArrayList() - temp.addAll(markerPoints) - temp.sortBy(MarkerDistanceData::distance) - val nearestPoint = temp.first() - nearestMarkerId = nearestPoint.markerId - handleMarker(nearestMarkerId) - } else { - binding.energyTipsView.text = "已靠近,请继续移动位置" - binding.energyTipsView.setTextColor(Color.parseColor("#8C5700")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_yellow) - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else if (hex.startsWith("53")) { - LoadingDialogHub.dismiss() - countDownTimer.cancel() - try { - val depthResponse = hex.take(10).hexToString() - val depth = depthResponse.drop(2).toInt() - AlertMessageDialog.Builder().setContext(context).setTitle("温馨提示") - .setMessage("标识器埋深:${depth}厘米").setPositiveButton("知道了") - .setOnDialogButtonClickListener(object : - AlertMessageDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - initTimer() - } - }).build().show() - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else { - val id = hex.take(20).hexToString() - if (id.isNumber()) { - markerId = id - handleMarker(markerId) - } - } - } - - private fun handleMarker(id: String) { - binding.depthButton.isEnabled = true - binding.depthButton.setTextColor(Color.WHITE) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_enable) - binding.markerInfoButton.isEnabled = true - binding.markerInfoButton.setTextColor(Color.WHITE) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_enable) - - binding.searchResultView.text = "已检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#428d00")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_green) - - //自动上传标识器 - if (isExecuteTask) { - val taskMarkerLocalBean = DataBaseManager.get.queryTaskMarkerById( - taskId, taskCode, id, "0" - ) - taskMarkerLocalBean?.apply { - taskViewModel.uploadMarker(context, this) - } - } - } - - //更新罗盘角度 - fun updateDegreeValue(degree: Int) { - binding.radarScanView.setDegreeValue(degree) - } - - private fun initTimer() { - searchMarkerTimer = Timer() - - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - //错开信号和能量定时器 - searchMarkerTimer.schedule(signalTask, 0, 201) - searchMarkerTimer.schedule(energyTask, 0, 251) - } - - override fun dismiss() { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - //降低串口电位 - gpioManager.setGpioLow("18") - isDetectMarker = false - soundPool.autoPause() - locationTool.stopLocation() - super.dismiss() - } - } - - /** - * 标识器与当前定位的数据 - * */ - data class MarkerDistanceData(var markerId: String, var distance: Float) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt new file mode 100644 index 0000000..14fe667 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt @@ -0,0 +1,462 @@ +package com.casic.detector.common.view + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.graphics.Color +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.view.WindowManager +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMapUtils +import com.amap.api.maps.model.LatLng +import com.casic.detector.common.R +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivitySearchMarkerBinding +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.model.MarkerDistanceData +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.casic.detector.common.widgets.RadarScanView +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.getSystemService +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.Constant +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.widget.dialog.AlertMessageDialog +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.OutputStream +import java.util.Timer +import java.util.TimerTask +import kotlin.math.atan2 + +class SearchMarkerActivity : KotlinBaseActivity(), + SensorEventListener { + + private val kTag = "SearchMarkerActivity" + private val context = this + private val locationTool by lazy { LocationTool(this) } + private val markerPoints by lazy { ArrayList() } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val sensorManager by lazy { getSystemService()!! } + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private val rotationMatrix = FloatArray(9)//旋转矩阵缓存 + private val valueArray = FloatArray(3)//方位角数值 + private var slowSoundResourceId = 0 + private var fastSoundResourceId = 0 + private var markerId = ""//实际探测出来的标识器ID + private var nearestMarkerId = ""//探测不到标识器的时候计算出来的最近的标识器ID + private var isExecuteTask = false + private var signalEnergy = 0 //标识器信号强度 + private var gravity: FloatArray? = null + private var geomagnetic: FloatArray? = null + private var serialPortService: SerialPortService? = null + private lateinit var searchMarkerTimer: Timer + private lateinit var searchSignalEnergyTimer: Timer + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + searchMarker() + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + val flag = intent.getStringExtra(Constant.INTENT_PARAM) as String + isExecuteTask = flag != "0" + + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + slowSoundResourceId = soundPool.load(this, R.raw.ring4, 1) + fastSoundResourceId = soundPool.load(this, R.raw.ring2, 1) + + //点位渲染,每次定位都计算当前位置与符合条件的点距离 + locationTool.getCurrentLocation(false, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + location?.apply { + renderDataPoint(this) + } + } + }) + + if (isExecuteTask) { + binding.taskStateView.visibility = View.GONE + binding.taskStateView.isSelected = false + } else { + binding.taskStateView.visibility = View.VISIBLE + binding.taskStateView.isSelected = true + } + + //在标识器信号强度很强的时候,从数据库中计算出距离最近的点,以防出现探测不到ID的情况 + searchSignalEnergyTimer = Timer() + searchSignalEnergyTimer.schedule(object : TimerTask() { + override fun run() { + //markerId为空,说明没有通过探测得到标识器ID,那么就需要通过计算得到近似的标识器 + if (signalEnergy >= 4100 && markerPoints.isNotEmpty()) { + try { + //需要转一下,不然会有并发问题 + val temp = ArrayList() + temp.addAll(markerPoints) + temp.sortBy(MarkerDistanceData::distance) + val nearestPoint = temp.first() + nearestMarkerId = nearestPoint.markerId + runOnUiThread { + handleMarker(nearestMarkerId) + } + markerPoints.clear() + } catch (e: NullPointerException) { + e.printStackTrace() + } + } + } + }, 0, 2000) + } + + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + serialPortService?.closeSerialPort() + "标识器深度探测超时,请移动位置重试".show(context) + searchMarker() + } + } + + override fun initEvent() { + binding.depthButton.setOnClickListener { + stopSearchMarker() + val result = DataBaseManager.get.queryMarkerById(markerId) + if (result.isNotEmpty()) { + val tag = when (result.first().markerType) { + "EM30" -> '7' + "EM50" -> '8' + "EM14" -> '9' + else -> '1' + } + if (tag == '1') { + "此标识器无法读取埋深!".show(this) + searchMarker() + } else { + LoadingDialogHub.show(this, "正在探测标识器埋深,请稍后...") + countDownTimer.start() + // 发送读取标识器埋设深度指令 + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + // 发送读取标识器埋设深度指令 + outStream.write(tag.code) + outStream.flush() + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("53")) { + LoadingDialogHub.dismiss() + countDownTimer.cancel() + try { + val depthResponse = hex.take(10).hexToString() + val depth = depthResponse.drop(2).toInt() + AlertMessageDialog.Builder().setContext(context) + .setTitle("温馨提示") + .setMessage("标识器埋深:${depth}厘米") + .setPositiveButton("知道了") + .setOnDialogButtonClickListener(object : + AlertMessageDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + serialPortService?.closeSerialPort() + searchMarker() + } + }).build().show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } catch (e: NumberFormatException) { + e.printStackTrace() + } + } + } + }) + } + } else { + LoadingDialogHub.dismiss() + "标识器未安装,安装成功后才可读取埋深!".show(this) + searchMarker() + } + } + + binding.markerInfoButton.setOnClickListener { + val id = if (markerId == "") { + nearestMarkerId + } else { + markerId + } + //查库 + val result = DataBaseManager.get.queryMarkerById(id) + if (result.isNotEmpty()) { + navigatePageTo(result.first().toJson()) + } else { + navigatePageTo(id) + } + //查看完就把ID置空,便于下次查看最新的ID + markerId = "" + } + } + + /** + * 搜索标识器 + * */ + private fun searchMarker() { + Thread.sleep(100) + + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + searchMarkerTimer = Timer() + searchMarkerTimer.schedule(object : TimerTask() { + override fun run() { + outStream.write('2'.code) + outStream.flush() + + Thread.sleep(50) + + outStream.write('6'.code) + outStream.flush() + } + }, 0, 200) + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("4E")) { + try { + //4E转为String为N,代表能量值 + //用能量值转动表盘 + val energyResponse = hex.take(10).hexToString() + signalEnergy = energyResponse.substring(1).toInt() + if (signalEnergy >= 4000) { + soundPool.play(fastSoundResourceId, 1f, 1f, 0, 0, 1f) + } else { + soundPool.play(slowSoundResourceId, 1f, 1f, 0, 0, 1f) + } + + //通过设置进度条表示能量值 + binding.energyPgBar.progress = signalEnergy + binding.energyValueView.text = "${signalEnergy}dB" + + //根据信号强度更新界面 + if (signalEnergy <= 700) {//18° + binding.energyTipsView.text = "信号较弱,可能距离较远" + binding.energyTipsView.setTextColor(Color.parseColor("#8D1717")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_red) + + binding.depthButton.isEnabled = false + binding.depthButton.setTextColor(Color.parseColor("#CCCCCC")) + binding.depthButton.setBackgroundResource(R.mipmap.left_button_disable) + binding.markerInfoButton.isEnabled = false + binding.markerInfoButton.setTextColor(Color.parseColor("#CCCCCC")) + binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_disable) + + binding.searchResultView.text = "未检测到标识器" + binding.searchResultView.setTextColor(Color.parseColor("#8D1717")) + binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_red) + } else if (signalEnergy >= 4100) { + binding.energyTipsView.text = "信号极强,接近标识器正上方" + binding.energyTipsView.setTextColor(Color.parseColor("#428d00")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_green) + } else { + binding.energyTipsView.text = "已靠近,请继续移动位置" + binding.energyTipsView.setTextColor(Color.parseColor("#8C5700")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_yellow) + } + } catch (e: NumberFormatException) { + e.printStackTrace() + } + } else { + val id = hex.take(20).hexToString() + if (id.isNumber()) { + markerId = id + handleMarker(markerId) + } + } + } + }) + } + + private fun stopSearchMarker() { + soundPool.autoPause() + searchMarkerTimer.cancel() + serialPortService?.closeSerialPort() + + Thread.sleep(100) + } + + /** + * 计算并渲染数据点。点太多采用协程计算,不然会有点卡顿。默认2s计算一次 + * @param location 定位点(RTK获取) + * */ + private fun renderDataPoint(location: AMapLocation) { + val longitude = location.longitude + val latitude = location.latitude + lifecycleScope.launch(Dispatchers.IO) { + val dataPoints = ArrayList() + DataBaseManager.get.loadMarkers().forEach { + val distance = AMapUtils.calculateLineDistance( + LatLng(it.lat.toDouble(), it.lng.toDouble()), LatLng(latitude, longitude) + ) + val formatDistance = "%.2f".format(distance).toFloat() + markerPoints.add(MarkerDistanceData(it.markerId, formatDistance)) + + if (formatDistance <= LocaleConstant.MAX_DISTANCE) { + val angle = atan2( + (it.lat.toDouble() - latitude), (it.lng.toDouble() - longitude) + ) + Math.PI + val formatAngle = "%.2f".format(angle).toDouble() + + dataPoints.add(RadarScanView.DataPoint(formatAngle, formatDistance)) + } + } + withContext(Dispatchers.Main) { + binding.radarScanView.renderPointData(dataPoints, + object : RadarScanView.OnGetNearestPointCallback { + override fun getNearestPoint(point: RadarScanView.DataPoint?) { + if (point == null) { + binding.distanceValueView.text = "大于5.5m" + binding.distancePgBar.progress = 100 + } else { + binding.distanceValueView.text = "${point.distance}m" + val progress = if (point.distance > LocaleConstant.MAX_DISTANCE) { + 100 + } else { + (point.distance / LocaleConstant.MAX_DISTANCE) * 100 + } + binding.distancePgBar.progress = progress.toInt() + } + } + } + ) + } + } + } + + override fun initViewBinding(): ActivitySearchMarkerBinding { + return ActivitySearchMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + private fun handleMarker(id: String) { + binding.depthButton.isEnabled = true + binding.depthButton.setTextColor(Color.WHITE) + binding.depthButton.setBackgroundResource(R.mipmap.left_button_enable) + binding.markerInfoButton.isEnabled = true + binding.markerInfoButton.setTextColor(Color.WHITE) + binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_enable) + + binding.searchResultView.text = "已检测到标识器" + binding.searchResultView.setTextColor(Color.parseColor("#428d00")) + binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_green) + + //自动上传标识器 + if (isExecuteTask) { + val taskId = SaveKeyValues.getValue(LocaleConstant.TASK_ID, "") as String + val taskCode = SaveKeyValues.getValue(LocaleConstant.TASK_CODE, "") as String + val taskMarkerLocalBean = DataBaseManager.get.queryTaskMarkerById( + taskId, taskCode, id, "0" + ) + taskMarkerLocalBean?.apply { + taskViewModel.uploadMarker(context, this) + } + } + } + + override fun onResume() { + super.onResume() + //注册加速度传感器监听 + val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL) + + //注册磁场传感器监听 + val magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) + sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL) + } + + override fun onPause() { + super.onPause() + sensorManager.unregisterListener(this) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + //精度发生变化时触发 + } + + override fun onSensorChanged(event: SensorEvent?) { + //值发生变化时触发 + val type = event?.sensor?.type + + if (type == Sensor.TYPE_ACCELEROMETER) { + gravity = event.values + } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { + geomagnetic = event.values + } + + if (gravity == null || geomagnetic == null) { + return + } + + if (SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)) { + SensorManager.getOrientation(rotationMatrix, valueArray) + + val degree = ((360f + valueArray[0] * 180f / Math.PI) % 360).toInt() + //更新罗盘角度 + binding.radarScanView.setDegreeValue(degree) + } + } + + override fun onDestroy() { + super.onDestroy() + stopSearchMarker() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89fd3c9..81f1347 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,8 @@ + + + + diff --git a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt index 458f3fb..ca7de13 100644 --- a/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt +++ b/app/src/main/java/com/casic/detector/common/base/BaseApplication.kt @@ -4,6 +4,7 @@ import com.casic.detector.common.greendao.DaoMaster import com.casic.detector.common.greendao.DaoSession import com.casic.detector.common.uart.SerialPort +import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.SaveKeyValues import java.io.File import java.io.IOException @@ -13,23 +14,12 @@ class BaseApplication : Application() { private val kTag = "BaseApplication" - private var serialPorts: ArrayList? = null + private var serialPorts = ArrayList() - @Throws(SecurityException::class, IOException::class, InvalidParameterException::class) - fun getSerialPorts(): ArrayList? { - serialPorts = ArrayList() - /** - * Open the serial port - * */ - serialPorts?.apply { - add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) - add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) - } - return serialPorts - } + fun getSerialPorts(): ArrayList = serialPorts fun closeSerialPort() { - serialPorts?.forEach { + serialPorts.forEach { it.closeSerialPort() } } @@ -49,6 +39,22 @@ val devOpenHelper = DaoMaster.DevOpenHelper(this, "Detector_Common.db", null) val daoMaster = DaoMaster(devOpenHelper.writableDatabase) daoSession = daoMaster.newSession() + + /** + * Open the serial port + * */ + try { + serialPorts.apply { + add(SerialPort(File("/dev/ttysWK1"), 9600, 0)) + add(SerialPort(File("/dev/ttysWK2"), 9600, 0)) + } + } catch (e: SecurityException) { + "您没有串口的读写权限!".show(this) + } catch (e: IOException) { + "因为不明原因,串口无法打开!".show(this) + } catch (e: InvalidParameterException) { + "请检查串口!".show(this) + } } fun getDaoSession(): DaoSession { diff --git a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt b/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt deleted file mode 100644 index 297ac02..0000000 --- a/app/src/main/java/com/casic/detector/common/base/SerialPortActivity.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.casic.detector.common.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope -import androidx.viewbinding.ViewBinding -import com.pengxh.kt.lite.extensions.show -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.OutputStream -import java.security.InvalidParameterException - - -abstract class SerialPortActivity : AppCompatActivity() { - - protected lateinit var binding: VB - - lateinit var out: OutputStream - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = initViewBinding() - setContentView(binding.root) - setupTopBarLayout() - initOnCreate(savedInstanceState) - observeRequestState() - initEvent() - - try { - val serialPorts = BaseApplication.get().getSerialPorts() - //读 - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[0].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - lifecycleScope.launch(Dispatchers.IO) { - serialPorts?.apply { - val stream = this[1].inputStream - while (isActive) { - try { - val buffer = ByteArray(64) - val size = stream.read(buffer) - if (size > 0) { - withContext(Dispatchers.Main) { - onDataReceived(buffer) - } - } - } catch (e: IOException) { - e.printStackTrace() - } - } - } - } - - //写 - serialPorts?.apply { - out = this[0].outputStream - } - } catch (e: SecurityException) { - "您没有串口的读写权限!".show(this) - } catch (e: IOException) { - "因为不明原因,串口无法打开!".show(this) - } catch (e: InvalidParameterException) { - "请检查串口!".show(this) - } - } - - /** - * 初始化ViewBinding - */ - abstract fun initViewBinding(): VB - - /** - * 特定页面定制沉浸式状态栏 - */ - abstract fun setupTopBarLayout() - - /** - * 初始化默认数据 - */ - abstract fun initOnCreate(savedInstanceState: Bundle?) - - /** - * 数据请求状态监听 - */ - abstract fun observeRequestState() - - /** - * 初始化业务逻辑 - */ - abstract fun initEvent() - - /** - * 串口读数,已经切回主线程 - * */ - abstract fun onDataReceived(buffer: ByteArray) - - override fun onDestroy() { - super.onDestroy() - BaseApplication.get().closeSerialPort() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt new file mode 100644 index 0000000..b06a068 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/callback/OnSerialPortDataListener.kt @@ -0,0 +1,9 @@ +package com.casic.detector.common.callback + +import java.io.OutputStream + +interface OnSerialPortDataListener { + fun write(outStream: OutputStream) + + fun onDataReceived(buffer: ByteArray) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt new file mode 100644 index 0000000..b1685d5 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/model/MarkerDistanceData.kt @@ -0,0 +1,6 @@ +package com.casic.detector.common.model + +/** + * 标识器与当前定位的数据 + * */ +data class MarkerDistanceData(var markerId: String, var distance: Float) diff --git a/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt new file mode 100644 index 0000000..090f604 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/service/SerialPortService.kt @@ -0,0 +1,104 @@ +package com.casic.detector.common.service + +import android.app.Service +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.lifecycleScope +import com.casic.detector.common.base.BaseApplication +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.utils.GpioManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.IOException + +class SerialPortService : Service(), LifecycleOwner { + + private val kTag = "SerialPortService" + private val registry = LifecycleRegistry(this) + private val gpioManager by lazy { GpioManager() } + private val serialPorts by lazy { BaseApplication.get().getSerialPorts() } + private var gpioState = "" + + override fun getLifecycle(): Lifecycle { + return registry + } + + override fun onBind(intent: Intent?): IBinder { + return ServiceBinder() + } + + inner class ServiceBinder : Binder() { + fun getSerialPortService(): SerialPortService { + return this@SerialPortService + } + } + + fun openSerialPort(listener: OnSerialPortDataListener) { + //调高串口电位 + gpioManager.setGpioHigh("18") + gpioState = "1" + Log.d(kTag, "openSerialPort: 调高串口电位") + + Thread.sleep(100) + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + //写 + listener.write(this[0].outputStream) + val stream = this[0].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + lifecycleScope.launch(Dispatchers.IO) { + serialPorts.apply { + val stream = this[1].inputStream + //读 + while (isActive) { + try { + val buffer = ByteArray(64) + val size = stream.read(buffer) + if (size > 0) { + withContext(Dispatchers.Main) { + if (gpioState == "1") { + listener.onDataReceived(buffer) + } + } + } + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + } + + fun closeSerialPort() { + //降低串口电位 + gpioManager.setGpioLow("18") + gpioState = "0" + Log.d(kTag, "closeSerialPort: 降低串口电位") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt new file mode 100644 index 0000000..b50aaaf --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/InstallMarkerActivity.kt @@ -0,0 +1,548 @@ +package com.casic.detector.common.view + +import android.app.DatePickerDialog +import android.content.ComponentName +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.widget.AdapterView +import androidx.lifecycle.ViewModelProvider +import com.amap.api.location.AMapLocation +import com.casic.detector.common.R +import com.casic.detector.common.adapter.EditableImageAdapter +import com.casic.detector.common.bean.MarkerLocalBean +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivityInstallMarkerBinding +import com.casic.detector.common.extensions.compressImage +import com.casic.detector.common.extensions.getDefaultValue +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.setDefaultValue +import com.casic.detector.common.extensions.show +import com.casic.detector.common.extensions.toColor +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.extensions.toObjectType +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.interfaces.OnResultCallbackListener +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.appendZero +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.extensions.dateToTimestamp +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.timestampToCompleteDate +import com.pengxh.kt.lite.extensions.timestampToTime +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.LoadState +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import java.io.File +import java.io.OutputStream +import java.util.Calendar +import java.util.Date + +class InstallMarkerActivity : KotlinBaseActivity() { + + private val kTag = "InstallMarkerActivity" + private val context = this + private val calendar by lazy { Calendar.getInstance() } + private val locationTool by lazy { LocationTool(this) } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val realPaths = ArrayList() //真实图片路径 + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var soundResourceId = 0 + private var serialPortService: SerialPortService? = null + private lateinit var imageAdapter: EditableImageAdapter + + override fun initEvent() { + //返回 + binding.titleInclude.leftBackView.setOnClickListener { + soundPool.autoPause() + finish() + } + + binding.objectInclude.objectTypeSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, view: View?, position: Int, id: Long + ) { + when (position) { + 0 -> { + //显示管线属性 + binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 1 -> { + //显示管线附属物属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 2 -> { + //显示管线特征点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.VISIBLE + binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE + } + + 3 -> { + //显示交叉穿越点属性 + binding.objectInclude.pipeInclude.root.visibility = View.GONE + binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE + binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE + binding.objectInclude.pipeCrossInclude.root.visibility = View.VISIBLE + } + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + + } + } + + //安装 + binding.installButton.setOnClickListener { + val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { + when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { + "管线" -> "请输入管线种类!".show(this) + "管线附属物" -> "请输入附属物名称!".show(this) + "管线特征管点" -> "请输入特征管点!".show(this) + "交叉穿越点" -> "请输入上层管种类!".show(this) + } + return@setOnClickListener + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { + if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { + "请输入管径".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { + "请输入埋深".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { + if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { + "请输入下层管管径".show(this) + return@setOnClickListener + } + } + + if (binding.objectInclude.areaView.text.isNullOrBlank()) { + "请输入所属区域".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.lineView.text.isNullOrBlank()) { + "请输入所属线路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.roadView.text.isNullOrBlank()) { + "请输入所属道路".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { + "请选择建设年代".show(this) + return@setOnClickListener + } + + if (binding.objectInclude.ownerView.text.isNullOrBlank()) { + "请输入权属单位".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { + "请先读取标识器获取ID".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { + "请输入标识器埋深".show(this) + return@setOnClickListener + } + + if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { + "请输入标识器安装部门".show(this) + return@setOnClickListener + } + + //先存本地再上传服务器 + saveMarkerInLocal() + + taskViewModel.installLabel( + this, companyId, + binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), + binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", + "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), + binding.objectInclude.areaView.text.toString(), + binding.objectInclude.lineView.text.toString(), + binding.objectInclude.roadView.text.toString(), + binding.objectInclude.constructDateView.text.toString(), + binding.objectInclude.ownerView.text.toString(), + objectId, + binding.identifierInclude.identifierIdView.text.toString(), + binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), + "${binding.identifierInclude.identifierDeepView.text}mm", + binding.identifierInclude.personDeptView.text.toString(), + binding.identifierInclude.installTimeView.text.toString(), + binding.identifierInclude.lngView.text.toString(), + binding.identifierInclude.latView.text.toString(), + binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), + binding.remarkView.text.toString(), + realPaths + ) + + //保存默认值 + "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) + "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) + "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) + "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) + "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) + "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) + "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) + "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) + "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) + "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) + "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) + } + + //读标识器 + binding.readMarkerButton.setOnClickListener { + LoadingDialogHub.show(this, "标识器读取中,请稍后...") + countDownTimer.start() + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + outStream.write('2'.code) + outStream.flush() + + soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) + binding.readMarkerButton.isEnabled = false + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + Log.d(kTag, hex) + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + countDownTimer.cancel() + cancelLoadingView() + binding.readMarkerButton.isEnabled = true + binding.identifierInclude.identifierIdView.text = markerId + } + } + }) + } + } + + /** + * 搜索标识器超时倒计时 + * */ + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + cancelLoadingView() + "读取此标识器ID超时,请重试".show(context) + binding.readMarkerButton.isEnabled = true + } + } + + private fun cancelLoadingView() { + LoadingDialogHub.dismiss() + soundPool.autoPause() + serialPortService?.closeSerialPort() + } + + private fun saveMarkerInLocal() { + val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String + + val marker = MarkerLocalBean() + marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() + marker.pipelineType = binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() + marker.pipelineMaterial = + binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() + marker.pipelineDiameter = "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" + marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" + marker.underlyingPipelineType = + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() + marker.underlyingPipelineMaterial = + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() + marker.underlyingPipelineDiameter = + "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" + marker.underlyingPipelineDepth = + "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" + marker.buryMethod = + binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() + marker.area = binding.objectInclude.areaView.text.toString() + marker.line = binding.objectInclude.lineView.text.toString() + marker.road = binding.objectInclude.roadView.text.toString() + marker.constructTime = binding.objectInclude.constructDateView.text.toString() + marker.owner = binding.objectInclude.ownerView.text.toString() + marker.objectId = objectId + marker.markerId = binding.identifierInclude.identifierIdView.text.toString() + marker.markerType = binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() + marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" + marker.installationDept = binding.identifierInclude.personDeptView.text.toString() + marker.updateTime = binding.identifierInclude.installTimeView.text.toString() + marker.lng = binding.identifierInclude.lngView.text.toString() + marker.lat = binding.identifierInclude.latView.text.toString() + marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() + marker.remark = binding.remarkView.text.toString() + marker.imagePath = realPaths.toJson() + + DataBaseManager.get.saveMarkerInLocale(marker) + } + + private fun takePicture() { + PictureSelector.create(this).openCamera(SelectMimeType.ofImage()) + .forResult(object : OnResultCallbackListener { + override fun onResult(result: ArrayList?) { + if (result == null) { + "拍照失败,请重试".show(context) + return + } + analyticalSelectResults(result[0]) + } + + override fun onCancel() { + + } + }) + } + + private fun analyticalSelectResults(result: LocalMedia) { + //压缩图片 + result.realPath.compressImage(context, object : OnImageCompressListener { + override fun onSuccess(file: File) { + realPaths.add(file.absolutePath) + imageAdapter.setupImage(realPaths) + } + + override fun onError(e: Throwable) { + e.printStackTrace() + } + }) + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + //在连接正常关闭的情况下不会被调用, 只在Service被破坏了或者被杀死的时候调用 + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + locationTool.getCurrentLocation(true, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + if (location != null) { + binding.identifierInclude.lngView.text = location.longitude.toString() + binding.identifierInclude.latView.text = location.latitude.toString() + } else { + "当前位置信号差,无法获取定位".show(context) + } + } + }) + soundResourceId = soundPool.load(this, R.raw.ring3, 1) + + //初始化数据 + initDefaultData() + } + + override fun initViewBinding(): ActivityInstallMarkerBinding { + return ActivityInstallMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + taskViewModel.loadState.observe(this) { + when (it) { + LoadState.Loading -> LoadingDialogHub.show(this, "标识器安装中,请稍后...") + + LoadState.Success -> { + LoadingDialogHub.dismiss() + clearDefaultData() + "安装成功".show(this) + } + + else -> LoadingDialogHub.dismiss() + } + } + } + + override fun setupTopBarLayout() { + + } + + private fun initDefaultData() { + binding.titleInclude.titleView.text = "安装新标识器" + binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) + + imageAdapter = EditableImageAdapter(this, 3, 3) + binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter + + //设置默认值 + binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) + binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) + binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) + binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) + binding.objectInclude.areaView.setText("areaView".getDefaultValue()) + binding.objectInclude.lineView.setText("lineView".getDefaultValue()) + binding.objectInclude.roadView.setText("roadView".getDefaultValue()) + binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) + binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) + binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) + + /**************************************************************************************/ + binding.objectInclude.objectTypeSpinner.show(this, LocaleConstant.POINT_TYPE_ARRAY, 0) + binding.objectInclude.pipeInclude.materialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeAttachInclude.attachSpinner.show( + this, LocaleConstant.ATTACH_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeFeatureInclude.featureSpinner.show( + this, LocaleConstant.FEATURE_NAME_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( + this, LocaleConstant.PIPE_TYPE_ARRAY, 0 + ) + binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( + this, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 + ) + binding.objectInclude.pipeInclude.buryTypeSpinner.show( + this, LocaleConstant.BURY_METHOD_ARRAY, 0 + ) + binding.objectInclude.constructDateView.setOnClickListener { + val datePicker = DatePickerDialog( + this, null, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ) + datePicker.show() + + datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { + val year = datePicker.datePicker.year + val month = datePicker.datePicker.month + 1 + val day = datePicker.datePicker.dayOfMonth + val selectedDate = String.format( + "%s-%s-%s", year, month.appendZero(), day.appendZero() + ) + + //当前时间 + val current = System.currentTimeMillis().timestampToTime() + val today = "$selectedDate $current".dateToTimestamp() + if (Date(today).after(Date())) { + "建设年代不能早于当前日期".show(context) + } else { + datePicker.dismiss() + binding.objectInclude.constructDateView.text = selectedDate + } + } + } + binding.identifierInclude.identifierTypeSpinner.show( + this, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 + ) + binding.identifierInclude.installTimeView.text = + System.currentTimeMillis().timestampToCompleteDate() + binding.identifierInclude.colorSpinner.show(this, LocaleConstant.COLOR_ARRAY, 0) + + imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { + override fun onAddImageClick() { + takePicture() + } + + override fun onItemClick(position: Int) { + if (realPaths[position].isEmpty()) { + "图片加载失败,无法查看大图".show(context) + } else { + navigatePageTo(position, realPaths) + } + } + + override fun onItemLongClick(view: View?, position: Int) { + imageAdapter.deleteImage(position) + } + }) + } + + //清除默认数据 + private fun clearDefaultData() { + "markerObjectTypeView".setDefaultValue("") + "pipelineDiameterView".setDefaultValue("") + "buryDeepView".setDefaultValue("") + "bottomPipeDiameterView".setDefaultValue("") + "bottomPointDeepView".setDefaultValue("") + "areaView".setDefaultValue("") + "lineView".setDefaultValue("") + "roadView".setDefaultValue("") + "ownerView".setDefaultValue("") + "identifierDeepView".setDefaultValue("") + "personDeptView".setDefaultValue("") + } + + override fun onDestroy() { + super.onDestroy() + soundPool.autoPause() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt index 1dac658..f949269 100644 --- a/app/src/main/java/com/casic/detector/common/view/MainActivity.kt +++ b/app/src/main/java/com/casic/detector/common/view/MainActivity.kt @@ -1,31 +1,25 @@ package com.casic.detector.common.view -import android.app.DatePickerDialog -import android.app.Dialog +import android.content.ComponentName import android.content.Context -import android.content.DialogInterface +import android.content.Intent +import android.content.ServiceConnection import android.graphics.BitmapFactory import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager import android.media.AudioAttributes import android.media.SoundPool import android.os.Bundle -import android.os.CountDownTimer +import android.os.IBinder import android.util.Log import android.view.KeyEvent import android.view.View -import android.widget.AdapterView import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.amap.api.location.AMapLocation import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions -import com.amap.api.maps.AMapUtils import com.amap.api.maps.CameraUpdateFactory import com.amap.api.maps.CoordinateConverter import com.amap.api.maps.model.BitmapDescriptorFactory @@ -34,38 +28,29 @@ import com.amap.api.maps.model.MarkerOptions import com.amap.api.maps.model.MyLocationStyle import com.casic.detector.common.R -import com.casic.detector.common.adapter.EditableImageAdapter -import com.casic.detector.common.base.SerialPortActivity +import com.casic.detector.common.base.BaseApplication import com.casic.detector.common.bean.MarkerLocalBean import com.casic.detector.common.bean.TaskLocalBean import com.casic.detector.common.callback.OnGetLocationListener -import com.casic.detector.common.callback.OnImageCompressListener +import com.casic.detector.common.callback.OnSerialPortDataListener import com.casic.detector.common.cluster.ClusterItem import com.casic.detector.common.cluster.ClusterOverlay import com.casic.detector.common.cluster.RegionItem import com.casic.detector.common.databinding.ActivityMainBinding -import com.casic.detector.common.databinding.DialogInstallMarkerBinding -import com.casic.detector.common.databinding.DialogSearchMarkerNewBinding import com.casic.detector.common.extensions.appendDownloadUrl -import com.casic.detector.common.extensions.compressImage import com.casic.detector.common.extensions.convertToGPGGA import com.casic.detector.common.extensions.createTaskCode import com.casic.detector.common.extensions.drawCircle -import com.casic.detector.common.extensions.getDefaultValue import com.casic.detector.common.extensions.hexToString import com.casic.detector.common.extensions.initImmersionBar import com.casic.detector.common.extensions.isNumber -import com.casic.detector.common.extensions.setDefaultValue -import com.casic.detector.common.extensions.show -import com.casic.detector.common.extensions.toColor import com.casic.detector.common.extensions.toHex -import com.casic.detector.common.extensions.toObjectType import com.casic.detector.common.model.TaskDetailLocalModel import com.casic.detector.common.model.TaskModel +import com.casic.detector.common.service.SerialPortService import com.casic.detector.common.utils.DataBaseManager import com.casic.detector.common.utils.ExcelTool import com.casic.detector.common.utils.FileType -import com.casic.detector.common.utils.GpioManager import com.casic.detector.common.utils.LocaleConstant import com.casic.detector.common.utils.LocationTool import com.casic.detector.common.utils.NtripAuthorizationCreator @@ -77,26 +62,15 @@ import com.casic.detector.common.vm.TaskViewModel import com.casic.detector.common.widgets.MarkerDetailDialog import com.casic.detector.common.widgets.QueryMarkerDialog -import com.casic.detector.common.widgets.RadarScanView import com.casic.detector.common.widgets.SamplePopupWindow -import com.luck.picture.lib.basic.PictureSelector -import com.luck.picture.lib.config.SelectMimeType -import com.luck.picture.lib.entity.LocalMedia -import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.extensions.appendZero -import com.pengxh.kt.lite.extensions.binding -import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.base.KotlinBaseActivity import com.pengxh.kt.lite.extensions.convertDrawable import com.pengxh.kt.lite.extensions.createDownloadFileDir -import com.pengxh.kt.lite.extensions.dateToTimestamp import com.pengxh.kt.lite.extensions.dp2px -import com.pengxh.kt.lite.extensions.getSystemService -import com.pengxh.kt.lite.extensions.initDialogLayoutParams import com.pengxh.kt.lite.extensions.isNetworkConnected import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.extensions.timestampToCompleteDate -import com.pengxh.kt.lite.extensions.timestampToTime import com.pengxh.kt.lite.extensions.toJson import com.pengxh.kt.lite.utils.FileDownloadManager import com.pengxh.kt.lite.utils.LoadState @@ -108,60 +82,41 @@ import com.pengxh.kt.lite.widget.dialog.BottomActionSheet import io.netty.buffer.Unpooled import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File +import java.io.OutputStream import java.nio.charset.StandardCharsets -import java.util.Calendar -import java.util.Date import java.util.Timer import java.util.TimerTask -import kotlin.math.atan2 -class MainActivity : SerialPortActivity(), SensorEventListener, - OnSocketConnectListener { +class MainActivity : KotlinBaseActivity(), OnSocketConnectListener { private val kTag = "MainActivity" private val context = this private val samplePopupWindow by lazy { SamplePopupWindow(this) } private val regionRadius by lazy { LocaleConstant.RADIUS_SIZE.dp2px(this) } private val backDrawables by lazy { HashMap() } - private val installDialog by lazy { InstallMarkerDialog(this) } - private val searchNewDialog by lazy { SearchMarkerNewDialog(this) } private val detailDialog by lazy { MarkerDetailDialog(this) } private val locationTool by lazy { LocationTool(this) } - private val rotationMatrix = FloatArray(9)//旋转矩阵缓存 - private val valueArray = FloatArray(3)//方位角数值 + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() private var clickTime: Long = 0 private var markers = ArrayList() private var clusterOverlay: ClusterOverlay? = null private var isFreeTask = false private var freeTaskTitle = "" private var ids = HashSet() - private var signalTask: TimerTask? = null - private var energyTask: TimerTask? = null - private var searchMarkerTimer: Timer? = null private var freeTaskId: String? = null - private var gravity: FloatArray? = null - private var geomagnetic: FloatArray? = null private var connectState = ConnectState.CLOSED private var socketClient: SocketClient? = null - private lateinit var aMap: AMap - private lateinit var sensorManager: SensorManager - - /***inner class 需要用到*****start*/ - private val gpioManager by lazy { GpioManager() } - private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } - private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) - .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() - private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private var serialPortService: SerialPortService? = null + private var searchMarkerTimer: Timer? = null private var soundResourceId = 0 - private var slowSoundResourceId = 0 - private var fastSoundResourceId = 0 private var isExecuteTask = false - - /***inner class 需要用到*****end*/ + private lateinit var aMap: AMap override fun initViewBinding(): ActivityMainBinding { return ActivityMainBinding.inflate(layoutInflater) @@ -171,10 +126,26 @@ binding.rootView.initImmersionBar(this, false, R.color.themeColor) } + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + + } + } + override fun initOnCreate(savedInstanceState: Bundle?) { + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + soundResourceId = soundPool.load(this, R.raw.ring3, 1) - slowSoundResourceId = soundPool.load(this, R.raw.ring4, 1) - fastSoundResourceId = soundPool.load(this, R.raw.ring2, 1) RtkLocationTool.getCurrentLocation(this) { if (connectState == ConnectState.SUCCESS) { @@ -192,8 +163,6 @@ samplePopupWindow.setPopupMenuItem(LocaleConstant.POPUP_TITLES) samplePopupWindow.setBackgroundDrawable(null) - sensorManager = getSystemService()!! - //task网络请求监听 taskViewModel.markerFileResult.observe(this) { if (it.isSuccess) { @@ -316,9 +285,6 @@ //安装。上传,然后存入本地库 binding.installButton.setOnClickListener { - /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * */ if (isFreeTask) { AlertMessageDialog.Builder().setContext(this).setTitle("温馨提示") .setMessage("正在自由巡检任务中,请完成此次任务之后再使用此功能") @@ -329,7 +295,7 @@ } }).build().show() } else { - installDialog.show() + navigatePageTo() } } @@ -449,8 +415,6 @@ //探测 binding.detectionButton.setOnClickListener { /** - * 改为Dialog方式,避免频繁打开/关闭串口 - * * 如果开启自由巡检就不让探测 * */ if (isFreeTask) { @@ -463,7 +427,12 @@ } }).build().show() } else { - searchNewDialog.show() + val flag = if (isExecuteTask) { + "1" + } else { + "0" + } + navigatePageTo(flag) } } @@ -476,14 +445,8 @@ override fun onConfirmClick() { isFreeTask = false soundPool.autoPause() - - //停止信号和ID搜索定时器 - signalTask?.cancel() - energyTask?.cancel() searchMarkerTimer?.cancel() - - //降低串口电位 - gpioManager.setGpioLow("18") + serialPortService?.closeSerialPort() binding.stopFreeTaskButton.visibility = View.GONE if (freeTaskId.isNullOrBlank()) { @@ -527,110 +490,79 @@ } private fun openSerialPort() { - //调高串口电位 - gpioManager.setGpioHigh("18") - - isFreeTask = true - //自由巡检 - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - searchMarkerTimer = Timer() - searchMarkerTimer?.apply { - schedule(signalTask, 0, 200) - schedule(energyTask, 0, 251) - } - binding.stopFreeTaskButton.visibility = View.VISIBLE - } + isFreeTask = true + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + searchMarkerTimer = Timer() + searchMarkerTimer?.schedule(object : TimerTask() { + override fun run() { + outStream.write('2'.code) + outStream.flush() - override fun onDataReceived(buffer: ByteArray) { - val hex = buffer.toHex() -// Log.d(kTag, "$kTag => $hex") - if (searchNewDialog.isDetectMarker) { - searchNewDialog.bindingValue(hex) - } else if (installDialog.isReadMarker) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - installDialog.bindingValue(markerId) + Thread.sleep(50) + + outStream.write('6'.code) + outStream.flush() + } + }, 0, 200) } - } else if (isFreeTask) { - val markerId = hex.take(20).hexToString() - if (markerId.isNumber()) { - //只响一次,因为探测频率高,所以依旧是连续的报警声 - soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) - //添加地图Marker - if (!ids.contains(markerId)) { - //根据markerId查询标识器经纬度 - val labels = DataBaseManager.get.queryMarkerById(markerId) - if (labels.isNotEmpty()) { - val bean = labels.first() - aMap.addMarker( - MarkerOptions().position( - LatLng(bean.lat.toDouble(), bean.lng.toDouble()) - ).icon(BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1)) - ) + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("4E")) { + //只响一次,因为探测频率高,所以依旧是连续的报警声 + soundPool.play(soundResourceId, 1f, 1f, 0, 0, 1f) + + try { + val energyResponse = hex.take(10).hexToString() + val energy = energyResponse.substring(1).toInt() + if (energy <= 1500 && detailDialog.isShowing) { + detailDialog.dismiss() + } + }catch (e:NumberFormatException){ + e.printStackTrace() } } - ids.add(markerId) - //显示标识器详细信息 - if (!detailDialog.isShowing) { - val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() - if (markerBean == null) { - "无法查询到此ID【${markerId}】的信息".show(context) - } else { - detailDialog.setMarker(markerBean) - detailDialog.show() + val markerId = hex.take(20).hexToString() + if (markerId.isNumber()) { + //添加地图Marker + if (!ids.contains(markerId)) { + //根据markerId查询标识器经纬度 + val labels = DataBaseManager.get.queryMarkerById(markerId) + if (labels.isNotEmpty()) { + val bean = labels.first() + aMap.addMarker( + MarkerOptions().position( + LatLng(bean.lat.toDouble(), bean.lng.toDouble()) + ).icon( + BitmapDescriptorFactory.fromResource(R.mipmap.label_blue1) + ) + ) + } + } + ids.add(markerId) + + //显示标识器详细信息 + if (!detailDialog.isShowing) { + val markerBean = DataBaseManager.get.queryMarkerById(markerId).firstOrNull() + if (markerBean == null) { + "无法查询到此ID【${markerId}】的信息".show(context) + } else { + detailDialog.setMarker(markerBean) + detailDialog.show() + } } } } - - if (hex.startsWith("4E")) { - val energyResponse = hex.take(10).hexToString() - try { - val energy = energyResponse.substring(1).toInt() - if (energy <= 500 && detailDialog.isShowing) { - detailDialog.dismiss() - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } - } + }) } override fun observeRequestState() { taskViewModel.loadState.observe(this) { when (it) { - LoadState.Loading -> { - if (installDialog.isInstallMarker) { - LoadingDialogHub.show(this, "标识器安装中,请稍后...") - } else { - LoadingDialogHub.show(this, "提交工单中,请稍后") - } - } - - LoadState.Success -> { - if (installDialog.isInstallMarker) { - installDialog.clearDefaultData() - installDialog.dismiss() - "${installDialog.markerId}安装成功".show(this) - } - LoadingDialogHub.dismiss() - } + LoadState.Loading -> LoadingDialogHub.show(this, "提交工单中,请稍后") else -> LoadingDialogHub.dismiss() } @@ -939,32 +871,6 @@ } else super.onKeyDown(keyCode, event) } - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - //精度发生变化时触发 - } - - override fun onSensorChanged(event: SensorEvent?) { - //值发生变化时触发 - val type = event?.sensor?.type - - if (type == Sensor.TYPE_ACCELEROMETER) { - gravity = event.values - } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { - geomagnetic = event.values - } - - if (gravity == null || geomagnetic == null) { - return - } - - if (SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)) { - SensorManager.getOrientation(rotationMatrix, valueArray) - - val degree = ((360f + valueArray[0] * 180f / Math.PI) % 360).toInt() - searchNewDialog.updateDegreeValue(degree) - } - } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { @@ -978,14 +884,6 @@ showLabelsOnMap() } - //注册加速度传感器监听 - val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) - sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL) - - //注册磁场传感器监听 - val magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) - sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL) - //取缓存 val remoteHost = SaveKeyValues.getValue( LocaleConstant.RTK_SERVER, "203.107.45.154" @@ -995,20 +893,20 @@ ) as String //连接千寻RTK服务器 - lifecycleScope.launch(Dispatchers.IO) { - if (socketClient != null) { - socketClient?.disconnect() - delay(3000) - } - - socketClient = SocketClient.Builder() - .setHostname(remoteHost) - .setPort(remotePort.toInt()) - .setTimeout(5000) - .setOnSocketListener(this@MainActivity) - .build() - socketClient?.connect() - } +// lifecycleScope.launch(Dispatchers.IO) { +// if (socketClient != null) { +// socketClient?.disconnect() +// delay(3000) +// } +// +// socketClient = SocketClient.Builder() +// .setHostname(remoteHost) +// .setPort(remotePort.toInt()) +// .setTimeout(5000) +// .setOnSocketListener(this@MainActivity) +// .build() +// socketClient?.connect() +// } } override fun onMessageResponse(data: ByteArray) { @@ -1021,12 +919,12 @@ val result = String(data, StandardCharsets.UTF_8) Log.d(kTag, "onMessageResponse: $result") if (result.contains("ICY 200 OK")) { - "高精度定位服务器连接成功".show(this) + "高精度定位服务连接成功".show(this) } } else { "收到千寻数据返回,长度:${data.size}".show(this) - out.write(data) - out.flush() +// out.write(data) +// out.flush() } } @@ -1061,7 +959,6 @@ override fun onPause() { super.onPause() binding.mapView.onPause() - sensorManager.unregisterListener(this) } override fun onSaveInstanceState(outState: Bundle) { @@ -1072,794 +969,11 @@ override fun onDestroy() { super.onDestroy() binding.mapView.onDestroy() - soundPool.release() + soundPool.autoPause() + searchMarkerTimer?.cancel() + serialPortService?.closeSerialPort() locationTool.stopLocation() - //降低串口电位 - gpioManager.setGpioLow("18") + unbindService(serviceConnection) + BaseApplication.get().closeSerialPort() } - - /**安装标识器对话框******************************************************************************/ - inner class InstallMarkerDialog(private val context: Context) : Dialog(context) { - - private val binding: DialogInstallMarkerBinding by binding() - private val calendar by lazy { Calendar.getInstance() } - private val realPaths = ArrayList() //真实图片路径 - private lateinit var imageAdapter: EditableImageAdapter - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - - var isReadMarker = false - var isInstallMarker = false - var markerId = "" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - - //初始化数据 - initDefaultData() - - //返回 - binding.titleInclude.leftBackView.setOnClickListener { - soundPool.autoPause() - dismiss() - } - - binding.objectInclude.objectTypeSpinner.onItemSelectedListener = - object : AdapterView.OnItemSelectedListener { - override fun onItemSelected( - parent: AdapterView<*>?, view: View?, position: Int, id: Long - ) { - when (position) { - 0 -> { - //显示管线属性 - binding.objectInclude.pipeInclude.root.visibility = View.VISIBLE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 1 -> { - //显示管线附属物属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 2 -> { - //显示管线特征点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = - View.VISIBLE - binding.objectInclude.pipeCrossInclude.root.visibility = View.GONE - } - - 3 -> { - //显示交叉穿越点属性 - binding.objectInclude.pipeInclude.root.visibility = View.GONE - binding.objectInclude.pipeAttachInclude.root.visibility = View.GONE - binding.objectInclude.pipeFeatureInclude.root.visibility = View.GONE - binding.objectInclude.pipeCrossInclude.root.visibility = - View.VISIBLE - } - } - } - - override fun onNothingSelected(parent: AdapterView<*>?) { - - } - } - - //安装 - binding.installButton.setOnClickListener { - val companyId = SaveKeyValues.getValue(LocaleConstant.USER_COMPANY_ID, "") as String - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - if (binding.objectInclude.pipeInclude.markerObjectTypeView.text.isNullOrBlank()) { - when (binding.objectInclude.objectTypeSpinner.selectedItem.toString()) { - "管线" -> "请输入管线种类!".show(context) - "管线附属物" -> "请输入附属物名称!".show(context) - "管线特征管点" -> "请输入特征管点!".show(context) - "交叉穿越点" -> "请输入上层管种类!".show(context) - } - return@setOnClickListener - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "管线") { - if (binding.objectInclude.pipeInclude.pipelineDiameterView.text.isNullOrBlank()) { - "请输入管径".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.pipeInclude.buryDeepView.text.isNullOrBlank()) { - "请输入埋深".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.objectTypeSpinner.selectedItem == "交叉穿越点") { - if (binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.isNullOrBlank()) { - "请输入下层管管径".show(context) - return@setOnClickListener - } - } - - if (binding.objectInclude.areaView.text.isNullOrBlank()) { - "请输入所属区域".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.lineView.text.isNullOrBlank()) { - "请输入所属线路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.roadView.text.isNullOrBlank()) { - "请输入所属道路".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.constructDateView.text.isNullOrBlank()) { - "请选择建设年代".show(context) - return@setOnClickListener - } - - if (binding.objectInclude.ownerView.text.isNullOrBlank()) { - "请输入权属单位".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierIdView.text.isNullOrBlank()) { - "请先读取标识器获取ID".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.identifierDeepView.text.isNullOrBlank()) { - "请输入标识器埋深".show(context) - return@setOnClickListener - } - - if (binding.identifierInclude.personDeptView.text.isNullOrBlank()) { - "请输入标识器安装部门".show(context) - return@setOnClickListener - } - - //先存本地再上传服务器 - saveMarkerInLocal() - - isInstallMarker = true - taskViewModel.installLabel( - context, - companyId, - binding.objectInclude.objectTypeSpinner.selectedItem.toString().toObjectType(), - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString(), - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm", - "${binding.objectInclude.pipeInclude.buryDeepView.text}mm", - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString(), - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString(), - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm", - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm", - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString(), - binding.objectInclude.areaView.text.toString(), - binding.objectInclude.lineView.text.toString(), - binding.objectInclude.roadView.text.toString(), - binding.objectInclude.constructDateView.text.toString(), - binding.objectInclude.ownerView.text.toString(), - objectId, - binding.identifierInclude.identifierIdView.text.toString(), - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString(), - "${binding.identifierInclude.identifierDeepView.text}mm", - binding.identifierInclude.personDeptView.text.toString(), - binding.identifierInclude.installTimeView.text.toString(), - binding.identifierInclude.lngView.text.toString(), - binding.identifierInclude.latView.text.toString(), - binding.identifierInclude.colorSpinner.selectedItem.toString().toColor(), - binding.remarkView.text.toString(), - realPaths - ) - - //保存默认值 - "markerObjectTypeView".setDefaultValue(binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString()) - "pipelineDiameterView".setDefaultValue(binding.objectInclude.pipeInclude.pipelineDiameterView.text.toString()) - "buryDeepView".setDefaultValue(binding.objectInclude.pipeInclude.buryDeepView.text.toString()) - "bottomPipeDiameterView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text.toString()) - "bottomPointDeepView".setDefaultValue(binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text.toString()) - "areaView".setDefaultValue(binding.objectInclude.areaView.text.toString()) - "lineView".setDefaultValue(binding.objectInclude.lineView.text.toString()) - "roadView".setDefaultValue(binding.objectInclude.roadView.text.toString()) - "ownerView".setDefaultValue(binding.objectInclude.ownerView.text.toString()) - "identifierDeepView".setDefaultValue(binding.identifierInclude.identifierDeepView.text.toString()) - "personDeptView".setDefaultValue(binding.identifierInclude.personDeptView.text.toString()) - } - - //读标识器 - binding.readMarkerButton.setOnClickListener { - LoadingDialogHub.show(this@MainActivity, "标识器读取中,请稍后...") - binding.readMarkerButton.isEnabled = false - - isReadMarker = true - - //调高串口电位 - gpioManager.setGpioHigh("18") - - soundPool.play(soundResourceId, 1f, 1f, 0, -1, 1f) - - out.write('2'.code) - out.flush() - - countDownTimer = object : CountDownTimer(10 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - soundPool.autoPause() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - "读取此标识器ID超时,请退出应用再试".show(context) - } - } - countDownTimer.start() - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - locationTool.getCurrentLocation(true, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - if (location != null) { - binding.identifierInclude.lngView.text = location.longitude.toString() - binding.identifierInclude.latView.text = location.latitude.toString() - } else { - "当前位置信号差,无法获取定位".show(context) - } - } - }) - } - - fun bindingValue(markerId: String) { - this.markerId = markerId - LoadingDialogHub.dismiss() - soundPool.autoPause() - countDownTimer.cancel() - binding.readMarkerButton.isEnabled = true - - //降低串口电位 - gpioManager.setGpioLow("18") - - isReadMarker = false - binding.identifierInclude.identifierIdView.text = markerId - } - - override fun dismiss() { - //降低串口电位 - gpioManager.setGpioLow("18") - soundPool.autoPause() - isInstallMarker = false - locationTool.stopLocation() - super.dismiss() - } - - private fun initDefaultData() { - binding.titleInclude.titleView.text = "安装新标识器" - binding.titleInclude.titleView.setTextColor(R.color.themeColor.convertColor(context)) - - imageAdapter = EditableImageAdapter(context, 3, 3) - binding.cameraInclude.addImageRecyclerView.adapter = imageAdapter - - //设置默认值 - binding.objectInclude.pipeInclude.markerObjectTypeView.setText("markerObjectTypeView".getDefaultValue()) - binding.objectInclude.pipeInclude.pipelineDiameterView.setText("pipelineDiameterView".getDefaultValue()) - binding.objectInclude.pipeInclude.buryDeepView.setText("buryDeepView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.setText("bottomPipeDiameterView".getDefaultValue()) - binding.objectInclude.pipeCrossInclude.bottomPointDeepView.setText("bottomPointDeepView".getDefaultValue()) - binding.objectInclude.areaView.setText("areaView".getDefaultValue()) - binding.objectInclude.lineView.setText("lineView".getDefaultValue()) - binding.objectInclude.roadView.setText("roadView".getDefaultValue()) - binding.objectInclude.ownerView.setText("ownerView".getDefaultValue()) - binding.identifierInclude.identifierDeepView.setText("identifierDeepView".getDefaultValue()) - binding.identifierInclude.personDeptView.setText("personDeptView".getDefaultValue()) - - /**************************************************************************************/ - binding.objectInclude.objectTypeSpinner.show( - this@MainActivity, LocaleConstant.POINT_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.materialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeAttachInclude.attachSpinner.show( - this@MainActivity, LocaleConstant.ATTACH_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeFeatureInclude.featureSpinner.show( - this@MainActivity, LocaleConstant.FEATURE_NAME_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.topPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.show( - this@MainActivity, LocaleConstant.PIPE_TYPE_ARRAY, 0 - ) - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.show( - this@MainActivity, LocaleConstant.PIPE_MATERIAL_ARRAY, 0 - ) - binding.objectInclude.pipeInclude.buryTypeSpinner.show( - this@MainActivity, LocaleConstant.BURY_METHOD_ARRAY, 0 - ) - - binding.objectInclude.constructDateView.setOnClickListener { - val datePicker = DatePickerDialog( - context, - null, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH) - ) - datePicker.show() - - datePicker.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener { - val year = datePicker.datePicker.year - val month = datePicker.datePicker.month + 1 - val day = datePicker.datePicker.dayOfMonth - val selectedDate = String.format( - "%s-%s-%s", year, month.appendZero(), day.appendZero() - ) - - //当前时间 - val current = System.currentTimeMillis().timestampToTime() - val today = "$selectedDate $current".dateToTimestamp() - if (Date(today).after(Date())) { - "建设年代不能早于当前日期".show(context) - } else { - datePicker.dismiss() - binding.objectInclude.constructDateView.text = selectedDate - } - } - } - - binding.identifierInclude.identifierTypeSpinner.show( - this@MainActivity, LocaleConstant.IDENTIFIER_TYPE_ARRAY, 0 - ) - - binding.identifierInclude.installTimeView.text = - System.currentTimeMillis().timestampToCompleteDate() - - binding.identifierInclude.colorSpinner.show( - this@MainActivity, LocaleConstant.COLOR_ARRAY, 0 - ) - - imageAdapter.setOnItemClickListener(object : EditableImageAdapter.OnItemClickListener { - override fun onAddImageClick() { - takePicture() - } - - override fun onItemClick(position: Int) { - if (realPaths[position].isEmpty()) { - "图片加载失败,无法查看大图".show(context) - } else { - context.navigatePageTo(position, realPaths) - } - } - - override fun onItemLongClick(view: View?, position: Int) { - imageAdapter.deleteImage(position) - } - }) - /**************************************************************************************/ - } - - //清除默认数据 - fun clearDefaultData() { - "markerObjectTypeView".setDefaultValue("") - "pipelineDiameterView".setDefaultValue("") - "buryDeepView".setDefaultValue("") - "bottomPipeDiameterView".setDefaultValue("") - "bottomPointDeepView".setDefaultValue("") - "areaView".setDefaultValue("") - "lineView".setDefaultValue("") - "roadView".setDefaultValue("") - "ownerView".setDefaultValue("") - "identifierDeepView".setDefaultValue("") - "personDeptView".setDefaultValue("") - } - - private fun saveMarkerInLocal() { - val objectId = SaveKeyValues.getValue(LocaleConstant.OBJECT_ID, "") as String - - val marker = MarkerLocalBean() - marker.objectType = binding.objectInclude.objectTypeSpinner.selectedItem.toString() - marker.pipelineType = - binding.objectInclude.pipeInclude.markerObjectTypeView.text.toString() - marker.pipelineMaterial = - binding.objectInclude.pipeInclude.materialSpinner.selectedItem.toString() - marker.pipelineDiameter = - "${binding.objectInclude.pipeInclude.pipelineDiameterView.text}mm" - marker.buryDepth = "${binding.objectInclude.pipeInclude.buryDeepView.text}mm" - marker.underlyingPipelineType = - binding.objectInclude.pipeCrossInclude.bottomPipeTypeSpinner.selectedItem.toString() - marker.underlyingPipelineMaterial = - binding.objectInclude.pipeCrossInclude.bottomPipeMaterialSpinner.selectedItem.toString() - marker.underlyingPipelineDiameter = - "${binding.objectInclude.pipeCrossInclude.bottomPipeDiameterView.text}mm" - marker.underlyingPipelineDepth = - "${binding.objectInclude.pipeCrossInclude.bottomPointDeepView.text}mm" - marker.buryMethod = - binding.objectInclude.pipeInclude.buryTypeSpinner.selectedItem.toString() - marker.area = binding.objectInclude.areaView.text.toString() - marker.line = binding.objectInclude.lineView.text.toString() - marker.road = binding.objectInclude.roadView.text.toString() - marker.constructTime = binding.objectInclude.constructDateView.text.toString() - marker.owner = binding.objectInclude.ownerView.text.toString() - marker.objectId = objectId - marker.markerId = binding.identifierInclude.identifierIdView.text.toString() - marker.markerType = - binding.identifierInclude.identifierTypeSpinner.selectedItem.toString() - marker.markerDepth = "${binding.identifierInclude.identifierDeepView.text}mm" - marker.installationDept = binding.identifierInclude.personDeptView.text.toString() - marker.updateTime = binding.identifierInclude.installTimeView.text.toString() - marker.lng = binding.identifierInclude.lngView.text.toString() - marker.lat = binding.identifierInclude.latView.text.toString() - marker.color = binding.identifierInclude.colorSpinner.selectedItem.toString().toColor() - marker.remark = binding.remarkView.text.toString() - marker.imagePath = realPaths.toJson() - - DataBaseManager.get.saveMarkerInLocale(marker) - } - - private fun takePicture() { - PictureSelector.create(this@MainActivity).openCamera(SelectMimeType.ofImage()) - .forResult(object : OnResultCallbackListener { - override fun onResult(result: java.util.ArrayList?) { - if (result == null) { - "拍照失败,请重试".show(context) - return - } - analyticalSelectResults(result[0]) - } - - override fun onCancel() { - - } - }) - } - - private fun analyticalSelectResults(result: LocalMedia) { - //压缩图片 - result.realPath.compressImage(context, object : OnImageCompressListener { - override fun onSuccess(file: File) { - realPaths.add(file.absolutePath) - imageAdapter.setupImage(realPaths) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) - } - } - - /**探测标识器新对话框****************************************************************************/ - inner class SearchMarkerNewDialog(private val context: Context) : Dialog(context) { - - private val taskId by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_ID, "") as String - } - private val taskCode by lazy { - SaveKeyValues.getValue(LocaleConstant.TASK_CODE, "") as String - } - private val markerPoints by lazy { ArrayList() } - private val binding: DialogSearchMarkerNewBinding by binding() - private var markerId = ""//实际探测出来的标识器ID - private var nearestMarkerId = ""//探测不到标识器的时候计算出来的最近的标识器ID - private lateinit var searchMarkerTimer: Timer - private lateinit var signalTask: TimerTask - private lateinit var energyTask: TimerTask - private lateinit var countDownTimer: CountDownTimer - private lateinit var locationTool: LocationTool - var isDetectMarker = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - this.initDialogLayoutParams(1f) - binding.depthButton.setOnClickListener { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - - out.write('3'.code) - val result = DataBaseManager.get.queryMarkerById(markerId) - if (result.isNotEmpty()) { - val tag = when (result.first().markerType) { - "EM30" -> '7' - "EM50" -> '8' - "EM14" -> '9' - else -> '1' - } - if (tag == '1') { - "此标识器无法读取埋深!".show(context) - initTimer() - } else { - // 发送读取标识器埋设深度指令 - LoadingDialogHub.show(this@MainActivity, "正在测距,请稍后...") - out.write(tag.code) - out.flush() - countDownTimer = object : CountDownTimer(15 * 1000, 1000) { - override fun onTick(millisUntilFinished: Long) { - - } - - override fun onFinish() { - LoadingDialogHub.dismiss() - "探测此标识器深度超时,请重试".show(context) - initTimer() - } - } - countDownTimer.start() - } - } else { - "标识器未安装,安装成功后才可读取埋深!".show(context) - initTimer() - } - } - - binding.markerInfoButton.setOnClickListener { - val id = if (markerId == "") { - nearestMarkerId - } else { - markerId - } - //查库 - val result = DataBaseManager.get.queryMarkerById(id) - if (result.isNotEmpty()) { - context.navigatePageTo(result.first().toJson()) - } else { - context.navigatePageTo(id) - } - //查看完就把ID置空,便于下次查看最新的ID - markerId = "" - } - } - - override fun show() { - super.show() - locationTool = LocationTool(context) - //点位渲染,每次定位都计算当前位置与符合条件的点距离 - locationTool.getCurrentLocation(false, object : OnGetLocationListener { - override fun onAMapLocationGet(location: AMapLocation?) { - location?.apply { - renderDataPoint(this) - } - } - }) - } - - /** - * 计算并渲染数据点。点太多采用协程计算,不然会有点卡顿 - * @param location 定位点(RTK获取) - * */ - private fun renderDataPoint(location: AMapLocation) { - val longitude = location.longitude - val latitude = location.latitude - lifecycleScope.launch(Dispatchers.IO) { - val dataPoints = ArrayList() - DataBaseManager.get.loadMarkers().forEach { - val distance = AMapUtils.calculateLineDistance( - LatLng(it.lat.toDouble(), it.lng.toDouble()), LatLng(latitude, longitude) - ) - val formatDistance = "%.2f".format(distance).toFloat() - - markerPoints.add(MarkerDistanceData(it.markerId, formatDistance)) - - if (formatDistance <= LocaleConstant.MAX_DISTANCE) { - val angle = atan2( - (it.lat.toDouble() - latitude), (it.lng.toDouble() - longitude) - ) + Math.PI - val formatAngle = "%.2f".format(angle).toDouble() - dataPoints.add( - RadarScanView.DataPoint(formatAngle, formatDistance) - ) - } - } - withContext(Dispatchers.Main) { - binding.radarScanView.renderPointData(dataPoints, - object : RadarScanView.OnGetNearestPointCallback { - override fun getNearestPoint(point: RadarScanView.DataPoint?) { - if (point == null) { - binding.distanceValueView.text = "大于5.5m" - binding.distancePgBar.progress = 100 - } else { - binding.distanceValueView.text = "${point.distance}m" - val progress = - if (point.distance > LocaleConstant.MAX_DISTANCE) { - 100 - } else { - (point.distance / LocaleConstant.MAX_DISTANCE) * 100 - } - binding.distancePgBar.progress = progress.toInt() - } - } - }) - } - } - } - - override fun onStart() { - super.onStart() - //调高串口电位 - gpioManager.setGpioHigh("18") - - initTimer() - - isDetectMarker = true - - if (isExecuteTask) { - binding.taskStateView.visibility = View.GONE - binding.taskStateView.isSelected = false - } else { - binding.taskStateView.visibility = View.VISIBLE - binding.taskStateView.isSelected = true - } - } - - fun bindingValue(hex: String) { - if (hex.startsWith("4E")) { - try { - //4E转为String为N,代表能量值 - //用能量值转动表盘 - val energyResponse = hex.take(10).hexToString() - val energy = energyResponse.substring(1).toInt() - if (energy >= 4000) { - soundPool.play(fastSoundResourceId, 1f, 1f, 0, 0, 1f) - } else { - soundPool.play(slowSoundResourceId, 1f, 1f, 0, 0, 1f) - } - - //通过设置进度条表示能量值 - binding.energyPgBar.progress = energy - binding.energyValueView.text = "${energy}dB" - - if (energy <= 700) {//18° - binding.energyTipsView.text = "信号较弱,可能距离较远" - binding.energyTipsView.setTextColor(Color.parseColor("#8D1717")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_red) - - binding.depthButton.isEnabled = false - binding.depthButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_disable) - binding.markerInfoButton.isEnabled = false - binding.markerInfoButton.setTextColor(Color.parseColor("#CCCCCC")) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_disable) - - binding.searchResultView.text = "未检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#8D1717")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_red) - } else if (energy >= 4100) { - binding.energyTipsView.text = "信号极强,接近标识器正上方" - binding.energyTipsView.setTextColor(Color.parseColor("#428d00")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_green) - - if (markerPoints.isEmpty()) { - Log.d(kTag, "bindingValue: markerPoints.isEmpty()") - return - } - - //需要转一下,不然会有并发问题 - val temp = ArrayList() - temp.addAll(markerPoints) - temp.sortBy(MarkerDistanceData::distance) - val nearestPoint = temp.first() - nearestMarkerId = nearestPoint.markerId - handleMarker(nearestMarkerId) - } else { - binding.energyTipsView.text = "已靠近,请继续移动位置" - binding.energyTipsView.setTextColor(Color.parseColor("#8C5700")) - binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_yellow) - } - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else if (hex.startsWith("53")) { - LoadingDialogHub.dismiss() - countDownTimer.cancel() - try { - val depthResponse = hex.take(10).hexToString() - val depth = depthResponse.drop(2).toInt() - AlertMessageDialog.Builder().setContext(context).setTitle("温馨提示") - .setMessage("标识器埋深:${depth}厘米").setPositiveButton("知道了") - .setOnDialogButtonClickListener(object : - AlertMessageDialog.OnDialogButtonClickListener { - override fun onConfirmClick() { - initTimer() - } - }).build().show() - } catch (e: NumberFormatException) { - e.printStackTrace() - } - } else { - val id = hex.take(20).hexToString() - if (id.isNumber()) { - markerId = id - handleMarker(markerId) - } - } - } - - private fun handleMarker(id: String) { - binding.depthButton.isEnabled = true - binding.depthButton.setTextColor(Color.WHITE) - binding.depthButton.setBackgroundResource(R.mipmap.left_button_enable) - binding.markerInfoButton.isEnabled = true - binding.markerInfoButton.setTextColor(Color.WHITE) - binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_enable) - - binding.searchResultView.text = "已检测到标识器" - binding.searchResultView.setTextColor(Color.parseColor("#428d00")) - binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_green) - - //自动上传标识器 - if (isExecuteTask) { - val taskMarkerLocalBean = DataBaseManager.get.queryTaskMarkerById( - taskId, taskCode, id, "0" - ) - taskMarkerLocalBean?.apply { - taskViewModel.uploadMarker(context, this) - } - } - } - - //更新罗盘角度 - fun updateDegreeValue(degree: Int) { - binding.radarScanView.setDegreeValue(degree) - } - - private fun initTimer() { - searchMarkerTimer = Timer() - - signalTask = object : TimerTask() { - override fun run() { - out.write('2'.code) - out.flush() - } - } - - energyTask = object : TimerTask() { - override fun run() { - out.write('6'.code) - out.flush() - } - } - - //错开信号和能量定时器 - searchMarkerTimer.schedule(signalTask, 0, 201) - searchMarkerTimer.schedule(energyTask, 0, 251) - } - - override fun dismiss() { - signalTask.cancel() - energyTask.cancel() - searchMarkerTimer.cancel() - //降低串口电位 - gpioManager.setGpioLow("18") - isDetectMarker = false - soundPool.autoPause() - locationTool.stopLocation() - super.dismiss() - } - } - - /** - * 标识器与当前定位的数据 - * */ - data class MarkerDistanceData(var markerId: String, var distance: Float) } \ No newline at end of file diff --git a/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt b/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt new file mode 100644 index 0000000..14fe667 --- /dev/null +++ b/app/src/main/java/com/casic/detector/common/view/SearchMarkerActivity.kt @@ -0,0 +1,462 @@ +package com.casic.detector.common.view + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.graphics.Color +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.media.AudioAttributes +import android.media.SoundPool +import android.os.Bundle +import android.os.CountDownTimer +import android.os.IBinder +import android.util.Log +import android.view.View +import android.view.WindowManager +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMapUtils +import com.amap.api.maps.model.LatLng +import com.casic.detector.common.R +import com.casic.detector.common.callback.OnGetLocationListener +import com.casic.detector.common.callback.OnSerialPortDataListener +import com.casic.detector.common.databinding.ActivitySearchMarkerBinding +import com.casic.detector.common.extensions.hexToString +import com.casic.detector.common.extensions.isNumber +import com.casic.detector.common.extensions.toHex +import com.casic.detector.common.model.MarkerDistanceData +import com.casic.detector.common.service.SerialPortService +import com.casic.detector.common.utils.DataBaseManager +import com.casic.detector.common.utils.LocaleConstant +import com.casic.detector.common.utils.LocationTool +import com.casic.detector.common.vm.TaskViewModel +import com.casic.detector.common.widgets.RadarScanView +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.getSystemService +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.Constant +import com.pengxh.kt.lite.utils.LoadingDialogHub +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.widget.dialog.AlertMessageDialog +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.OutputStream +import java.util.Timer +import java.util.TimerTask +import kotlin.math.atan2 + +class SearchMarkerActivity : KotlinBaseActivity(), + SensorEventListener { + + private val kTag = "SearchMarkerActivity" + private val context = this + private val locationTool by lazy { LocationTool(this) } + private val markerPoints by lazy { ArrayList() } + private val taskViewModel by lazy { ViewModelProvider(this)[TaskViewModel::class.java] } + private val sensorManager by lazy { getSystemService()!! } + private val attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build() + private val soundPool = SoundPool.Builder().setMaxStreams(16).setAudioAttributes(attr).build() + private val rotationMatrix = FloatArray(9)//旋转矩阵缓存 + private val valueArray = FloatArray(3)//方位角数值 + private var slowSoundResourceId = 0 + private var fastSoundResourceId = 0 + private var markerId = ""//实际探测出来的标识器ID + private var nearestMarkerId = ""//探测不到标识器的时候计算出来的最近的标识器ID + private var isExecuteTask = false + private var signalEnergy = 0 //标识器信号强度 + private var gravity: FloatArray? = null + private var geomagnetic: FloatArray? = null + private var serialPortService: SerialPortService? = null + private lateinit var searchMarkerTimer: Timer + private lateinit var searchSignalEnergyTimer: Timer + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) { + if (iBinder is SerialPortService.ServiceBinder) { + serialPortService = iBinder.getSerialPortService() + Log.d(kTag, "onServiceConnected: 服务已绑定") + searchMarker() + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + + } + } + + override fun initOnCreate(savedInstanceState: Bundle?) { + val flag = intent.getStringExtra(Constant.INTENT_PARAM) as String + isExecuteTask = flag != "0" + + //绑定串口通信服务 + Intent(this, SerialPortService::class.java).also { + bindService(it, serviceConnection, Context.BIND_AUTO_CREATE) + } + + slowSoundResourceId = soundPool.load(this, R.raw.ring4, 1) + fastSoundResourceId = soundPool.load(this, R.raw.ring2, 1) + + //点位渲染,每次定位都计算当前位置与符合条件的点距离 + locationTool.getCurrentLocation(false, object : OnGetLocationListener { + override fun onAMapLocationGet(location: AMapLocation?) { + location?.apply { + renderDataPoint(this) + } + } + }) + + if (isExecuteTask) { + binding.taskStateView.visibility = View.GONE + binding.taskStateView.isSelected = false + } else { + binding.taskStateView.visibility = View.VISIBLE + binding.taskStateView.isSelected = true + } + + //在标识器信号强度很强的时候,从数据库中计算出距离最近的点,以防出现探测不到ID的情况 + searchSignalEnergyTimer = Timer() + searchSignalEnergyTimer.schedule(object : TimerTask() { + override fun run() { + //markerId为空,说明没有通过探测得到标识器ID,那么就需要通过计算得到近似的标识器 + if (signalEnergy >= 4100 && markerPoints.isNotEmpty()) { + try { + //需要转一下,不然会有并发问题 + val temp = ArrayList() + temp.addAll(markerPoints) + temp.sortBy(MarkerDistanceData::distance) + val nearestPoint = temp.first() + nearestMarkerId = nearestPoint.markerId + runOnUiThread { + handleMarker(nearestMarkerId) + } + markerPoints.clear() + } catch (e: NullPointerException) { + e.printStackTrace() + } + } + } + }, 0, 2000) + } + + private val countDownTimer = object : CountDownTimer(10 * 1000, 1000) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + serialPortService?.closeSerialPort() + "标识器深度探测超时,请移动位置重试".show(context) + searchMarker() + } + } + + override fun initEvent() { + binding.depthButton.setOnClickListener { + stopSearchMarker() + val result = DataBaseManager.get.queryMarkerById(markerId) + if (result.isNotEmpty()) { + val tag = when (result.first().markerType) { + "EM30" -> '7' + "EM50" -> '8' + "EM14" -> '9' + else -> '1' + } + if (tag == '1') { + "此标识器无法读取埋深!".show(this) + searchMarker() + } else { + LoadingDialogHub.show(this, "正在探测标识器埋深,请稍后...") + countDownTimer.start() + // 发送读取标识器埋设深度指令 + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + // 发送读取标识器埋设深度指令 + outStream.write(tag.code) + outStream.flush() + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("53")) { + LoadingDialogHub.dismiss() + countDownTimer.cancel() + try { + val depthResponse = hex.take(10).hexToString() + val depth = depthResponse.drop(2).toInt() + AlertMessageDialog.Builder().setContext(context) + .setTitle("温馨提示") + .setMessage("标识器埋深:${depth}厘米") + .setPositiveButton("知道了") + .setOnDialogButtonClickListener(object : + AlertMessageDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + serialPortService?.closeSerialPort() + searchMarker() + } + }).build().show() + } catch (e: WindowManager.BadTokenException) { + e.printStackTrace() + } catch (e: NumberFormatException) { + e.printStackTrace() + } + } + } + }) + } + } else { + LoadingDialogHub.dismiss() + "标识器未安装,安装成功后才可读取埋深!".show(this) + searchMarker() + } + } + + binding.markerInfoButton.setOnClickListener { + val id = if (markerId == "") { + nearestMarkerId + } else { + markerId + } + //查库 + val result = DataBaseManager.get.queryMarkerById(id) + if (result.isNotEmpty()) { + navigatePageTo(result.first().toJson()) + } else { + navigatePageTo(id) + } + //查看完就把ID置空,便于下次查看最新的ID + markerId = "" + } + } + + /** + * 搜索标识器 + * */ + private fun searchMarker() { + Thread.sleep(100) + + serialPortService?.openSerialPort(object : OnSerialPortDataListener { + override fun write(outStream: OutputStream) { + searchMarkerTimer = Timer() + searchMarkerTimer.schedule(object : TimerTask() { + override fun run() { + outStream.write('2'.code) + outStream.flush() + + Thread.sleep(50) + + outStream.write('6'.code) + outStream.flush() + } + }, 0, 200) + } + + override fun onDataReceived(buffer: ByteArray) { + val hex = buffer.toHex() + if (hex.startsWith("4E")) { + try { + //4E转为String为N,代表能量值 + //用能量值转动表盘 + val energyResponse = hex.take(10).hexToString() + signalEnergy = energyResponse.substring(1).toInt() + if (signalEnergy >= 4000) { + soundPool.play(fastSoundResourceId, 1f, 1f, 0, 0, 1f) + } else { + soundPool.play(slowSoundResourceId, 1f, 1f, 0, 0, 1f) + } + + //通过设置进度条表示能量值 + binding.energyPgBar.progress = signalEnergy + binding.energyValueView.text = "${signalEnergy}dB" + + //根据信号强度更新界面 + if (signalEnergy <= 700) {//18° + binding.energyTipsView.text = "信号较弱,可能距离较远" + binding.energyTipsView.setTextColor(Color.parseColor("#8D1717")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_red) + + binding.depthButton.isEnabled = false + binding.depthButton.setTextColor(Color.parseColor("#CCCCCC")) + binding.depthButton.setBackgroundResource(R.mipmap.left_button_disable) + binding.markerInfoButton.isEnabled = false + binding.markerInfoButton.setTextColor(Color.parseColor("#CCCCCC")) + binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_disable) + + binding.searchResultView.text = "未检测到标识器" + binding.searchResultView.setTextColor(Color.parseColor("#8D1717")) + binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_red) + } else if (signalEnergy >= 4100) { + binding.energyTipsView.text = "信号极强,接近标识器正上方" + binding.energyTipsView.setTextColor(Color.parseColor("#428d00")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_green) + } else { + binding.energyTipsView.text = "已靠近,请继续移动位置" + binding.energyTipsView.setTextColor(Color.parseColor("#8C5700")) + binding.energyTipsView.setBackgroundResource(R.mipmap.bg_large_text_yellow) + } + } catch (e: NumberFormatException) { + e.printStackTrace() + } + } else { + val id = hex.take(20).hexToString() + if (id.isNumber()) { + markerId = id + handleMarker(markerId) + } + } + } + }) + } + + private fun stopSearchMarker() { + soundPool.autoPause() + searchMarkerTimer.cancel() + serialPortService?.closeSerialPort() + + Thread.sleep(100) + } + + /** + * 计算并渲染数据点。点太多采用协程计算,不然会有点卡顿。默认2s计算一次 + * @param location 定位点(RTK获取) + * */ + private fun renderDataPoint(location: AMapLocation) { + val longitude = location.longitude + val latitude = location.latitude + lifecycleScope.launch(Dispatchers.IO) { + val dataPoints = ArrayList() + DataBaseManager.get.loadMarkers().forEach { + val distance = AMapUtils.calculateLineDistance( + LatLng(it.lat.toDouble(), it.lng.toDouble()), LatLng(latitude, longitude) + ) + val formatDistance = "%.2f".format(distance).toFloat() + markerPoints.add(MarkerDistanceData(it.markerId, formatDistance)) + + if (formatDistance <= LocaleConstant.MAX_DISTANCE) { + val angle = atan2( + (it.lat.toDouble() - latitude), (it.lng.toDouble() - longitude) + ) + Math.PI + val formatAngle = "%.2f".format(angle).toDouble() + + dataPoints.add(RadarScanView.DataPoint(formatAngle, formatDistance)) + } + } + withContext(Dispatchers.Main) { + binding.radarScanView.renderPointData(dataPoints, + object : RadarScanView.OnGetNearestPointCallback { + override fun getNearestPoint(point: RadarScanView.DataPoint?) { + if (point == null) { + binding.distanceValueView.text = "大于5.5m" + binding.distancePgBar.progress = 100 + } else { + binding.distanceValueView.text = "${point.distance}m" + val progress = if (point.distance > LocaleConstant.MAX_DISTANCE) { + 100 + } else { + (point.distance / LocaleConstant.MAX_DISTANCE) * 100 + } + binding.distancePgBar.progress = progress.toInt() + } + } + } + ) + } + } + } + + override fun initViewBinding(): ActivitySearchMarkerBinding { + return ActivitySearchMarkerBinding.inflate(layoutInflater) + } + + override fun observeRequestState() { + + } + + override fun setupTopBarLayout() { + + } + + private fun handleMarker(id: String) { + binding.depthButton.isEnabled = true + binding.depthButton.setTextColor(Color.WHITE) + binding.depthButton.setBackgroundResource(R.mipmap.left_button_enable) + binding.markerInfoButton.isEnabled = true + binding.markerInfoButton.setTextColor(Color.WHITE) + binding.markerInfoButton.setBackgroundResource(R.mipmap.right_button_enable) + + binding.searchResultView.text = "已检测到标识器" + binding.searchResultView.setTextColor(Color.parseColor("#428d00")) + binding.searchResultView.setBackgroundResource(R.mipmap.bg_small_text_green) + + //自动上传标识器 + if (isExecuteTask) { + val taskId = SaveKeyValues.getValue(LocaleConstant.TASK_ID, "") as String + val taskCode = SaveKeyValues.getValue(LocaleConstant.TASK_CODE, "") as String + val taskMarkerLocalBean = DataBaseManager.get.queryTaskMarkerById( + taskId, taskCode, id, "0" + ) + taskMarkerLocalBean?.apply { + taskViewModel.uploadMarker(context, this) + } + } + } + + override fun onResume() { + super.onResume() + //注册加速度传感器监听 + val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL) + + //注册磁场传感器监听 + val magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) + sensorManager.registerListener(this, magnetic, SensorManager.SENSOR_DELAY_NORMAL) + } + + override fun onPause() { + super.onPause() + sensorManager.unregisterListener(this) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + //精度发生变化时触发 + } + + override fun onSensorChanged(event: SensorEvent?) { + //值发生变化时触发 + val type = event?.sensor?.type + + if (type == Sensor.TYPE_ACCELEROMETER) { + gravity = event.values + } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { + geomagnetic = event.values + } + + if (gravity == null || geomagnetic == null) { + return + } + + if (SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)) { + SensorManager.getOrientation(rotationMatrix, valueArray) + + val degree = ((360f + valueArray[0] * 180f / Math.PI) % 360).toInt() + //更新罗盘角度 + binding.radarScanView.setDegreeValue(degree) + } + } + + override fun onDestroy() { + super.onDestroy() + stopSearchMarker() + locationTool.stopLocation() + unbindService(serviceConnection) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_install_marker.xml b/app/src/main/res/layout/activity_install_marker.xml new file mode 100644 index 0000000..65c2262 --- /dev/null +++ b/app/src/main/res/layout/activity_install_marker.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +