Explorar o código

refactor(更新) :
- WebSocket 消息处理增强:
- 新增处理 `rebootMachine` (硬件重启)、`rebootApp` (软件重启)、`updateApp` (应用更新)、`dockConfig` (基座配置) 和 `dockPort` (端口配置) 的逻辑。
- 应用更新时,显示带进度的下载弹窗,并通过 `LzTekUtils` 调用系统 API 进行静默安装。
- 基座和端口配置接收到后会保存并通过重启应用生效。
- 新增 `LzTekUtils` 工具类,封装了获取 MAC 地址、重启设备和安装 APK 等系统 SDK 功能。
- 新增 `InstallReceiver`,用于监听应用安装和更新事件,并在完成后延时启动应用。
- 移除 `StompManager` 类及其相关逻辑。
- 串口管理 (`PortManager`):
- 移除了打开串口失败时的 Toast 和音频提示。
- `openCtrlBord` 方法中移除了硬编码的调试端口配置。
- Toast 工具类 (`ToastUtils`) 重构:
- 改为 object 单例。
- 增加在非主线程调用时自动切换到主线程的逻辑。
- 始终使用 ApplicationContext,避免内存泄漏和 `BadTokenException`。
- 增加内部 try-catch 块,防止因 Android 7.1.x 系统 Bug 导致的崩溃。
- 调整依赖库版本:`SIKExtension` 更新至 `1.1.76`。
- 移除 `ModBusController` 中未使用的 `updateSwitchStatus` 和 `BusinessManager` 中的 `updateAllBuckleStatus` 和 `updateSwitchStatus` 方法。
- `SwitchStatusPresenter` 中临时移除了随机设置电机状态的逻辑,现在根据实际数据设置。
- `MotorMapInfoRespVO` 中 `status` 字段改为可变类型 (`var`)。
- `CrashUtil` 中移除了程序异常时的 Toast 提示。
- `SwitchStatusFragment` 中移除了地图加载失败时的 Toast 提示。
- `ArcsoftTestActivity` 中移除了人脸引擎初始化失败时的 Toast 提示。
- `PresentationLoginActivity` 中移除了在页面不可见时刷卡的 Toast 提示。
- `WorkshopFragment` 中移除了地图标记点击时的 Toast 提示。
- `PresentationActivity` 中移除了部分调试和未使用的代码。
- `AndroidManifest.xml` 中注册了 `InstallReceiver` 并移除了 `SwitchStatusFragment` 的 activity 声明。
- 新增了 `apk_downloading` 和 `restart_after_install` 字符串资源。

周文健 hai 1 mes
pai
achega
13ce605d01
Modificáronse 24 ficheiros con 279 adicións e 199 borrados
  1. 1 1
      app/build.gradle
  2. BIN=BIN
      app/libs/sdkapi.jar
  3. 14 4
      app/src/main/AndroidManifest.xml
  4. 5 14
      app/src/main/java/com/grkj/iscs_mars/BusinessManager.kt
  5. 1 0
      app/src/main/java/com/grkj/iscs_mars/MyApplication.kt
  6. 0 14
      app/src/main/java/com/grkj/iscs_mars/modbus/ModBusController.kt
  7. 0 13
      app/src/main/java/com/grkj/iscs_mars/modbus/PortManager.kt
  8. 1 1
      app/src/main/java/com/grkj/iscs_mars/model/vo/map/MotorMapInfoRespVO.kt
  9. 0 14
      app/src/main/java/com/grkj/iscs_mars/presentation/PresentationActivity.kt
  10. 0 1
      app/src/main/java/com/grkj/iscs_mars/presentation/PresentationLoginActivity.kt
  11. 54 0
      app/src/main/java/com/grkj/iscs_mars/receivers/InstallReceiver.kt
  12. 0 1
      app/src/main/java/com/grkj/iscs_mars/util/CrashUtil.kt
  13. 33 0
      app/src/main/java/com/grkj/iscs_mars/util/LzTekUtils.kt
  14. 0 115
      app/src/main/java/com/grkj/iscs_mars/util/StompManager.kt
  15. 30 14
      app/src/main/java/com/grkj/iscs_mars/util/ToastUtils.kt
  16. 0 1
      app/src/main/java/com/grkj/iscs_mars/view/activity/test/face/arcsoft/ArcsoftTestActivity.kt
  17. 0 1
      app/src/main/java/com/grkj/iscs_mars/view/fragment/SwitchStatusFragment.kt
  18. 0 1
      app/src/main/java/com/grkj/iscs_mars/view/fragment/WorkshopFragment.kt
  19. 3 3
      app/src/main/java/com/grkj/iscs_mars/view/presenter/SwitchStatusPresenter.kt
  20. 0 1
      app/src/main/java/com/grkj/iscs_mars/view/widget/CustomSwitchStationLayer.kt
  21. 131 0
      app/src/main/java/com/grkj/iscs_mars/websocket/MessageBusinessManager.kt
  22. 2 0
      app/src/main/res/values-en/strings.xml
  23. 2 0
      app/src/main/res/values-zh/strings.xml
  24. 2 0
      app/src/main/res/values/strings.xml

