Ver código fonte

1. 优化蓝牙连接方案
2. 优化下发作业票方式

bjb 1 mês atrás
pai
commit
cb9a398ea9

+ 7 - 0
app/src/main/java/com/iscs/bozzys/api/ApiRequest.kt

@@ -378,4 +378,11 @@ object ApiRequest {
         return requestApi { api.createTicket(ticket) }
     }
 
+    /**
+     * 归还钥匙
+     */
+    suspend fun returnKey(params: UpdateReturn): Result<Response<ReturnInfo>> {
+        return requestApi { api.returnKey(params) }
+    }
+
 }

+ 10 - 0
app/src/main/java/com/iscs/bozzys/api/ApiService.kt

@@ -342,4 +342,14 @@ interface ApiService {
         @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
     ): Response<Boolean>
 
+    /**
+     * 归还钥匙
+     */
+    @Headers("Content-Type: application/json")
+    @POST("/admin-api/isc/work-handle/updateKeyBack")
+    suspend fun returnKey(
+        @Body body: UpdateReturn,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<ReturnInfo>
+
 }

+ 2 - 0
app/src/main/java/com/iscs/bozzys/api/IsolationPoint.kt

@@ -4,6 +4,8 @@ import kotlinx.serialization.Serializable
 
 /**
  * 隔离点位
+ *
+ * @param status 状态 0-初始 1-已上锁 2-已解锁
  */
 @Serializable
 data class IsolationPoint(

+ 20 - 0
app/src/main/java/com/iscs/bozzys/api/ReturnInfo.kt

@@ -0,0 +1,20 @@
+package com.iscs.bozzys.api
+
+import kotlinx.serialization.Serializable
+
+/**
+ * 归还设备返回信息
+ *
+ * @param workName      作业名称
+ * @param nodeName      节点名称
+ * @param orderNo       作业编号
+ * @param backStatus    归还状态
+ *                      0-失败 1-成功
+ */
+@Serializable
+data class ReturnInfo(
+    val workName: String,
+    val nodeName: String,
+    val orderNo: String,
+    val backStatus: String
+)

+ 35 - 12
app/src/main/java/com/iscs/bozzys/ui/pages/detail/task/PageDetailTask.kt

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -75,8 +76,11 @@ class PageDetailTask : PageBase() {
     // 页面携带数据对象
     private lateinit var task: Task
 
-    private lateinit var nfcAdapter: NfcAdapter
-    private lateinit var nfcPendingIntent: PendingIntent
+    // NFC适配器
+    private var nfcAdapter: NfcAdapter? = null
+
+    // 处理NFC事件携带的跳转方式
+    private var nfcPendingIntent: PendingIntent? = null
     private val vm: VMDetailTask by viewModels()
 
 
@@ -96,12 +100,14 @@ class PageDetailTask : PageBase() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         nfcAdapter = NfcAdapter.getDefaultAdapter(this)
-        nfcPendingIntent = PendingIntent.getActivity(
-            this,
-            0,
-            Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
-            PendingIntent.FLAG_MUTABLE
-        )
+        if (nfcAdapter != null) {
+            nfcPendingIntent = PendingIntent.getActivity(
+                this,
+                0,
+                Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
+                PendingIntent.FLAG_MUTABLE
+            )
+        }
         val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
             arrayOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT)
         } else {
@@ -113,12 +119,12 @@ class PageDetailTask : PageBase() {
 
     override fun onResume() {
         super.onResume()
-        nfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, null, null)
+        nfcAdapter?.enableForegroundDispatch(this, nfcPendingIntent, null, null)
     }
 
     override fun onPause() {
         super.onPause()
-        nfcAdapter.disableForegroundDispatch(this)
+        nfcAdapter?.disableForegroundDispatch(this)
     }
 
     override fun onNewIntent(intent: Intent) {
@@ -405,13 +411,30 @@ class PageDetailTask : PageBase() {
                             verticalArrangement = Arrangement.Center,
                             horizontalAlignment = Alignment.CenterHorizontally
                         ) {
+                            Spacer(
+                                modifier = Modifier
+                                    .padding(end = 5.dp)
+                                    .size(8.dp)
+                                    .align(Alignment.End)
+                                    .background(color = if (it.status == "1") Color.Red else Color.Green, shape = RoundedCornerShape(50))
+                            )
                             AsyncImage(
                                 it.pointIcon,
                                 contentDescription = null,
-                                modifier = Modifier.size(40.dp),
+                                modifier = Modifier
+                                    .size(40.dp)
+                                    .offset(y = (-5).dp),
                                 contentScale = ContentScale.Fit
                             )
-                            Text(it.pointName ?: "", fontSize = 12.sp, lineHeight = 12.sp, modifier = Modifier.padding(top = 10.dp), color = Text)
+                            Text(
+                                it.pointName ?: "",
+                                fontSize = 12.sp,
+                                lineHeight = 12.sp,
+                                modifier = Modifier
+                                    .offset(y = (-5).dp)
+                                    .padding(top = 10.dp),
+                                color = Text
+                            )
                         }
                     }
                 }

