refactor(serial): 将 SerialPortManager 合并到 ScaleManager
- 删除 SerialPortManager,串口生命周期(open/startReceive/release)内嵌到 ScaleManager - ScaleManager 改为 object 单例,去除 .instance 调用 - init() 简化为只接收 activateCode,去除 deviceId + activateMap 查表逻辑 - 新增 start(preHook) 方法合并 open + startReceive,支持链式调用到末尾 - BaseActivity.sendCmd 改为直接调用 ScaleManager.sendCmd Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,6 @@ import com.shuwei.intelligent.shelves.serial.ProtocolConstants
|
|||||||
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.C_TEMP_CMD
|
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.C_TEMP_CMD
|
||||||
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.START_TEMP_CTRL_CMD
|
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.START_TEMP_CTRL_CMD
|
||||||
import com.shuwei.intelligent.shelves.serial.ScaleManager
|
import com.shuwei.intelligent.shelves.serial.ScaleManager
|
||||||
import com.shuwei.intelligent.shelves.serial.SerialPortManager
|
|
||||||
import com.shuwei.intelligent.shelves.utils.GridLayoutTool
|
import com.shuwei.intelligent.shelves.utils.GridLayoutTool
|
||||||
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
|
import com.shuwei.intelligent.shelves.utils.IntervalExecutor
|
||||||
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
|
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
|
||||||
@@ -77,33 +76,34 @@ class HomeActivity : BaseActivity() {
|
|||||||
|
|
||||||
// 串口协议解析器,通过回调通知本 Activity
|
// 串口协议解析器,通过回调通知本 Activity
|
||||||
private fun initScaleManager() {
|
private fun initScaleManager() {
|
||||||
ScaleManager.instance
|
lifecycleScope.launch {
|
||||||
.init(
|
val ok = ScaleManager
|
||||||
sendAction = { cmd ->
|
.init(activateCode = ProtocolConstants.ACTIVE_MAP[App.deviceId] ?: "")
|
||||||
// ScaleManager 的自动应答(心跳/对时等)直接写串口,不经过 BaseActivity.sendCmd
|
.onHeartbeat { _, data ->
|
||||||
lifecycleScope.launch { SerialPortManager.send(cmd) }
|
data.weights.forEach { weight ->
|
||||||
},
|
val shelfNo = weight.index
|
||||||
deviceId = App.deviceId ?: "",
|
if (shelfNo in 1..list.size) {
|
||||||
activateMap = ProtocolConstants.ACTIVE_MAP
|
list.firstOrNull { it.deviceNo == shelfNo }?.let { model ->
|
||||||
)
|
model.weight = weight.grams.toDouble()
|
||||||
.onHeartbeat { _, data ->
|
val pos = list.indexOf(model)
|
||||||
data.weights.forEach { weight ->
|
shelfAdapter.notifyItemChanged(pos)
|
||||||
val shelfNo = weight.index
|
log("getWeightInfo: deviceNo=${model.deviceNo},realWeight=${weight.grams}")
|
||||||
if (shelfNo in 1..list.size) {
|
EventBus.getDefault().post(SendWeightEvent(model.deviceNo, weight.grams))
|
||||||
list.firstOrNull { it.deviceNo == shelfNo }?.let { model ->
|
}
|
||||||
model.weight = weight.grams.toDouble()
|
|
||||||
val pos = list.indexOf(model)
|
|
||||||
shelfAdapter.notifyItemChanged(pos)
|
|
||||||
log("getWeightInfo: deviceNo=${model.deviceNo},realWeight=${weight.grams}")
|
|
||||||
EventBus.getDefault().post(SendWeightEvent(model.deviceNo, weight.grams))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.onTempReport { _ ->
|
||||||
.onTempReport { _ ->
|
updateLeftStatus(deviceName)
|
||||||
updateLeftStatus(deviceName)
|
}
|
||||||
}
|
.onLog { message -> log(message) }
|
||||||
.onLog { message -> log(message) }
|
.start { data ->
|
||||||
|
lastDataReceivedTime = System.currentTimeMillis()
|
||||||
|
noDataWarningDialog?.takeIf { it.isShowing }?.dismiss()
|
||||||
|
log("receiveSerialPortData: $data")
|
||||||
|
}
|
||||||
|
log("onCreate: openState=$ok")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -119,28 +119,9 @@ class HomeActivity : BaseActivity() {
|
|||||||
updateLeftStatus("")
|
updateLeftStatus("")
|
||||||
initRecyclerView()
|
initRecyclerView()
|
||||||
|
|
||||||
// 初始化 ScaleManager(串口初始化前注册回调)
|
// 初始化 ScaleManager:注册回调、打开串口、启动接收
|
||||||
initScaleManager()
|
initScaleManager()
|
||||||
|
|
||||||
// 串口初始化,独立协程,不阻塞网络状态监听
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val openState = SerialPortManager.open()
|
|
||||||
log("onCreate: openState=$openState")
|
|
||||||
if (openState) {
|
|
||||||
SerialPortManager.startReceive { data ->
|
|
||||||
// 切换到主线程协程处理串口数据
|
|
||||||
lifecycleScope.launch {
|
|
||||||
runCatching { receiveSerialPortData(data) }
|
|
||||||
.onFailure {
|
|
||||||
it.printStackTrace()
|
|
||||||
log("串口数据处理异常: ${it.message}")
|
|
||||||
toast("串口数据处理异常,请检查设备连接")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 网络状态监听,独立协程,与串口初始化并行执行
|
// 网络状态监听,独立协程,与串口初始化并行执行
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
@@ -250,14 +231,6 @@ class HomeActivity : BaseActivity() {
|
|||||||
loadEmptyView()
|
loadEmptyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun receiveSerialPortData(srcData: String) {
|
|
||||||
log("receiveSerialPortData: $srcData")
|
|
||||||
// 重置无数据计时,并关闭已显示的超时提示弹窗
|
|
||||||
lastDataReceivedTime = System.currentTimeMillis()
|
|
||||||
noDataWarningDialog?.takeIf { it.isShowing }?.dismiss()
|
|
||||||
ScaleManager.instance.onDataReceived(srcData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var tipDialog: CommonDialog? = null
|
private var tipDialog: CommonDialog? = null
|
||||||
|
|
||||||
/** 最后一次成功收到串口数据的时间戳(毫秒) */
|
/** 最后一次成功收到串口数据的时间戳(毫秒) */
|
||||||
@@ -309,8 +282,7 @@ class HomeActivity : BaseActivity() {
|
|||||||
noDataWarningDialog?.dismiss()
|
noDataWarningDialog?.dismiss()
|
||||||
noDataWarningDialog = null
|
noDataWarningDialog = null
|
||||||
|
|
||||||
runBlocking { SerialPortManager.close() }
|
runBlocking { ScaleManager.release() }
|
||||||
ScaleManager.instance.release()
|
|
||||||
saveTaskJob?.cancel()
|
saveTaskJob?.cancel()
|
||||||
overdueTaskJob?.cancel()
|
overdueTaskJob?.cancel()
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
@@ -333,10 +305,10 @@ class HomeActivity : BaseActivity() {
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (isNeedOpenLock()) {
|
if (isNeedOpenLock()) {
|
||||||
val openCmd = if (list[position].deviceNo in 1..5)
|
val openCmd = if (list[position].deviceNo in 1..5)
|
||||||
ScaleManager.instance.buildOpenLock1Cmd()
|
ScaleManager.buildOpenLock1Cmd()
|
||||||
else
|
else
|
||||||
ScaleManager.instance.buildOpenLock2Cmd()
|
ScaleManager.buildOpenLock2Cmd()
|
||||||
ScaleManager.instance.sendCmd(openCmd)
|
ScaleManager.sendCmd(openCmd)
|
||||||
}
|
}
|
||||||
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
|
launch(Intent(this@HomeActivity, ShelfActivity::class.java).also {
|
||||||
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
|
it.putExtra(ShelfActivity.SHELF_MODEL, list[position])
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class ShelfActivity : BaseActivity() {
|
|||||||
//binding.tvFoodWeight.text = "0克"
|
//binding.tvFoodWeight.text = "0克"
|
||||||
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
|
EventBus.getDefault().post(ClearShelfEvent(shelfModel!!.deviceNo, tempWeight))
|
||||||
}
|
}
|
||||||
sendCmd(ScaleManager.instance.buildGetStatusCmd())
|
sendCmd(ScaleManager.buildGetStatusCmd())
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
viewModel.getGoodsListUiState.collect { state ->
|
viewModel.getGoodsListUiState.collect { state ->
|
||||||
@@ -359,7 +359,7 @@ class ShelfActivity : BaseActivity() {
|
|||||||
private fun clearZero() {
|
private fun clearZero() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val shelfNo = shelfModel?.deviceNo ?: 0
|
val shelfNo = shelfModel?.deviceNo ?: 0
|
||||||
val zeroClearingCmd = ScaleManager.instance.buildScaleZeroCmd(shelfNo)
|
val zeroClearingCmd = ScaleManager.buildScaleZeroCmd(shelfNo)
|
||||||
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
|
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
|
||||||
sendCmd(zeroClearingCmd)
|
sendCmd(zeroClearingCmd)
|
||||||
// sendCmd(
|
// sendCmd(
|
||||||
@@ -369,7 +369,7 @@ class ShelfActivity : BaseActivity() {
|
|||||||
realWeight = 0
|
realWeight = 0
|
||||||
// binding.tvFoodWeight.text = "${realWeight}克"
|
// binding.tvFoodWeight.text = "${realWeight}克"
|
||||||
|
|
||||||
sendCmd(ScaleManager.instance.buildGetStatusCmd())
|
sendCmd(ScaleManager.buildGetStatusCmd())
|
||||||
startTime = System.currentTimeMillis()
|
startTime = System.currentTimeMillis()
|
||||||
Loading.show(this@ShelfActivity)
|
Loading.show(this@ShelfActivity)
|
||||||
window.decorView.postDelayed({ Loading.dismiss() }, 10000)
|
window.decorView.postDelayed({ Loading.dismiss() }, 10000)
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ import androidx.activity.result.ActivityResultLauncher
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.shuwei.intelligent.shelves.activity.LogActivity
|
import com.shuwei.intelligent.shelves.activity.LogActivity
|
||||||
import com.shuwei.intelligent.shelves.R
|
import com.shuwei.intelligent.shelves.R
|
||||||
import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding
|
import com.shuwei.intelligent.shelves.databinding.ActivityBaseBinding
|
||||||
import com.shuwei.intelligent.shelves.databinding.PopupRightStatusMenuBinding
|
import com.shuwei.intelligent.shelves.databinding.PopupRightStatusMenuBinding
|
||||||
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_CLOSE
|
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_CLOSE
|
||||||
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_OPEN
|
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_OPEN
|
||||||
import com.shuwei.intelligent.shelves.serial.SerialPortManager
|
import com.shuwei.intelligent.shelves.serial.ScaleManager
|
||||||
import com.shuwei.intelligent.shelves.utils.FileLogger
|
import com.shuwei.intelligent.shelves.utils.FileLogger
|
||||||
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
|
import com.shuwei.intelligent.shelves.utils.ext.clickWithDebounce
|
||||||
import com.shuwei.intelligent.shelves.utils.ext.dp
|
import com.shuwei.intelligent.shelves.utils.ext.dp
|
||||||
@@ -215,10 +214,8 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
|
|
||||||
fun sendCmd(cmd: String) {
|
fun sendCmd(cmd: String) {
|
||||||
lifecycleScope.launch {
|
ScaleManager.sendCmd(cmd)
|
||||||
val result = SerialPortManager.send(cmd)
|
log("发送命令: $cmd")
|
||||||
log("发送命令${cmd}结果:${result}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileLogger by lazy { FileLogger(this) }
|
val fileLogger by lazy { FileLogger(this) }
|
||||||
|
|||||||
@@ -12,59 +12,69 @@ import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.CMD_TIME_SYN
|
|||||||
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.CMD_UNLOCK
|
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.CMD_UNLOCK
|
||||||
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.FOOTER_HEX
|
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.FOOTER_HEX
|
||||||
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.HEADER_HEX
|
import com.shuwei.intelligent.shelves.serial.ScaleProtocolConstants.HEADER_HEX
|
||||||
|
import com.shuwei.intelligent.shelves.utils.hexToBytes
|
||||||
|
import com.shuwei.intelligent.shelves.utils.toHexString2
|
||||||
|
import io.github.jeadyx.jserialport.AndroidSerialPort
|
||||||
|
import io.github.jeadyx.jserialport.SerialPort
|
||||||
|
import io.github.jeadyx.jserialport.SerialPortFactory
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 称重控制板协议管理器(SDK 入口)
|
* 称重控制板协议管理器(SDK 入口)
|
||||||
*
|
*
|
||||||
* 职责:
|
* 职责:
|
||||||
|
* - 管理串口连接(open/startReceive/release)
|
||||||
* - 接收串口原始分片数据,自动拼装帧并分发到对应回调
|
* - 接收串口原始分片数据,自动拼装帧并分发到对应回调
|
||||||
* - 内置各命令的自动应答逻辑(心跳/对时/卡号等)
|
* - 内置各命令的自动应答逻辑(心跳/对时/卡号等)
|
||||||
* - 提供链式回调注册、常用指令构建、资源释放
|
* - 提供链式回调注册、常用指令构建、资源释放
|
||||||
*
|
*
|
||||||
* 设计原则:
|
|
||||||
* - 不依赖具体串口实现,通过 init(sendAction) 注入发送函数
|
|
||||||
* - 不依赖任何业务类,可直接复制到其他项目使用
|
|
||||||
* - 非线程安全,建议在同一线程(如串口IO线程或主线程)中使用
|
|
||||||
*
|
|
||||||
* 典型用法:
|
* 典型用法:
|
||||||
* ```kotlin
|
* ```kotlin
|
||||||
* // 初始化(Activity/Application中)
|
* // 初始化并注册回调
|
||||||
* ScaleManager.instance
|
* ScaleManager
|
||||||
* .init { hexStr -> scope.launch { SerialPortManager.send(hexStr) } }
|
* .init(deviceId = App.deviceId, activateMap = ACTIVE_MAP)
|
||||||
* .onHeartbeat { frame, data ->
|
* .onHeartbeat { frame, data ->
|
||||||
* data.weights.forEach { Log.d("Scale", "秤${it.index}: ${it.grams}g") }
|
* data.weights.forEach { Log.d("Scale", "秤${it.index}: ${it.grams}g") }
|
||||||
* }
|
* }
|
||||||
* .onStatus { frame, data ->
|
|
||||||
* Log.d("Scale", "锁1状态: ${data.lock1Lock}")
|
|
||||||
* }
|
|
||||||
* .onLog { msg -> Log.d("Scale", msg) }
|
* .onLog { msg -> Log.d("Scale", msg) }
|
||||||
*
|
*
|
||||||
* // 在串口数据回调中调用
|
* // 打开串口并启动接收
|
||||||
* ScaleManager.instance.onDataReceived(hexData)
|
* lifecycleScope.launch {
|
||||||
|
* if (ScaleManager.open()) {
|
||||||
|
* ScaleManager.startReceive { data ->
|
||||||
|
* // 主线程前置钩子,处理看门狗等副作用
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* // 主动发送指令
|
* // 主动发送指令
|
||||||
* ScaleManager.instance.sendCmd(ScaleManager.instance.buildGetStatusCmd())
|
* ScaleManager.sendCmd(ScaleManager.buildGetStatusCmd())
|
||||||
*
|
*
|
||||||
* // 页面销毁时释放回调(不影响其他页面注册的回调,若需完全释放则调用 release())
|
* // 页面销毁时释放
|
||||||
|
* runBlocking { ScaleManager.release() }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class ScaleManager {
|
object ScaleManager {
|
||||||
|
|
||||||
companion object {
|
/** 串口端口名,默认 /dev/ttyS1 */
|
||||||
/** 默认单例,适用于单设备场景;多设备场景请自行实例化 */
|
var portName: String = "/dev/ttyS1"
|
||||||
@JvmStatic
|
/** 串口波特率,默认 115200 */
|
||||||
val instance: ScaleManager by lazy { ScaleManager() }
|
var baudRate: Int = 115200
|
||||||
}
|
|
||||||
|
|
||||||
private val parser = ScaleFrameParser()
|
private val parser = ScaleFrameParser()
|
||||||
private var sendAction: ((String) -> Unit)? = null
|
private var serialPort: AndroidSerialPort? = null
|
||||||
|
private var scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
/** 激活码映射表:key=deviceId,value=完整激活应答帧十六进制字符串 */
|
/** 当前设备激活码,非空时收到激活请求自动应答 */
|
||||||
private var activateMap: Map<String, String> = emptyMap()
|
private var activateCode: String = ""
|
||||||
|
|
||||||
/** 当前设备ID,用于激活码查表 */
|
|
||||||
private var currentDeviceId: String = ""
|
|
||||||
|
|
||||||
/** 心跳应答:心跳上报间隔(秒),默认5秒 */
|
/** 心跳应答:心跳上报间隔(秒),默认5秒 */
|
||||||
var heartbeatInterval: Int = 5
|
var heartbeatInterval: Int = 5
|
||||||
@@ -91,25 +101,78 @@ class ScaleManager {
|
|||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化,注入串口发送函数与激活码映射表
|
* 初始化,设置设备激活码
|
||||||
*
|
*
|
||||||
* @param sendAction 发送十六进制字符串指令的函数,在该函数内完成实际串口写入
|
* @param activateCode 当前设备的激活应答帧十六进制字符串,非空时收到激活请求自动应答
|
||||||
* @param deviceId 当前设备ID,用于激活码查表
|
|
||||||
* @param activateMap 激活码映射表:key=deviceId,value=完整激活应答帧十六进制字符串;
|
|
||||||
* 传入后终端激活请求将由 SDK 内部自动应答,无需再注册 onActivate 回调
|
|
||||||
* @return this,支持链式调用
|
* @return this,支持链式调用
|
||||||
*/
|
*/
|
||||||
fun init(
|
fun init(activateCode: String = ""): ScaleManager {
|
||||||
sendAction: (String) -> Unit,
|
this.activateCode = activateCode
|
||||||
deviceId: String = "",
|
|
||||||
activateMap: Map<String, String> = emptyMap()
|
|
||||||
): ScaleManager {
|
|
||||||
this.sendAction = sendAction
|
|
||||||
this.currentDeviceId = deviceId
|
|
||||||
this.activateMap = activateMap
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// 串口生命周期
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开串口并启动接收,在回调注册完成后调用(链式末尾)
|
||||||
|
*
|
||||||
|
* @param preHook 每帧数据到达时的前置钩子(主线程),用于看门狗计时、UI副作用等
|
||||||
|
* @return true=串口打开成功并已启动接收,false=串口打开失败
|
||||||
|
*/
|
||||||
|
suspend fun start(preHook: ((String) -> Unit)? = null): Boolean {
|
||||||
|
if (!openPort()) return false
|
||||||
|
startReceive(preHook)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun openPort(): Boolean {
|
||||||
|
if (!scope.isActive) {
|
||||||
|
scope = CoroutineScope(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
serialPort = SerialPortFactory.create().apply {
|
||||||
|
open(
|
||||||
|
portName = portName,
|
||||||
|
baudRate = baudRate,
|
||||||
|
dataBits = 8,
|
||||||
|
stopBits = 1,
|
||||||
|
parity = SerialPort.PARITY_NONE
|
||||||
|
)
|
||||||
|
} as AndroidSerialPort?
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startReceive(preHook: ((String) -> Unit)? = null) {
|
||||||
|
scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
try {
|
||||||
|
val flow = serialPort?.read()
|
||||||
|
flow?.collect { buffer ->
|
||||||
|
val data = buffer.toHexString2().uppercase(Locale.ROOT)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
preHook?.invoke(data)
|
||||||
|
onDataReceived(data)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
log("串口数据处理异常: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// 数据接收入口
|
// 数据接收入口
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -243,11 +306,18 @@ class ScaleManager {
|
|||||||
* @param hexStr 完整帧十六进制字符串,含协议头尾
|
* @param hexStr 完整帧十六进制字符串,含协议头尾
|
||||||
*/
|
*/
|
||||||
fun sendCmd(hexStr: String) {
|
fun sendCmd(hexStr: String) {
|
||||||
val action = sendAction ?: run {
|
if (!scope.isActive) {
|
||||||
log("警告:ScaleManager 尚未初始化,请先调用 init(sendAction)")
|
log("警告:ScaleManager 串口未打开或已释放")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
action(hexStr)
|
scope.launch {
|
||||||
|
try {
|
||||||
|
serialPort?.write(hexStr.hexToBytes())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
log("发送命令失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -352,7 +422,10 @@ class ScaleManager {
|
|||||||
* 释放所有资源:清空回调列表、发送函数及帧缓冲
|
* 释放所有资源:清空回调列表、发送函数及帧缓冲
|
||||||
* 通常在 Activity/Fragment 销毁或不再使用时调用
|
* 通常在 Activity/Fragment 销毁或不再使用时调用
|
||||||
*/
|
*/
|
||||||
fun release() {
|
suspend fun release() {
|
||||||
|
scope.cancel()
|
||||||
|
serialPort?.close()
|
||||||
|
serialPort = null
|
||||||
heartbeatListeners.clear()
|
heartbeatListeners.clear()
|
||||||
statusListeners.clear()
|
statusListeners.clear()
|
||||||
lockListeners.clear()
|
lockListeners.clear()
|
||||||
@@ -364,9 +437,7 @@ class ScaleManager {
|
|||||||
activateListeners.clear()
|
activateListeners.clear()
|
||||||
rawListeners.clear()
|
rawListeners.clear()
|
||||||
logListeners.clear()
|
logListeners.clear()
|
||||||
sendAction = null
|
activateCode = ""
|
||||||
activateMap = emptyMap()
|
|
||||||
currentDeviceId = ""
|
|
||||||
parser.reset()
|
parser.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,7 +471,7 @@ class ScaleManager {
|
|||||||
*/
|
*/
|
||||||
private fun handleActivate(frame: ScaleFrame, rawHex: String) {
|
private fun handleActivate(frame: ScaleFrame, rawHex: String) {
|
||||||
log("收到[终端激活]指令,终端ID:${frame.terminalId},固件版本:${frame.firmwareVersion},原始数据:$rawHex")
|
log("收到[终端激活]指令,终端ID:${frame.terminalId},固件版本:${frame.firmwareVersion},原始数据:$rawHex")
|
||||||
val activeCmd = activateMap[currentDeviceId]
|
val activeCmd = activateCode
|
||||||
if (!activeCmd.isNullOrBlank()) {
|
if (!activeCmd.isNullOrBlank()) {
|
||||||
sendCmd(activeCmd)
|
sendCmd(activeCmd)
|
||||||
log("激活码自动应答:$activeCmd")
|
log("激活码自动应答:$activeCmd")
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package com.shuwei.intelligent.shelves.serial
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.shuwei.intelligent.shelves.utils.hexToBytes
|
|
||||||
import com.shuwei.intelligent.shelves.utils.toHexString2
|
|
||||||
import io.github.jeadyx.jserialport.AndroidSerialPort
|
|
||||||
import io.github.jeadyx.jserialport.SerialPort
|
|
||||||
import io.github.jeadyx.jserialport.SerialPortFactory
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
object SerialPortManager {
|
|
||||||
|
|
||||||
private const val PORT_NAME: String = "/dev/ttyS1"
|
|
||||||
private const val BAUD_RATE: Int = 115200
|
|
||||||
private var serialPort: AndroidSerialPort? = null
|
|
||||||
|
|
||||||
// private var serialPort: SerialPort? = null
|
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
// 打开串口
|
|
||||||
suspend fun open(): Boolean {
|
|
||||||
return try {
|
|
||||||
serialPort = SerialPortFactory.create().apply {
|
|
||||||
open(
|
|
||||||
portName = PORT_NAME,
|
|
||||||
baudRate = BAUD_RATE,
|
|
||||||
dataBits = 8,
|
|
||||||
stopBits = 1,
|
|
||||||
parity = SerialPort.PARITY_NONE
|
|
||||||
)
|
|
||||||
} as AndroidSerialPort?
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送数据
|
|
||||||
suspend fun send(data: String): Boolean {
|
|
||||||
return try {
|
|
||||||
serialPort?.write(data.hexToBytes())
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动接收协程
|
|
||||||
fun startReceive(callback: (String) -> Unit) {
|
|
||||||
scope.launch {
|
|
||||||
while (isActive) {
|
|
||||||
try {
|
|
||||||
val flow = serialPort?.read()
|
|
||||||
flow?.collect { buffer ->
|
|
||||||
val data = buffer.toHexString2().uppercase(Locale.ROOT)
|
|
||||||
callback(data)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
delay(50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭串口
|
|
||||||
suspend fun close() {
|
|
||||||
scope.cancel()
|
|
||||||
serialPort?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user