+ 1 - 1
app/build.gradle

@@ -168,7 +168,7 @@ dependencies {
 
     implementation 'io.github.razerdp:BasePopup:3.2.1'
 
-    implementation 'com.github.SilverIceKey:SIKExtension:1.1.72'
+    implementation 'com.github.SilverIceKey:SIKExtension:1.1.76'
     implementation 'com.github.liangjingkanji:BRV:1.6.1'
     implementation("com.github.SilverIceKey:SIKCronJob:1.0.5")
 }

BIN=BIN
app/libs/sdkapi.jar


+ 14 - 4
app/src/main/AndroidManifest.xml

@@ -51,10 +51,6 @@
             android:name=".view.activity.HomeActivity"
             android:exported="false"
             android:windowSoftInputMode="adjustPan|adjustResize" />
-        <activity
-            android:name=".view.fragment.SwitchStatusFragment"
-            android:exported="false"
-            android:windowSoftInputMode="adjustPan|adjustResize" />
         <activity
             android:name=".view.activity.LoginActivity"
             android:exported="true"
@@ -120,6 +116,20 @@
                 <action android:name="android.media.AUDIO_BECOMEING_NOISY" />
             </intent-filter>
         </receiver>
+        <receiver
+            android:name=".receivers.InstallReceiver"
+            android:exported="true">
+            <intent-filter>
+                <!-- 首次安装完成(不是替换安装) -->
+                <action android:name="android.intent.action.PACKAGE_ADDED" />
+                <data android:scheme="package" />
+            </intent-filter>
+            <intent-filter>
+                <!-- 本应用升级完成 -->
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+            </intent-filter>
+        </receiver>
+
     </application>
 
 </manifest>

+ 5 - 14
app/src/main/java/com/grkj/iscs_mars/BusinessManager.kt

@@ -322,6 +322,11 @@ object BusinessManager {
      * 链接底座
      */
     fun connectDock(isNeedInit: Boolean = false) {
+        val dockData = SPUtils.getDockConfig(SIKCore.getApplication())
+        LogUtil.i("基座数据:${dockData}")
+        if (dockData?.isEmpty() == true || dockData == "[]") {
+            return
+        }
         ModBusController.interruptReadTrashBinStatus(false)
         ModBusController.start(MyApplication.instance!!.applicationContext)
         ModBusController.unregisterListener(MyApplication.instance!!.applicationContext)
@@ -659,20 +664,6 @@ object BusinessManager {
         }
     }
 
-    /**
-     * 更新所有锁仓状态
-     */
-    fun updateAllBuckleStatus(done: () -> Unit) {
-        ModBusController.updateAllBuckleStatus(done)
-    }
-
-    /**
-     * 更新开关状态
-     */
-    fun updateSwitchStatus(done: () -> Unit) {
-        ModBusController.updateSwitchStatus(done)
-    }
-
     /**
      * 钥匙归还提示确认弹窗,当前策略:作业票未完成是否强制上传数据
      */

+ 1 - 0
app/src/main/java/com/grkj/iscs_mars/MyApplication.kt

@@ -16,6 +16,7 @@ import com.grkj.iscs_mars.util.NetHttpManager
 import com.grkj.iscs_mars.util.SPUtils
 import com.grkj.iscs_mars.util.log.LogUtil
 import com.grkj.iscs_mars.websocket.WebSocketConfig
+import com.lztek.toolkit.Lztek
 import com.sik.cronjob.managers.CronJobManager
 import com.sik.sikcore.SIKCore
 

+ 0 - 14
app/src/main/java/com/grkj/iscs_mars/modbus/ModBusController.kt

@@ -317,20 +317,6 @@ object ModBusController {
         }
     }
 