+ 31 - 6
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailTask.kt

@@ -4,13 +4,18 @@ import android.nfc.Tag
 import androidx.lifecycle.viewModelScope
 import com.iscs.bozzys.api.ApiRequest
 import com.iscs.bozzys.api.ApiRequest.getResponse
+import com.iscs.bozzys.api.ApiRequest.isCodeOk
 import com.iscs.bozzys.api.Attachment
 import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.Key
+import com.iscs.bozzys.api.KeyNfc
+import com.iscs.bozzys.api.KeyTicket
 import com.iscs.bozzys.api.Lock
+import com.iscs.bozzys.api.LockNfc
 import com.iscs.bozzys.api.Node
 import com.iscs.bozzys.api.Task
 import com.iscs.bozzys.api.TaskFormInfo
+import com.iscs.bozzys.api.Ticket
 import com.iscs.bozzys.event.RefreshEvent
 import com.iscs.bozzys.event.RefreshEventBus
 import com.iscs.bozzys.ui.common.VMBase
@@ -54,9 +59,16 @@ class VMDetailTask : VMBase() {
 
     private var locks: ArrayList<Lock> = arrayListOf()
 
+    /**
+     * 添加钥匙数据监听器
+     */
     private val onTaskChangeListener = object : OnTaskStatusChangeListener() {
-        override fun onSendTicketSuccess() {
-            super.onSendTicketSuccess()
+
+        /**
+         * 作业任务下发成功
+         */
+        override fun onSendTicketSuccess(mac: String) {
+            super.onSendTicketSuccess(mac)
             viewModelScope.launch {
                 val node = _state.value.node
                 val keyList = _state.value.keys
@@ -74,19 +86,29 @@ class VMDetailTask : VMBase() {
                 } else {
                     // 刷新页面
                     getTaskFormInfo(this@VMDetailTask.task)
-                    BleTask.removeDeviceUsed(mac)
                 }
             }
         }
+
+        override fun onReadTicketSuccess(mac: String, ticket: KeyTicket) {
+            super.onReadTicketSuccess(mac, ticket)
+            val node = _state.value.node
+            val nfc = keys.find { it.macAddress == mac }?.keyNfc ?: ""
+            if ((node.keys?.any { it.keyNfc == nfc } ?: false)) {
+                // 刷新页面
+                getTaskFormInfo(this@VMDetailTask.task, false)
+            }
+        }
+
     }
 
     /**
      * 获取任务表单信息
      */
-    fun getTaskFormInfo(task: Task) {
+    fun getTaskFormInfo(task: Task, showLoading: Boolean = true) {
         this.task = task
         viewModelScope.launch {
-            loading.emit(StateLoading(show = true))
+            loading.emit(StateLoading(show = showLoading))
             ApiRequest.getTaskInfoByNodeId(task.nodeId).onSuccess {
                 val taskInfo = it.data
                 if (taskInfo == null) {
@@ -185,7 +207,8 @@ class VMDetailTask : VMBase() {
             _state.value = _state.value.copy(deviceInfo = "所需设备" to "请将参与作业设备靠近PDA识别区进行录入")
             // 根据点位个数构造所需钥匙和挂锁数量
             val locks = mutableListOf<Lock>()
-            node.points?.forEach { locks += Lock() }
+            // 如果是隔离,需要进行挂锁录入
+            if (node.type == "isolation") node.points?.forEach { locks += Lock() }
             _state.value = _state.value.copy(keys = mutableListOf(Key()), locks = locks)
         }
     }
@@ -308,6 +331,7 @@ class VMDetailTask : VMBase() {
         val node = _state.value.node
         LogUtil.d("PointInfo", "points -> ${node.points}, keys -> ${node.keys}, locks -> ${node.locks}")
         if (node.type == "isolation") { // 隔离
+            if (node.points?.all { it.status == "1" } ?: false) return false
             // 如果初始化还未配置钥匙和锁具,这里是要显示取设备的
             if (node.keys.isNullOrEmpty() || node.locks.isNullOrEmpty() || node.points.isNullOrEmpty()) return true
             // 未取出或已归还钥匙数
@@ -316,6 +340,7 @@ class VMDetailTask : VMBase() {
             val unPointsCount = node.points.filter { it.status != "1" }.size
             return unTakeKeyCount != 0 && unPointsCount != 0
         } else if (node.type == "releaseIsolation") { // 解除隔离
+            if (node.points?.all { it.status == "2" } ?: false) return false
             // 解除隔离按钮只有所有共锁人都解锁后可进行
             // 是否所有共锁人都已解除共锁
             val isCoUnlocked = node.nodeUserList?.filter { user -> user.type == "jtcolocker" }?.all { user -> user.status == "2" } ?: false

+ 27 - 1
app/src/main/java/com/iscs/bozzys/utils/ble/BleManager.kt

@@ -103,6 +103,10 @@ class BleManager(
                 LogUtil.i(TAG, "gattCallback onConnectionStateChange() gatt disconnected")
                 if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
                     this@BleManager.doneConnect?.resume(BleConnectResult(false), null)
+                    // 执行断开连接操作
+                    gatt.close()
+                    device = null
+                    this@BleManager.gatt = null
                 }
             }
         }
@@ -141,6 +145,29 @@ class BleManager(
             LogUtil.i(TAG, "gattCallback onDescriptorRead() data write success")
         }
 
+        override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
+            super.onCharacteristicChanged(gatt, characteristic)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) return
+            LogUtil.w(TAG, "gattCallback <--- ${characteristic?.value?.byteArrayToHexString(" ")}")
+            val data = characteristic?.value ?: byteArrayOf()
+            var key = ""
+            receiverPool.forEach { item ->
+                if (data.byteArrayToHexString().startsWith(item.key.split("_")[1])) {
+                    // 找到指定响应体
+                    key = item.key
+                    return@forEach
+                }
+            }
+            if (key.isNotEmpty()) {
+                val deferred = receiverPool.remove(key)
+                if (deferred != null && !deferred.isCompleted) {
+                    val spl = key.split("_")
+                    val rspCodeLen = spl[1].length / 2
+                    deferred.complete(BleFrame(spl[0].hexToByteArray(), data.copyOfRange(rspCodeLen, data.size), spl[1].hexToByteArray()))
+                }
+            }
+        }
+
         override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
             super.onCharacteristicChanged(gatt, characteristic, value)
             LogUtil.w(TAG, "gattCallback <--- ${value.byteArrayToHexString(" ")}")
@@ -230,7 +257,6 @@ class BleManager(
     fun disconnect() {
         device?.let {
             gatt?.disconnect()
-            gatt?.close()
         }
     }
 

+ 38 - 3
app/src/main/java/com/iscs/bozzys/utils/ble/BleTask.kt

@@ -10,6 +10,9 @@ import android.bluetooth.le.ScanFilter
 import android.bluetooth.le.ScanResult
 import android.bluetooth.le.ScanSettings
 import com.iscs.bozzys.Entry
+import com.iscs.bozzys.api.ApiRequest
+import com.iscs.bozzys.api.ApiRequest.getResponse
+import com.iscs.bozzys.api.KeyTicket
 import com.iscs.bozzys.utils.LogUtil
 import com.iscs.bozzys.utils.ble.BleFrameExt.buildBLEDisconnectCMD
 import com.iscs.bozzys.utils.ble.BleFrameExt.buildBLEGetPowerCMD
@@ -31,6 +34,7 @@ import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.serialization.json.Json
 
 /**
  * 用于管理蓝牙钥匙设备的任务读取操作
@@ -168,6 +172,26 @@ object BleTask {
                 pkgList.forEach { datas += it.pkgData }
                 val ticketJson = String(datas)
                 LogUtil.i("BleTask", "[${device.address}] 读取钥匙作业 -> $ticketJson")
+                // 解析作业票
+                val json = Json { ignoreUnknownKeys = true }
+                try {
+                    // 处理作业票完成情况
+                    val ticket = json.decodeFromString<KeyTicket>(ticketJson)
+                    LogUtil.i("BleTask", "[${device.address}] 作业信息解析中")
+                    // 先找到蓝牙指定的设备NFC
+                    val keys = ApiRequest.getKeyList(mutableMapOf("page" to 1, "pageSize" to -1)).getOrElse { it.getResponse() }
+                    keys.data?.list?.find { it.macAddress == device.address }?.let { key ->
+                        val params = ticket.getReturnParams(key.keyNfc)
+                        // 执行归还数据核对
+                        LogUtil.i("BleTask", "[${device.address}] 归还钥匙,提交的数据 -> $params")
+                        val keyRsp = ApiRequest.returnKey(params).getOrElse { it.getResponse() }
+                        LogUtil.i("BleTask", "[${device.address}] 归还钥匙,返回的数据 -> ${keyRsp.data}")
+                        // 归还钥匙接口调用结果验证
+                        listener.forEach { it.onReadTicketSuccess(device.address, ticket) }
+                    }
+                } catch (e: Exception) {
+                    LogUtil.i("BleTask", "[${device.address}] 解析作业异常 -> $e")
+                }
             }
             val disRet = bm.writeByResponse(token.buildBLEDisconnectCMD()).getDisconnectResult()
             LogUtil.d("BleTask", "[${device.address}] 断开蓝牙连接:${disRet == 1}")
@@ -213,7 +237,7 @@ object BleTask {
             val disRet = bm.writeByResponse(token.buildBLEDisconnectCMD()).getDisconnectResult()
             LogUtil.d("BleTask", "断开蓝牙连接:${disRet == 1}")
             removeDeviceUsed(device.address)
-            listener.forEach { it.onSendTicketSuccess() }
+            listener.forEach { it.onSendTicketSuccess(device.address) }
         }
         bm.disconnect()
     }
@@ -225,11 +249,22 @@ object BleTask {
  */
 abstract class OnTaskStatusChangeListener {
 
-    open fun onSendTicketSuccess() {
+    /**
+     * 用于更新下发作业票的状态
+     *
+     * @param mac   蓝牙MAC地址
+     */
+    open fun onSendTicketSuccess(mac: String) {
 
     }
 
-    open fun onReadTicketSuccess() {
+    /**
+     * 读取作业票成功
+     *
+     * @param mac
+     * @param ticket
+     */
+    open fun onReadTicketSuccess(mac: String, ticket: KeyTicket) {
 
     }