-    /**
-     * 更新开关状态
-     */
-    fun updateSwitchStatus(done: () -> Unit) {
-        modBusManager?.mSlaveAddressList?.find { it == (0xA1).toByte() }?.let {
-            modBusManager?.sendTo(it, MBFrame.READ_BUCKLE_STATUS) { res ->
-                LogUtil.d("****************************************************************************")
-                // 过滤非空的数据,重置slaveCount
-                // 不再使用slaveCount,改用地址池
-                switchStatus(res, done)
-            }
-        }
-    }
-
     /**
      * 第9,10锁位卡扣状态
      */

+ 0 - 13
app/src/main/java/com/grkj/iscs_mars/modbus/PortManager.kt

@@ -121,11 +121,6 @@ class PortManager private constructor(
             } catch (e: Exception) {
                 blocked = false
                 LogUtil.e("异常 : ${e.message}")
-//                Tip.toast("串口[${port}, ${bps}, ${usb}]打开失败[${e.javaClass.simpleName}]${e.message}")
-//                synchronized(Tip) {
-//                    Tip.audioNum(port)
-//                    Tip.audio(R.raw.unconnect_port)
-//                }
                 return null
             }
         }
@@ -171,14 +166,6 @@ class PortManager private constructor(
          */
         @WorkerThread
         fun openCtrlBord(ctx: Context): PortManager? {
-            // TODO 端口号待定:大屏调试设备-1,小屏调试设备-0
-//            val port = 4
-//            val bps = 115200
-//            val usb = true
-//            LogUtil.i("主控板 port = ${port}, bps = ${bps}, usb = $usb")
-//            return open(port, bps, usb)
-
-
             val port = SPUtils.getPortConfig(ctx)
             val bps = 115200
             LogUtil.i("主控板 port = $port, bps = $bps")

+ 1 - 1
app/src/main/java/com/grkj/iscs_mars/model/vo/map/MotorMapInfoRespVO.kt

@@ -33,6 +33,6 @@ data class MotorMapInfoRespVO(
 
         val pointSerialNumber: String?,
 
-        val status: String?,
+        var status: String?,
     )
 }

+ 0 - 14
app/src/main/java/com/grkj/iscs_mars/presentation/PresentationActivity.kt

@@ -110,20 +110,6 @@ class PresentationActivity :
             ToastUtils.tip("移动柜的锁数量不够2个")
             return
         }
-//        val lockIdxList = lockList.map { it.idx } as MutableList
-
-//        // 蓝牙钥匙
-//        val keyList = ModBusController.getKeyByDockType(DeviceConst.DOCK_TYPE_PORTABLE)
-//        if (keyList.isNullOrEmpty()) {
-//            ToastUtils.tip("没有找到钥匙")
-//            return
-//        }
-//        val rfid = keyList[0].rfid
-//        if (rfid.isNullOrEmpty()) {
-//            ToastUtils.tip("没有找到钥匙的RFID")
-//            return
-//        }
-
         // 创建工单
         if (mBinding?.siPersonLock?.mSelectIdx == null) {
             ToastUtils.tip("请选择上锁人")

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/presentation/PresentationLoginActivity.kt

@@ -78,7 +78,6 @@ class PresentationLoginActivity : BaseActivity<ActivityPresentationLoginBinding>
                                             val rfid = res.copyOfRange(3, 11).toHexStrings(false).removeLeadingZeros()
                                             LogUtil.i("卡片RFID : $rfid")
                                             if(window.decorView.visibility != View.VISIBLE) {
-//                                            ToastUtils.tip("当前页面不可见")
                                                 val current = ActivityUtils.currentActivity()
                                                 if (current is LockerActivity) {
                                                     current.showDlg(rfid)

+ 54 - 0
app/src/main/java/com/grkj/iscs_mars/receivers/InstallReceiver.kt

@@ -0,0 +1,54 @@
+package com.grkj.iscs_mars.receivers
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.grkj.iscs_mars.util.log.LogUtil
+import com.grkj.iscs_mars.view.activity.LoginActivity
+
+/**
+ * APK 安装/更新接收器(只负责“确保能拉起一次”)
+ * - 不做任何去重/进程判断
+ * - 每次收到都重设一次闹钟,但用相同 PendingIntent 覆盖,最终只会启动一次
+ */
+class InstallReceiver : BroadcastReceiver() {
+
+    override fun onReceive(context: Context, intent: Intent?) {
+        val action = intent?.action ?: return
+
+        val isFirstInstall = action == Intent.ACTION_PACKAGE_ADDED &&
+                !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
+                intent.data?.schemeSpecificPart == context.packageName
+
+        val isSelfReplaced = action == Intent.ACTION_MY_PACKAGE_REPLACED
+
+        if (!isFirstInstall && !isSelfReplaced) return
+
+        LogUtil.i("InstallReceiver,schedule app launch, action=$action")
+        scheduleSingleLaunch(context)
+    }
+
+    /** 用相同的 Intent + requestCode + flags 覆盖旧任务,保证最终只触发一次 */
+    private fun scheduleSingleLaunch(context: Context) {
+        val start = Intent(context, LoginActivity::class.java).apply {
+            // 固定 action,确保 PendingIntent 唯一,可覆盖
+            action = "com.grkj.iscs_mars.ACTION_LAUNCH_AFTER_INSTALL"
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        }
+
+        // 关键:FLAG_CANCEL_CURRENT/UPDATE_CURRENT 其一,用同一 requestCode=0
+        val pi = PendingIntent.getActivity(
+            context,
+            0,
+            start,
+            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+        )
+
+        val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+        val triggerAt = System.currentTimeMillis() + 2000L // 2s 后拉起
+        // 用精确 + 允息,最大化命中
+        am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAt, pi)
+    }
+}

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/util/CrashUtil.kt

@@ -66,7 +66,6 @@ class CrashUtil : Thread.UncaughtExceptionHandler {
         }
         Thread {
             Looper.prepare()
-            ToastUtils.tip("很抱歉,程序出现异常,即将退出")
             Looper.loop()
         }.start()
         //收集设备参数信息

+ 33 - 0
app/src/main/java/com/grkj/iscs_mars/util/LzTekUtils.kt

@@ -0,0 +1,33 @@
+package com.grkj.iscs_mars.util
+
+import com.lztek.toolkit.Lztek
+import com.sik.sikcore.SIKCore
+import java.io.File
+
+/**
+ * 系统SDK Api
+ */
+object LzTekUtils {
+    private val lzTek: Lztek by lazy { Lztek.create(SIKCore.getApplication()) }
+
+    /**
+     * 获取以太网Mac地址
+     */
+    fun getEthMacAddress(): String {
+        return lzTek.ethMac
+    }
+
+    /**
+     * 重启机器
+     */
+    fun rebootMachine() {
+        lzTek.hardReboot()
+    }
+
+    /**
+     * 安装Apk
+     */
+    fun installApk(apk: File) {
+        lzTek.installApplication(apk.absolutePath)
+    }
+}

+ 0 - 115
app/src/main/java/com/grkj/iscs_mars/util/StompManager.kt

@@ -1,115 +0,0 @@
-package com.grkj.iscs_mars.util
-
-import androidx.lifecycle.MutableLiveData
-import cn.zhxu.okhttps.OkHttps
-import cn.zhxu.stomp.Header
-import cn.zhxu.stomp.Stomp
-import com.grkj.iscs_mars.model.UrlConsts
-import com.grkj.iscs_mars.util.log.LogUtil
-import java.util.concurrent.Executors
-import java.util.concurrent.ScheduledExecutorService
-import java.util.concurrent.TimeUnit
-
-class StompManager {
-    var myStomp: Stomp?=null
-    var scheduledExecutorService:ScheduledExecutorService?=null
-    var cacheTime=0L
-    var cacheDisconnectTime=0L
-    var topicRefuseStr:String?=null
-    val serverData = MutableLiveData<String>()
-
-    companion object{
-        fun getInstance() = InstanceHelper.sSingle
-    }
-
-    object InstanceHelper{
-        val sSingle = StompManager()
-    }
-
-    fun startStomp(){
-        if (myStomp?.isConnected==true){return}
-        LogUtil.d("stomp连接开始!")
-        NetHttpManager.getInstance().requestTokenAndRefreshIfExpired {
-            if (!it.isNullOrBlank()){
-                myStomp=Stomp.over(OkHttps.webSocket(UrlConsts.WEB_SOCKET).heatbeat(10,10))
-                    .setOnConnected {
-                        scheduledExecutorService?.shutdownNow()
-                        scheduledExecutorService=null
-                        LogUtil.d("stomp连接完成!")
-                        cacheTime=System.currentTimeMillis()
-                        if (cacheDisconnectTime>20*1000){
-//                            NetStateUtil.instance.setNetState(0)
-                        }
-                        topicRefuse()
-                    }
-                    .setOnDisconnected {
-                        LogUtil.d("stomp连接断开:${it.reason}")
-                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
-//                        NetStateUtil.instance.setNetState(2)
-                        reConnectStomp()
-                    }
-                    .setOnException {
-                        LogUtil.d("stomp连接异常:${it.toString()}")
-                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
-//                        NetStateUtil.instance.setNetState(2)
-                        reConnectStomp()
-                    }
-                    .setOnError {
-                        LogUtil.d("stomp连接错误:${it.toString()}")
-                        cacheDisconnectTime=System.currentTimeMillis()-cacheTime
-//                        NetStateUtil.instance.setNetState(2)
-                        reConnectStomp()
-                    }
-                    .connect(listOf(Header("login",it),Header("host","/eiotyun-kitchen-refuse")))
-            }else{
-                reConnectStomp()
-            }
-        }
-    }
-
-    fun topicRefuse(){
-//        FenBiDeNetApi.getInstance().getPlatformInfo {
-//            it?.let {
-//                topicRefuseStr?.let {
-//                    myStomp?.untopic(it)
-//                }
-//                topicRefuseStr="/p-${it.platformId}-a.machine.${AppUtils.getSerialNo(FenBiDeApplication.instance)}"
-//                //创建设备
-//                myStomp?.topic(topicRefuseStr){
-//                    EventBus.getDefault().post(StompCreatEvent(it.payload=="1",it.payload))
-//                }
-//                //配置参数
-//                myStomp?.topic("${topicRefuseStr}.refresh"){
-//                    try {
-//                        EventBus.getDefault().post(Gson().fromJson(it.payload, StompRefreshEvent::class.java))
-//                    }catch (e:Exception){
-//                        ToastUtils.tip(FenBiDeApplication.instance.getString(R.string.str_stomp_exception,it.payload))
-//                        LogUtil.d("refresh:${FenBiDeApplication.instance.getString(R.string.str_stomp_exception,it.payload)}",e)
-//                    }
-//                }
-//            }?:let {
-//                try {
-//                    Thread.sleep(10000)
-//                }catch (e:Exception){}
-//                topicRefuse()
-//            }
-//        }
-    }
-
-    fun stopStomp(){
-        topicRefuseStr?.let {
-            myStomp?.untopic(it)
-        }
-        myStomp?.disconnect(true)
-    }
-
-    fun reConnectStomp(){
-        scheduledExecutorService?:let {
-            scheduledExecutorService= Executors.newScheduledThreadPool(1)
-            scheduledExecutorService?.scheduleWithFixedDelay(Runnable {
-                startStomp()
-            },0,30000, TimeUnit.MILLISECONDS)
-        }
-    }
-
-}

+ 30 - 14
app/src/main/java/com/grkj/iscs_mars/util/ToastUtils.kt

@@ -1,25 +1,41 @@
 package com.grkj.iscs_mars.util
 
-import android.content.Context
 import android.os.Looper
 import android.widget.Toast
 import com.grkj.iscs_mars.MyApplication
+import com.grkj.iscs_mars.util.log.LogUtil
 
-class ToastUtils private constructor(context: Context) : Toast(context) {
+/**
+ * 安全 Toast 工具类
+ * - 自动切主线程
+ * - 始终使用 ApplicationContext,避免 BadTokenException
+ * - 内部 try/catch,防止 7.1.x 系统 Bug 崩溃
+ */
+object ToastUtils {
 
-    companion object {
+    fun tip(text: CharSequence?, duration: Int = Toast.LENGTH_SHORT) {
+        if (text.isNullOrEmpty()) return
 
-        fun tip(text: CharSequence?, duration: Int = LENGTH_SHORT) {
-            if (Looper.myLooper() != Looper.getMainLooper()) {
-                Executor.runOnMain {
-                    tip(text)
-                }
-                return
-            }
-            makeText(MyApplication.instance!!, text, duration).show()
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            Executor.runOnMain { showSafeToast(text, duration) }
+        } else {
+            showSafeToast(text, duration)
         }
+    }
+
+    fun tip(textResId: Int, duration: Int = Toast.LENGTH_SHORT) {
+        val ctx = MyApplication.instance?.applicationContext ?: return
+        val str = ctx.getString(textResId)
+        tip(str, duration)
+    }
 
-        fun tip(text: Int, duration: Int = LENGTH_SHORT) =
-            tip(MyApplication.instance?.applicationContext?.resources?.getString(text), duration)
+    private fun showSafeToast(text: CharSequence, duration: Int) {
+        try {
+            val ctx = MyApplication.instance?.applicationContext ?: return
+            Toast.makeText(ctx, text, duration).show()
+        } catch (e: Throwable) {
+            // 避免 BadToken 崩溃,打印日志即可
+            LogUtil.d("ToastUtils,showSafeToast failed: ${e.message}")
+        }
     }
-}
+}

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/view/activity/test/face/arcsoft/ArcsoftTestActivity.kt

@@ -106,7 +106,6 @@ class ArcsoftTestActivity : BaseActivity<ActivityArcsoftTestBinding>(), OnGlobal
         )
         LogUtil.i("initEngine:  init: $afCode")
         if (afCode != ErrorInfo.MOK) {
-//            ToastUtils.tip(getString(R.string.init_failed, afCode))
         }
     }
 

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/view/fragment/SwitchStatusFragment.kt

@@ -235,7 +235,6 @@ class SwitchStatusFragment(private val vp2: ViewPager2?) :
             }
 
             override fun onMapLoadFail() {
-                ToastUtils.Companion.tip("onMapLoadFail")
             }
         })
     }

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/view/fragment/WorkshopFragment.kt

@@ -170,7 +170,6 @@ class WorkshopFragment(val changePage: (PageChangeBO) -> Unit) :
                     markLayer = CustomMarkLayer(mBinding?.mapview, mPointList)
                     markLayer?.setMarkIsClickListener(object : CustomMarkLayer.MarkIsClickListener {
                         override fun markIsClick(index: Int, btnIndex: Int, isClickIcon: Boolean) {
-//                        ToastUtils.tip(mPointList[index].name + " is selected, btnIndex is " + btnIndex + ", isClickIcon is " + isClickIcon)
                             if (btnIndex == -1) {
                                 changePage(
                                     PageChangeBO(

+ 3 - 3
app/src/main/java/com/grkj/iscs_mars/view/presenter/SwitchStatusPresenter.kt

@@ -129,7 +129,8 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
                     if (yAxisFlip) {
                         worldY = backendH - worldY
                     }
-
+                    // todo 随机电机状态
+//                    pt.status = Random.nextInt(2).toString()
                     val switchStatus =
                         if (pt.status == "1") CustomSwitchStationLayer.STATUS_ON
                         else CustomSwitchStationLayer.STATUS_OFF
@@ -146,8 +147,7 @@ class SwitchStatusPresenter : BasePresenter<ISwitchStatusView>() {
                         pointSerialNumber = pt.pointSerialNumber,
                         isSelected = false,
                         pointNfc = pt.pointNfc ?: "",
-//                        status = switchStatus
-                        status = Random.nextInt(2)
+                        status = switchStatus
                     )
                     points += p
                     byId[p.entityId] = p

+ 0 - 1
app/src/main/java/com/grkj/iscs_mars/view/widget/CustomSwitchStationLayer.kt

@@ -15,7 +15,6 @@ import com.grkj.iscs_mars.modbus.DockBean
 import com.onlylemi.mapview.library.MapView
 import com.onlylemi.mapview.library.layer.MapBaseLayer
 import kotlin.math.abs
-import kotlin.random.Random
 
 /**
  * 自定义开关层(保持对外 API 兼容):

+ 131 - 0
app/src/main/java/com/grkj/iscs_mars/websocket/MessageBusinessManager.kt

@@ -1,7 +1,22 @@
 package com.grkj.iscs_mars.websocket
 
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.grkj.iscs_mars.R
+import com.grkj.iscs_mars.util.AppUtils
+import com.grkj.iscs_mars.util.CommonUtils
+import com.grkj.iscs_mars.util.LzTekUtils
+import com.grkj.iscs_mars.util.SPUtils
 import com.grkj.iscs_mars.util.log.LogUtil
+import com.grkj.iscs_mars.view.dialog.LoadingProgressDialog
+import com.grkj.iscs_mars.view.fragment.DockTestFragment
+import com.sik.sikandroid.activity.ActivityTracker
+import com.sik.sikcore.SIKCore
 import com.sik.sikcore.shell.ShellUtils
+import com.sik.sikcore.thread.ThreadUtils
+import com.sik.siknet.http.httpDownloadFile
+import com.sik.siknet.http.interceptor.ProgressListener
+import kotlinx.coroutines.delay
 
 /**
  * 消息业务处理
@@ -12,6 +27,41 @@ object MessageBusinessManager {
      */
     const val MESSAGE_TYPE_COMMAND_TO_LOG = "commandToLog"
 
+    /**
+     * 重启硬件
+     */
+    const val MESSAGE_TYPE_REBOOT_MACHINE = "rebootMachine"
+
+    /**
+     * 重启软件
+     */
+    const val MESSAGE_TYPE_REBOOT_APP = "rebootApp"
+
+    /**
+     * 更新app
+     */
+    const val MESSAGE_TYPE_UPDATE_APP = "updateApp"
+
+    /**
+     * 基座配置
+     */
+    const val MESSAGE_TYPE_DOCK_CONFIG = "dockConfig"
+
+    /**
+     * 端口配置
+     */
+    const val MESSAGE_TYPE_DOCK_PORT = "dockPort"
+
+    /**
+     * 加载弹窗
+     */
+    private var loadingProgressDialog: LoadingProgressDialog? = null
+
+    /**
+     * 最后更新时间
+     */
+    private var lastUpdateTime: Long = System.currentTimeMillis()
+
     /**
      * 处理消息
      */
@@ -24,6 +74,87 @@ object MessageBusinessManager {
                 val result = ShellUtils.execCmd(messageEntity.content!!)
                 LogUtil.i("执行结果:${result.successMsg}")
             }
+
+            MESSAGE_TYPE_REBOOT_MACHINE -> {
+                LzTekUtils.rebootMachine()
+            }
+
+            MESSAGE_TYPE_REBOOT_APP -> {
+                CommonUtils.restartApp(SIKCore.getApplication())
+            }
+
+            MESSAGE_TYPE_UPDATE_APP -> {
+                val apkDownloadPath = messageEntity.content ?: return
+                ThreadUtils.runOnIO {
+                    val downloadApk = apkDownloadPath.httpDownloadFile(progressListener = object :
+                        ProgressListener {
+                        override fun update(
+                            bytesRead: Long,
+                            contentLength: Long,
+                            done: Boolean
+                        ) {
+                            if (System.currentTimeMillis() - lastUpdateTime < 1000 && !done) {
+                                return
+                            }
+                            lastUpdateTime = System.currentTimeMillis()
+                            ThreadUtils.runOnMain {
+                                ActivityTracker.getCurrentActivity()?.let {
+                                    if (loadingProgressDialog == null) {
+                                        loadingProgressDialog = LoadingProgressDialog(it)
+                                    }
+                                    if (loadingProgressDialog?.isShowing == false && !done) {
+                                        loadingProgressDialog?.show()
+                                    }
+                                    val percent = if (contentLength > 0) {
+                                        LogUtil.i(
+                                            "下载进度:${
+                                                String.format(
+                                                    "%.2f",
+                                                    bytesRead * 100f / contentLength
+                                                )
+                                            },${bytesRead},${contentLength}"
+                                        )
+                                        String.format("%.2f", bytesRead * 100f / contentLength)
+                                    } else {
+                                        "--" // 或者直接显示 "…" 表示未知
+                                    }
+                                    if (!done) {
+                                        loadingProgressDialog?.setProgress(
+                                            "${CommonUtils.getStr(R.string.apk_downloading)},${percent}%"
+                                        )
+                                    } else {
+                                        loadingProgressDialog?.setProgress("${CommonUtils.getStr(R.string.restart_after_install)}")
+                                    }
+                                }
+                            }
+                        }
+                    })
+                    downloadApk?.let {
+                        LogUtil.i("调用静默安装API进行安装:${it.absolutePath}")
+                        LzTekUtils.installApk(it)
+                    }
+                }
+            }
+
+            MESSAGE_TYPE_DOCK_CONFIG -> {
+                val dockConfig = messageEntity.content ?: return
+                try {
+                    Gson().fromJson<List<DockTestFragment.DockTestBean>>(
+                        dockConfig,
+                        object : TypeToken<List<DockTestFragment.DockTestBean>>() {}.type
+                    )
+                    SPUtils.saveDockConfig(SIKCore.getApplication(), dockConfig)
+                    CommonUtils.restartApp(SIKCore.getApplication())
+                } catch (e: Exception) {
+                    LogUtil.i("解析dock配置失败:${e.message}")
+                }
+            }
+
+            MESSAGE_TYPE_DOCK_PORT -> {
+                val dockPort = messageEntity.content ?: return
+                SPUtils.savePortConfig(SIKCore.getApplication(), dockPort)
+                CommonUtils.restartApp(SIKCore.getApplication())
+            }
         }
     }
 }

+ 2 - 0
app/src/main/res/values-en/strings.xml

@@ -413,4 +413,6 @@
     <string name="switch_status_tv">Switch Status:</string>
     <string name="device_inputting">Hardware inputting……</string>
     <string name="motor_map">Motor Switch Map</string>
+    <string name="apk_downloading">Apk downloading……</string>
+    <string name="restart_after_install">Auto start after install</string>
 </resources>

+ 2 - 0
app/src/main/res/values-zh/strings.xml

@@ -413,4 +413,6 @@
     <string name="switch_status_tv">电机状态:</string>
     <string name="device_inputting">硬件录入中……</string>
     <string name="motor_map">电机开关布局图</string>
+    <string name="apk_downloading">安装包下载中……</string>
+    <string name="restart_after_install">安装完成之后自动启动</string>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -413,4 +413,6 @@
     <string name="switch_status_tv">电机状态:</string>
     <string name="device_inputting">硬件录入中……</string>
     <string name="motor_map">电机开关布局图</string>
+    <string name="apk_downloading">安装包下载中……</string>
+    <string name="restart_after_install">安装完成之后自动启动</string>
 </resources>