10 Commits
Author SHA1 Message Date
lvmeng 8145eb588b refactor(serial): 将串口配置常量重命名为大写格式
- 将 portName 常量重命名为 PORT_NAME
- 将 baudRate 常量重命名为 BAUD_RATE
- 更新代码中对这些常量的引用以使用新的命名约定
- 保持串口通信功能完全不变
2026-05-21 09:01:27 +08:00
mazengfei c91d5656aa 修改旧版设备秤数量导致数据显示不准的问题 2026-05-20 16:50:53 +08:00
mazengfei 532b22a5c6 设备支持3列显示,统一处理显示和提交数据顺序 2026-05-20 15:36:28 +08:00
mazengfei e9d04bfb70 优化启动页面样式 2026-04-28 16:52:54 +08:00
mazengfei afba5220b3 首页设置禁止返回按钮 2026-04-28 16:41:21 +08:00
lvmeng 4ace12c6c7 style(ui): 调整加载动画尺寸并替换为矢量资源
- 将加载动画ImageView尺寸从160dp调整为120dp
- 替换loading_spinner为新的loading矢量资源
- 添加loading.xml矢量动画资源文件
- 注释掉HomeActivity中的临时报告对话框逻辑
- 简化HomeActivity中的RespData类型转换代码
2026-04-28 15:37:47 +08:00
mazengfei ee36869003 优化 2026-04-07 17:19:12 +08:00
lvmengandClaude Sonnet 4.6 c23c215941 fix: 修复 noDataWatchdogTask 潜在的内存泄漏问题
在 Runnable 中添加生命周期检查,防止 Activity 销毁后任务继续执行。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:58:22 +08:00
lvmengandClaude Sonnet 4.6 3d67dd2c2d refactor: 优化货架列表转换逻辑并支持奇数长度处理
- 移除 subList 创建,直接通过索引访问元素
- 修复奇数长度列表的处理逻辑
- 保持单次循环,提升性能

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:53:00 +08:00
mazengfeiandClaude Sonnet 4.6 133f309d4b refactor: 统一时钟管理并优化状态栏显示逻辑
- 在 BaseActivity 中实现统一的时钟管理机制,避免各子类重复实现
- LogActivity 和 ShelfActivity 覆写 onClockTick() 自定义状态栏显示
- HomeActivity 移除重复的时间更新逻辑,启用右侧状态栏菜单
- 提取协议常量到 ProtocolConstants,提高代码可维护性
- 优化开锁逻辑判断,改为基于 deviceId 的动态判断
- 新增右侧状态栏弹出菜单布局文件

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:07:41 +08:00
14 changed files with 529 additions and 139 deletions
+1 -1
View File
@@ -43,7 +43,7 @@
android:name=".activity.ShelfActivity" android:name=".activity.ShelfActivity"
android:screenOrientation="portrait" android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" /> tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" /> <!-- <activity android:name="com.shuwei.intelligent.shelves.activity.SettingActivity" />-->
<activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" /> <activity android:name="com.shuwei.intelligent.shelves.activity.LogActivity" />
<receiver <receiver
@@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.activity.addCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -29,12 +30,15 @@ import com.shuwei.intelligent.shelves.net.NetViewModel
import com.shuwei.intelligent.shelves.net.RespData import com.shuwei.intelligent.shelves.net.RespData
import com.shuwei.intelligent.shelves.net.UiState import com.shuwei.intelligent.shelves.net.UiState
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.FOOTER
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEADER
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LEFT_SHELF_OPEN_CMD import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LEFT_SHELF_OPEN_CMD
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.RIGHT_SHELF_OPEN_CMD import com.shuwei.intelligent.shelves.serial.ProtocolConstants.RIGHT_SHELF_OPEN_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.SerialFrameParser import com.shuwei.intelligent.shelves.serial.SerialFrameParser
import com.shuwei.intelligent.shelves.serial.SerialPortManager import com.shuwei.intelligent.shelves.serial.SerialPortManager
import com.shuwei.intelligent.shelves.serial.SerialProtocolHandler import com.shuwei.intelligent.shelves.serial.SerialProtocolHandler
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
import com.shuwei.intelligent.shelves.utils.ext.gone import com.shuwei.intelligent.shelves.utils.ext.gone
@@ -43,18 +47,16 @@ import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.utils.ext.visible import com.shuwei.intelligent.shelves.utils.ext.visible
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class HomeActivity : BaseActivity() { class HomeActivity : BaseActivity() {
companion object { companion object {}
private const val MM_DD_EEEE__HH_MM_SS = "MM月dd日 EEEE HH:mm:ss"
} override val enableRightStatusMenu: Boolean = true
// 货架列表数据 // 货架列表数据
private val list: MutableList<ShelfModel> = mutableListOf() private val list: MutableList<ShelfModel> = mutableListOf()
@@ -68,8 +70,8 @@ class HomeActivity : BaseActivity() {
// 当前湿度 // 当前湿度
private var showHumidity = "0" private var showHumidity = "0"
// 是否在点击货架时发送开锁指令;有门锁的部署点(如1楼餐厅)为 true,无门锁的部署点(如401室)为 false // 仅特定 deviceId 的部署点需要发送开锁指令
private var isNeedOpenLock = true private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751"
private var deviceName = "" private var deviceName = ""
@@ -78,11 +80,12 @@ class HomeActivity : BaseActivity() {
private val viewModel: NetViewModel by viewModels() private val viewModel: NetViewModel by viewModels()
// 串口协议解析器,通过回调通知本 Activity // 串口协议解析器,通过回调通知本 Activity
private val serialProtocolHandler = SerialProtocolHandler(object : SerialProtocolHandler.Callback { private val serialProtocolHandler =
SerialProtocolHandler(object : SerialProtocolHandler.Callback {
override fun onSendCmd(cmd: String) = sendCmd(cmd) override fun onSendCmd(cmd: String) = sendCmd(cmd)
override fun onWeightUpdated(shelfNo: Int, weight: Int) { override fun onWeightUpdated(shelfNo: Int, weight: Int) {
if (shelfNo in 1..10 && list.size == 10) { if (shelfNo in 1..list.size) {
list.firstOrNull { it.deviceNo == shelfNo }?.let { model -> list.firstOrNull { it.deviceNo == shelfNo }?.let { model ->
model.weight = weight.toDouble() model.weight = weight.toDouble()
val pos = list.indexOf(model) val pos = list.indexOf(model)
@@ -95,16 +98,17 @@ class HomeActivity : BaseActivity() {
override fun onTempReport(logMsg: String) { override fun onTempReport(logMsg: String) {
updateLeftStatus(deviceName) updateLeftStatus(deviceName)
if (tipDialog == null) { log(logMsg)
tipDialog = CommonDialog(this@HomeActivity).apply { // if (tipDialog == null) {
dialogTitle = "温馨提示" // tipDialog = CommonDialog(this@HomeActivity).apply {
confirmBlock = { logMsg.copyTextToClipboard(this@HomeActivity) } // dialogTitle = "温馨提示"
} // confirmBlock = { logMsg.copyTextToClipboard(this@HomeActivity) }
} // }
if (tipDialog?.isShowing == false) { // }
tipDialog?.dialogContent = logMsg // if (tipDialog?.isShowing == false) {
tipDialog?.show() // tipDialog?.dialogContent = logMsg
} // tipDialog?.show()
// }
} }
override fun onLog(message: String) = log(message) override fun onLog(message: String) = log(message)
@@ -119,7 +123,9 @@ class HomeActivity : BaseActivity() {
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
setBackground() setBackground()
setContentView(binding.root) setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) {
}
updateLeftStatus("") updateLeftStatus("")
initRecyclerView() initRecyclerView()
@@ -168,7 +174,7 @@ class HomeActivity : BaseActivity() {
launch { launch {
viewModel.updateOverdueStateUiState.collect { state -> viewModel.updateOverdueStateUiState.collect { state ->
if (state is UiState.Success<*>) { if (state is UiState.Success<*>) {
val respData = state.data as? com.shuwei.intelligent.shelves.net.RespData<*> val respData = state.data as? RespData<*>
if (respData == null) { if (respData == null) {
log("updateOverdueState: 数据类型错误,期望 RespData") log("updateOverdueState: 数据类型错误,期望 RespData")
return@collect return@collect
@@ -201,6 +207,13 @@ class HomeActivity : BaseActivity() {
viewModel.getShelfList(deviceId = App.deviceId) viewModel.getShelfList(deviceId = App.deviceId)
} }
private val defDeviceList = listOf(
//原410货柜
"7a991439-3a12-3ef7-809b-c0258b839473",
//原1楼餐厅货柜
"4787e213-90ab-3e32-88e0-ac271a937751"
)
private fun getTokenSuccess(data: RespData<*>) { private fun getTokenSuccess(data: RespData<*>) {
log("getTokenSuccess: $data") log("getTokenSuccess: $data")
data.data?.let { data.data?.let {
@@ -227,18 +240,14 @@ class HomeActivity : BaseActivity() {
updateLeftStatus(deviceName) updateLeftStatus(deviceName)
App.canteenId = shelfResult.placeId App.canteenId = shelfResult.placeId
val tempList = shelfResult.containerGoodsList var tempList = shelfResult.containerGoodsList
if (tempList.isNullOrEmpty()) { if (tempList.isNullOrEmpty()) {
loadEmptyView() loadEmptyView()
return return
} }
list.clear() list.clear()
val subList01 = tempList.subList(0, tempList.size / 2) tempList = GridLayoutTool.initListSort(tempList)
val subList02 = tempList.subList(tempList.size / 2, tempList.size) list.addAll(tempList)
repeat(tempList.size / 2) { index ->
list.add(subList01[index].also { it.deviceId = App.deviceId })
list.add(subList02[index].also { it.deviceId = App.deviceId })
}
shelfAdapter.notifyDataSetChanged() shelfAdapter.notifyDataSetChanged()
} }
@@ -271,12 +280,16 @@ class HomeActivity : BaseActivity() {
*/ */
private val noDataWatchdogTask = object : Runnable { private val noDataWatchdogTask = object : Runnable {
override fun run() { override fun run() {
// 生命周期检查,防止 Activity 销毁后继续执行
if (isFinishing || isDestroyed) return
val elapsed = System.currentTimeMillis() - lastDataReceivedTime val elapsed = System.currentTimeMillis() - lastDataReceivedTime
if (elapsed >= 5 * 60 * 1000L) { if (elapsed >= 5 * 60 * 1000L) {
if (noDataWarningDialog == null) { if (noDataWarningDialog == null) {
noDataWarningDialog = CommonDialog(this@HomeActivity).apply { noDataWarningDialog = CommonDialog(this@HomeActivity).apply {
dialogTitle = "设备提示" dialogTitle = "设备提示"
dialogContent = "长时间未收到数据,若无法开门或秤重量不更新情况,请考虑断电重启设备" dialogContent =
"长时间未收到数据,若无法开门或秤重量不更新情况,请考虑断电重启设备"
} }
} }
if (noDataWarningDialog?.isShowing == false) { if (noDataWarningDialog?.isShowing == false) {
@@ -304,7 +317,7 @@ class HomeActivity : BaseActivity() {
noDataWarningDialog?.dismiss() noDataWarningDialog?.dismiss()
noDataWarningDialog = null noDataWarningDialog = null
SerialPortManager.close() runBlocking { SerialPortManager.close() }
saveTaskJob?.cancel() saveTaskJob?.cancel()
overdueTaskJob?.cancel() overdueTaskJob?.cancel()
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
@@ -325,7 +338,7 @@ class HomeActivity : BaseActivity() {
private fun onShelfItemClicked(position: Int) { private fun onShelfItemClicked(position: Int) {
shelfIndex = position shelfIndex = position
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)
LEFT_SHELF_OPEN_CMD LEFT_SHELF_OPEN_CMD
else else
@@ -363,10 +376,11 @@ class HomeActivity : BaseActivity() {
} }
private fun initRecyclerView() { private fun initRecyclerView() {
binding.rvShelf.run { val layoutManager = GridLayoutTool.getLayoutManager(this@HomeActivity) ?: return
layoutManager = GridLayoutManager(this@HomeActivity, 2, GridLayoutManager.VERTICAL, false) binding.rvShelf.let {
adapter = shelfAdapter it.layoutManager = layoutManager
itemAnimator.let { it.adapter = shelfAdapter
it.itemAnimator.let {
if (it is DefaultItemAnimator) { if (it is DefaultItemAnimator) {
it.supportsChangeAnimations = false it.supportsChangeAnimations = false
} }
@@ -374,31 +388,17 @@ class HomeActivity : BaseActivity() {
} }
} }
private val dateTimeFormatter by lazy { SimpleDateFormat(MM_DD_EEEE__HH_MM_SS, Locale.CHINA) }
private fun updateDateTime() {
updateRightStatus(dateTimeFormatter.format(Date()))
}
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
handler.postDelayed(this, 1000)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
hideStatusBar() hideStatusBar()
handler.post(updateTask)
// 启动无数据看门狗(每30秒检查一次) // 启动无数据看门狗(每30秒检查一次)
handler.postDelayed(noDataWatchdogTask, 30 * 1000L) handler.postDelayed(noDataWatchdogTask, 30 * 1000L)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
handler.removeCallbacks(updateTask)
// 停止无数据看门狗 // 停止无数据看门狗
handler.removeCallbacks(noDataWatchdogTask) handler.removeCallbacks(noDataWatchdogTask)
} }
@@ -438,7 +438,8 @@ class HomeActivity : BaseActivity() {
* 保存定时任务 * 保存定时任务
*/ */
private fun saveGoodsTask() { private fun saveGoodsTask() {
saveTaskJob = taskExecutor.startIntervalTaskWithInitialDelay(1 * 60 * 1000L, 5 * 60 * 1000L) { saveTaskJob =
taskExecutor.startIntervalTaskWithInitialDelay(1 * 60 * 1000L, 5 * 60 * 1000L) {
syncShelfGoodsToServer() syncShelfGoodsToServer()
} }
} }
@@ -477,8 +478,8 @@ class HomeActivity : BaseActivity() {
* 构建 ShelfBody 并提交到服务端(被定时任务与 ShelfActivity 返回共用) * 构建 ShelfBody 并提交到服务端(被定时任务与 ShelfActivity 返回共用)
*/ */
private fun syncShelfGoodsToServer() { private fun syncShelfGoodsToServer() {
log("performSync: list:${list.toJsonString()}") log("performSync显示list:${list.toJsonString()}")
val submitList = list.sortedBy { it.deviceNo } val submitList = GridLayoutTool.submitListSort(list)
submitList.forEach { submitList.forEach {
if (it.goodsId.isNullOrBlank()) { if (it.goodsId.isNullOrBlank()) {
it.weight = it.weightBak it.weight = it.weightBak
@@ -491,7 +492,7 @@ class HomeActivity : BaseActivity() {
it.humidity = showHumidity it.humidity = showHumidity
it.goodsList = submitList it.goodsList = submitList
} }
log("performSync: body:${body.toJsonString()}") log("performSync提交body:${body.toJsonString()}")
viewModel.saveShelfGoodsList(body) viewModel.saveShelfGoodsList(body)
} }
} }
@@ -1,28 +1,53 @@
package com.shuwei.intelligent.shelves.activity package com.shuwei.intelligent.shelves.activity
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.widget.TextView
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import com.shuwei.intelligent.shelves.adapter.LogAdapter import com.shuwei.intelligent.shelves.adapter.LogAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityLogListBinding import com.shuwei.intelligent.shelves.databinding.ActivityLogListBinding
import com.shuwei.intelligent.shelves.dialog.CommonDialog import com.shuwei.intelligent.shelves.dialog.CommonDialog
import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard import com.shuwei.intelligent.shelves.utils.ext.copyTextToClipboard
import com.shuwei.intelligent.shelves.utils.ext.dp
import com.shuwei.intelligent.shelves.utils.ext.gone import com.shuwei.intelligent.shelves.utils.ext.gone
import com.shuwei.intelligent.shelves.utils.ext.startActivity import com.shuwei.intelligent.shelves.utils.ext.startActivity
import com.shuwei.intelligent.shelves.utils.ext.toast import com.shuwei.intelligent.shelves.utils.ext.toast
import com.shuwei.intelligent.shelves.serial.ProtocolConstants
import com.shuwei.intelligent.shelves.utils.ext.visible import com.shuwei.intelligent.shelves.utils.ext.visible
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class LogActivity : BaseActivity() { class LogActivity : BaseActivity() {
companion object { companion object {
const val IS_LOG_DIR = "isLogDir" const val IS_LOG_DIR = "isLogDir"
const val LOG_DATE = "logDate" const val LOG_DATE = "logDate"
const val YYYY_MM_DD__EEEE_HH_MM_SS = "yyyy年MM月dd日 EEEE***HH:mm:ss"
}
/**
* 覆写时钟回调:左侧状态栏显示年月日星期,右侧状态栏显示时分秒
*/
override fun onClockTick() {
val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
val arr = sdf.format(Date()).split("***")
updateLeftStatus(arr[0])
updateRightStatus(arr[1])
} }
private lateinit var binding:ActivityLogListBinding private lateinit var binding:ActivityLogListBinding
private var isLogDir = false private var isLogDir = false
private var logDate = "" private var logDate = ""
private val logList = mutableListOf<String>() private val logList = mutableListOf<String>()
private val originalLogList = mutableListOf<String>() // 原始日志备份
private val logAdapter by lazy { private val logAdapter by lazy {
LogAdapter(logList).apply { LogAdapter(logList).apply {
setOnItemClickListener { _, _, position -> setOnItemClickListener { _, _, position ->
@@ -62,24 +87,20 @@ class LogActivity : BaseActivity() {
logAdapter.notifyDataSetChanged() logAdapter.notifyDataSetChanged()
} }
if (isLogDir) {
logList.addAll(fileLogger.getLogFiles())
} else {
logList.addAll(fileLogger.readLogsByName(logDate))
}
binding.rvLogList.let { binding.rvLogList.let {
it.layoutManager = LinearLayoutManager(this) it.layoutManager = LinearLayoutManager(this)
it.adapter = logAdapter it.adapter = logAdapter
} }
setupSearch()
setupJumpButton()
binding.btnFind.run { binding.btnFind.run {
if (isLogDir) gone() else visible() if (isLogDir) gone() else visible()
} }
binding.btnFind.setOnClickListener { binding.btnFind.setOnClickListener {
if (isLogDir) { if (isLogDir) return@setOnClickListener
return@setOnClickListener val list = logList.filter { it.contains("${ProtocolConstants.HEADER}${ProtocolConstants.CMD_TEMP_REPORT}") }
}
val list = logList.filter { it.contains("${HomeActivity.HEADER}1001") }
if (list.isEmpty()) { if (list.isEmpty()) {
toast("未找到到1001指令") toast("未找到到1001指令")
return@setOnClickListener return@setOnClickListener
@@ -88,6 +109,95 @@ class LogActivity : BaseActivity() {
logList.addAll(list) logList.addAll(list)
logAdapter.notifyDataSetChanged() logAdapter.notifyDataSetChanged()
} }
// 后台加载日志数据
lifecycleScope.launch {
binding.progressBar.visible()
val data = withContext(Dispatchers.IO) {
if (isLogDir) fileLogger.getLogFiles()
else fileLogger.readLogsByName(logDate)
}
logList.addAll(data)
originalLogList.addAll(data)
logAdapter.notifyDataSetChanged()
binding.progressBar.gone()
setupTimeNavigation()
}
}
// 搜索功能
private fun setupSearch() {
binding.etSearch.addTextChangedListener(afterTextChanged = { s ->
val keyword = s?.toString() ?: ""
logList.clear()
logList.addAll(if (keyword.isEmpty()) originalLogList else originalLogList.filter { it.contains(keyword, ignoreCase = true) })
logAdapter.notifyDataSetChanged()
})
}
// 快速跳转功能
private fun setupJumpButton() {
var isAtTop = true
binding.fabJump.setOnClickListener {
if (isAtTop) {
binding.rvLogList.scrollToPosition(logList.size - 1)
} else {
binding.rvLogList.scrollToPosition(0)
}
isAtTop = !isAtTop
}
}
// 时间导航功能
private fun setupTimeNavigation() {
if (isLogDir) {
binding.hsvTimeNav.gone()
return
}
val timeMap = mutableMapOf<String, Int>()
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
originalLogList.forEachIndexed { index, log ->
val timeStr = log.substringAfter("[").substringBefore("]")
try {
val date = dateFormat.parse(timeStr)
date?.let {
val hourKey = SimpleDateFormat("HH:00", Locale.getDefault()).format(it)
if (!timeMap.containsKey(hourKey)) {
timeMap[hourKey] = index
}
}
} catch (e: Exception) {
// 忽略解析失败的日志
}
}
if (timeMap.isEmpty()) {
binding.hsvTimeNav.gone()
return
}
timeMap.toSortedMap().forEach { (hour, position) ->
val btn = TextView(this).apply {
text = hour
textSize = 14f
setTextColor(Color.BLACK)
setPadding(12.dp, 8.dp, 12.dp, 8.dp)
gravity = Gravity.CENTER
setBackgroundColor(Color.parseColor("#E0E0E0"))
setOnClickListener {
binding.rvLogList.scrollToPosition(position)
}
}
val params = android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
android.widget.LinearLayout.LayoutParams.MATCH_PARENT
).apply {
marginEnd = 8.dp
}
binding.llTimeNav.addView(btn, params)
}
} }
} }
@@ -3,8 +3,6 @@ package com.shuwei.intelligent.shelves.activity
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.activity.viewModels import androidx.activity.viewModels
@@ -18,7 +16,7 @@ import com.chad.library.adapter4.util.setOnDebouncedItemClick
import com.scwang.smart.refresh.layout.constant.RefreshState import com.scwang.smart.refresh.layout.constant.RefreshState
import com.shuwei.intelligent.shelves.App import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.R import com.shuwei.intelligent.shelves.R
import com.shuwei.intelligent.shelves.activity.HomeActivity.Companion.DEVICE_INFO_CMD import com.shuwei.intelligent.shelves.serial.ProtocolConstants
import com.shuwei.intelligent.shelves.adapter.SearchAdapter import com.shuwei.intelligent.shelves.adapter.SearchAdapter
import com.shuwei.intelligent.shelves.base.BaseActivity import com.shuwei.intelligent.shelves.base.BaseActivity
import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding import com.shuwei.intelligent.shelves.databinding.ActivityShelfBinding
@@ -148,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(DEVICE_INFO_CMD) sendCmd(ProtocolConstants.DEVICE_INFO_CMD)
lifecycleScope.launch { lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.getGoodsListUiState.collect { state -> viewModel.getGoodsListUiState.collect { state ->
@@ -305,31 +303,23 @@ class ShelfActivity : BaseActivity() {
} }
} }
private fun updateDateTime() { /**
* 覆写时钟回调:左侧状态栏显示年月日星期,右侧状态栏显示时分秒
*/
override fun onClockTick() {
val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA) val sdf = SimpleDateFormat(YYYY_MM_DD__EEEE_HH_MM_SS, Locale.CHINA)
val dateTime = sdf.format(Date()) val arr = sdf.format(Date()).split("***")
val arr = dateTime.split("***")
updateLeftStatus(arr[0]) updateLeftStatus(arr[0])
updateRightStatus(arr[1]) updateRightStatus(arr[1])
} }
private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
handler.postDelayed(this, 1000)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
hideStatusBar() hideStatusBar()
handler.post(updateTask)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
handler.removeCallbacks(updateTask)
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
@@ -371,20 +361,20 @@ class ShelfActivity : BaseActivity() {
val shelfNo = shelfModel?.deviceNo ?: 0 val shelfNo = shelfModel?.deviceNo ?: 0
val hex = shelfNo.toString(16).padStart(2, '0').uppercase() val hex = shelfNo.toString(16).padStart(2, '0').uppercase()
Log.d(TAG, "onCreate: hex=$hex") Log.d(TAG, "onCreate: hex=$hex")
val zeroClearingCmd = "${HomeActivity.HEADER}0401${hex}${HomeActivity.FOOTER}" val zeroClearingCmd = "${ProtocolConstants.HEADER}0401${hex}${ProtocolConstants.FOOTER}"
log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}") log("编号${shelfNo}货架执行清零指令:${zeroClearingCmd}")
sendCmd(zeroClearingCmd) sendCmd(zeroClearingCmd)
sendCmd( // sendCmd(
if (shelfNo in 1..5) HomeActivity.LEFT_SHELF_OPEN_CMD else HomeActivity.RIGHT_SHELF_OPEN_CMD // if (shelfNo in 1..5) ProtocolConstants.LEFT_SHELF_OPEN_CMD else ProtocolConstants.RIGHT_SHELF_OPEN_CMD
) // )
//清零 //清零
realWeight = 0 realWeight = 0
// binding.tvFoodWeight.text = "${realWeight}克" // binding.tvFoodWeight.text = "${realWeight}克"
sendCmd(DEVICE_INFO_CMD) sendCmd(ProtocolConstants.DEVICE_INFO_CMD)
startTime = System.currentTimeMillis() startTime = System.currentTimeMillis()
Loading.show(this@ShelfActivity) Loading.show(this@ShelfActivity)
// window.decorView.postDelayed({ Loading.dismiss() }, 5000) window.decorView.postDelayed({ Loading.dismiss() }, 10000)
} }
KeyboardUtil.hideKeyboard(window.decorView) KeyboardUtil.hideKeyboard(window.decorView)
} }
@@ -22,6 +22,7 @@ import com.shuwei.intelligent.shelves.serial.ProtocolConstants.LIGHT_OPEN
import com.shuwei.intelligent.shelves.serial.SerialPortManager import com.shuwei.intelligent.shelves.serial.SerialPortManager
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.startActivity import com.shuwei.intelligent.shelves.utils.ext.startActivity
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -29,6 +30,9 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import com.shuwei.intelligent.shelves.utils.ext.setOnDoubleClickListener import com.shuwei.intelligent.shelves.utils.ext.setOnDoubleClickListener
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() { open class BaseActivity : AppCompatActivity() {
@@ -64,13 +68,22 @@ open class BaseActivity : AppCompatActivity() {
lightSwitchClick() lightSwitchClick()
binding.tvLeftStatus.setOnDoubleClickListener { binding.tvLeftStatus.setOnDoubleClickListener {
finish() //finish()
val intent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_HOME)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
} }
binding.tvRightStatus.setOnDoubleClickListener { binding.tvRightStatus.setOnDoubleClickListener {
showRightStatusPopup(binding.tvRightStatus) if (enableRightStatusMenu) showRightStatusPopup(binding.tvRightStatus)
} }
} }
/** 是否允许右侧状态栏双击弹出菜单,默认关闭,仅 HomeActivity 开启 */
protected open val enableRightStatusMenu: Boolean = false
fun hideStatusBar() {} fun hideStatusBar() {}
fun statusBarDarkFont() { fun statusBarDarkFont() {
@@ -92,7 +105,7 @@ open class BaseActivity : AppCompatActivity() {
} }
fun updateLeftStatus(data: String) { fun updateLeftStatus(data: String) {
binding.tvLeftStatus.text = data binding.tvLeftStatus.text = data.ifBlank { " " }
} }
fun updateRightStatus(data: String) { fun updateRightStatus(data: String) {
@@ -107,6 +120,26 @@ open class BaseActivity : AppCompatActivity() {
onUserInactive() onUserInactive()
} }
// ---- 右侧状态栏时钟 ----
// 默认时间格式:月日星期 时:分:秒
private val CLOCK_FORMAT = "MM月dd日 EEEE HH:mm:ss"
private val clockFormatter by lazy { SimpleDateFormat(CLOCK_FORMAT, Locale.CHINA) }
/**
* 每秒时钟回调,默认将当前时间显示在右侧状态栏。
* 子类可覆写以自定义格式或同时更新左侧状态栏。
*/
open fun onClockTick() {
updateRightStatus(clockFormatter.format(Date()))
}
private val clockTask = object : Runnable {
override fun run() {
onClockTick()
mHandler.postDelayed(this, 1000)
}
}
/** /**
* 用户超时无操作时回调,默认空实现,子类按需覆写 * 用户超时无操作时回调,默认空实现,子类按需覆写
*/ */
@@ -128,14 +161,18 @@ open class BaseActivity : AppCompatActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// 启动定时器 // 启动超时定时器
mHandler.postDelayed(mTimeoutRunnable, TIME_OUT) mHandler.postDelayed(mTimeoutRunnable, TIME_OUT)
// 启动右侧状态栏时钟
mHandler.post(clockTask)
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
// 取消定时器 // 取消超时定时器
mHandler.removeCallbacks(mTimeoutRunnable) mHandler.removeCallbacks(mTimeoutRunnable)
// 停止右侧状态栏时钟
mHandler.removeCallbacks(clockTask)
} }
fun switchLight(isOpen: Boolean) { fun switchLight(isOpen: Boolean) {
@@ -159,8 +196,8 @@ open class BaseActivity : AppCompatActivity() {
val menuBinding = PopupRightStatusMenuBinding.inflate(LayoutInflater.from(this)) val menuBinding = PopupRightStatusMenuBinding.inflate(LayoutInflater.from(this))
val popup = PopupWindow( val popup = PopupWindow(
menuBinding.root, menuBinding.root,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 120.dp,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT, 101.dp,
true true
) )
menuBinding.tvEnvSwitch.setOnClickListener { menuBinding.tvEnvSwitch.setOnClickListener {
@@ -1,5 +1,7 @@
package com.shuwei.intelligent.shelves.serial package com.shuwei.intelligent.shelves.serial
import com.shuwei.intelligent.shelves.App
/** /**
* 串口通信协议常量定义 * 串口通信协议常量定义
* 统一管理所有协议头、协议尾、各类指令及帧解析偏移量 * 统一管理所有协议头、协议尾、各类指令及帧解析偏移量
@@ -63,7 +65,11 @@ object ProtocolConstants {
const val DEVICE_STATUS_WEIGHT_OFFSET = HEADER.length + 14 const val DEVICE_STATUS_WEIGHT_OFFSET = HEADER.length + 14
/** 货架数量(每侧5格,共10格,协议中12组) */ /** 货架数量(每侧5格,共10格,协议中12组) */
const val SHELF_SENSOR_COUNT = 12 fun shelfSensorCount(): Int {
return if (device2Columns.contains(App.deviceId)) 12
else if (device3Columns.contains(App.deviceId)) 24
else 0
}
/** 每组重量数据字节数(十六进制字符数) */ /** 每组重量数据字节数(十六进制字符数) */
const val BYTES_PER_WEIGHT = 8 const val BYTES_PER_WEIGHT = 8
@@ -72,10 +78,18 @@ object ProtocolConstants {
const val TEMPERATURE_FIELD_LEN = 2 const val TEMPERATURE_FIELD_LEN = 2
/** 心跳帧最小有效长度 */ /** 心跳帧最小有效长度 */
const val HEARTBEAT_MIN_LENGTH = 200 fun heartbeatMinLength(): Int {
return if (device2Columns.contains(App.deviceId)) 200
else if (device3Columns.contains(App.deviceId)) 296
else 0
}
/** 心跳帧中温度字段起始偏移(经协议分析得出,前面含重量+其他传感器数据) */ /** 心跳帧中温度字段起始偏移(经协议分析得出,前面含重量+其他传感器数据) */
const val HEARTBEAT_TEMPERATURE_START = 158 fun heartbeatTemperatureStart(): Int {
return if (device2Columns.contains(App.deviceId)) 158
else if (device3Columns.contains(App.deviceId)) 158+96
else 0
}
/** 激活请求体有效长度 */ /** 激活请求体有效长度 */
const val ACTIVATE_BODY_LENGTH = 44 const val ACTIVATE_BODY_LENGTH = 44
@@ -88,8 +102,11 @@ object ProtocolConstants {
// ---------------------------------------------------------------- // ----------------------------------------------------------------
/** 激活指令(1楼餐厅) */ /** 激活指令(1楼餐厅) */
const val ACTIVE_CMD = // const val ACTIVE_CMD =
"${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}" // "${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}"
// 410激活码
// const val ACTIVE_CMD = "${HEADER}000281C5D70A7D42894EA17650F5D0BB516838E7863C9ABD766C38958AFF0F3C062D${FOOTER}"
/** 左货架开锁指令 */ /** 左货架开锁指令 */
const val LEFT_SHELF_OPEN_CMD = const val LEFT_SHELF_OPEN_CMD =
@@ -119,4 +136,45 @@ object ProtocolConstants {
/** 温控指令 */ /** 温控指令 */
const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}" const val TEMPERATURE_CTRL = "${HEADER}0F01040000${FOOTER}"
/**
* 设备2列,10格
*/
val device2Columns = listOf(
//原410货柜
"7a991439-3a12-3ef7-809b-c0258b839473",
//原1楼餐厅货柜
"4787e213-90ab-3e32-88e0-ac271a937751"
)
/**
* 设备3列,15格,左侧1列竖向编号1、2、3、4、5,右侧2列5行编号为6-7、8-9、10-11、12-13、14-15
*/
val device3ColumnsOrder = listOf(1, 6, 7, 2, 8, 9, 3, 10, 11, 4, 12, 13, 5, 14, 15)
/**
* 设备2列,10格,左侧1列竖向编号1、2、3、4、5,右侧1列6,7,8,9,10
*/
val device2ColumnsOrder = listOf(1, 6, 2, 7, 3, 8, 4, 9, 5, 10)
/**
* 设备3列,15格,左侧1列竖向编号1、2、3、4、5,右侧2列5行编号为6-7、8-9、10-11、12-13、14-15
*/
val device3Columns = listOf(
//新设备1
"d369f9b0-1c5b-3066-ba72-988e1449cecc",
//新设备2
"505ce1b1-facc-3eb2-855a-3dadda5ba358"
)
/** 激活指令mapkey为设备号,value为激活码 */
val ACTIVE_MAP = mutableMapOf(
//原410货柜
"7a991439-3a12-3ef7-809b-c0258b839473" to "${HEADER}000281C5D70A7D42894EA17650F5D0BB516838E7863C9ABD766C38958AFF0F3C062D${FOOTER}",
//原1楼餐厅货柜
"4787e213-90ab-3e32-88e0-ac271a937751" to "${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}",
//新设备1,临时使用1楼餐厅激活码
"d369f9b0-1c5b-3066-ba72-988e1449cecc" to "${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}",
//新设备2,临时使用1楼餐厅激活码
"505ce1b1-facc-3eb2-855a-3dadda5ba358" to "${HEADER}000281C5D70A7D428B44A57454F5DACA241938938B4B9CBD73673E9C8E7FC0C4992D${FOOTER}",
)
} }
@@ -13,8 +13,9 @@ import java.nio.charset.Charset
import java.util.Locale import java.util.Locale
object SerialPortManager { object SerialPortManager {
private const val portName: String = "/dev/ttyS1"
private const val baudRate: Int = 115200 private const val PORT_NAME: String = "/dev/ttyS1"
private const val BAUD_RATE: Int = 115200
private var serialPort: AndroidSerialPort? = null private var serialPort: AndroidSerialPort? = null
// private var serialPort: SerialPort? = null // private var serialPort: SerialPort? = null
@@ -25,8 +26,8 @@ object SerialPortManager {
return try { return try {
serialPort = SerialPortFactory.create().apply { serialPort = SerialPortFactory.create().apply {
open( open(
portName = portName, portName = PORT_NAME,
baudRate = baudRate, baudRate = BAUD_RATE,
dataBits = 8, dataBits = 8,
stopBits = 1, stopBits = 1,
parity = SerialPort.PARITY_NONE parity = SerialPort.PARITY_NONE
@@ -69,7 +70,7 @@ object SerialPortManager {
} }
// 关闭串口 // 关闭串口
fun close() { suspend fun close() {
scope.cancel() scope.cancel()
serialPort?.close() serialPort?.close()
} }
@@ -1,7 +1,8 @@
package com.shuwei.intelligent.shelves.serial package com.shuwei.intelligent.shelves.serial
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.ACTIVATE_BODY_LENGTH import com.shuwei.intelligent.shelves.serial.ProtocolConstants.ACTIVATE_BODY_LENGTH
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.ACTIVE_CMD import com.shuwei.intelligent.shelves.serial.ProtocolConstants.ACTIVE_MAP
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.BYTES_PER_WEIGHT import com.shuwei.intelligent.shelves.serial.ProtocolConstants.BYTES_PER_WEIGHT
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.CMD_ACTIVATE import com.shuwei.intelligent.shelves.serial.ProtocolConstants.CMD_ACTIVATE
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.CMD_CARD_UPLOAD import com.shuwei.intelligent.shelves.serial.ProtocolConstants.CMD_CARD_UPLOAD
@@ -19,10 +20,7 @@ import com.shuwei.intelligent.shelves.serial.ProtocolConstants.DEVICE_STATUS_WEI
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.FIX_BODY_LENGTH import com.shuwei.intelligent.shelves.serial.ProtocolConstants.FIX_BODY_LENGTH
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.FOOTER import com.shuwei.intelligent.shelves.serial.ProtocolConstants.FOOTER
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEADER import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEADER
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEARTBEAT_MIN_LENGTH
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEARTBEAT_TEMPERATURE_START
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEARTBEAT_WEIGHT_OFFSET import com.shuwei.intelligent.shelves.serial.ProtocolConstants.HEARTBEAT_WEIGHT_OFFSET
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.SHELF_SENSOR_COUNT
import com.shuwei.intelligent.shelves.serial.ProtocolConstants.TEMPERATURE_FIELD_LEN import com.shuwei.intelligent.shelves.serial.ProtocolConstants.TEMPERATURE_FIELD_LEN
import com.shuwei.intelligent.shelves.utils.binaryToHex import com.shuwei.intelligent.shelves.utils.binaryToHex
import com.shuwei.intelligent.shelves.utils.hexToBinary import com.shuwei.intelligent.shelves.utils.hexToBinary
@@ -96,8 +94,9 @@ class SerialProtocolHandler(private val callback: Callback) {
if (reqBody.length == ACTIVATE_BODY_LENGTH) { if (reqBody.length == ACTIVATE_BODY_LENGTH) {
terminalId = reqBody.substring(2, 32) terminalId = reqBody.substring(2, 32)
terminalVersion = reqBody.substring(32, 42) terminalVersion = reqBody.substring(32, 42)
callback.onLog("收到[激活]${cmdFlag}指令:${data},终端id:${terminalId},固件版本:${terminalVersion},发送激活命令:$ACTIVE_CMD") val activeCode = ACTIVE_MAP[App.deviceId]?:""
callback.onSendCmd(ACTIVE_CMD) callback.onLog("收到[激活]${cmdFlag}指令:${data},终端id:${terminalId},固件版本:${terminalVersion},发送激活命令:$activeCode")
callback.onSendCmd(activeCode)
} }
} }
@@ -120,7 +119,7 @@ class SerialProtocolHandler(private val callback: Callback) {
private fun handleDeviceStatus(data: String, cmdFlag: String) { private fun handleDeviceStatus(data: String, cmdFlag: String) {
callback.onLog("收到[获取设备状态]${cmdFlag}指令:${data}") callback.onLog("收到[获取设备状态]${cmdFlag}指令:${data}")
val weightStartIndex = DEVICE_STATUS_WEIGHT_OFFSET val weightStartIndex = DEVICE_STATUS_WEIGHT_OFFSET
val weightEndIndex = weightStartIndex + SHELF_SENSOR_COUNT * BYTES_PER_WEIGHT val weightEndIndex = weightStartIndex + ProtocolConstants.shelfSensorCount() * BYTES_PER_WEIGHT
val temperatureEndIndex = weightEndIndex + TEMPERATURE_FIELD_LEN val temperatureEndIndex = weightEndIndex + TEMPERATURE_FIELD_LEN
try { try {
parseWeightData(start = weightStartIndex, end = weightEndIndex, data = data) parseWeightData(start = weightStartIndex, end = weightEndIndex, data = data)
@@ -176,11 +175,11 @@ class SerialProtocolHandler(private val callback: Callback) {
*/ */
private fun parseHeartbeatData(data: String) { private fun parseHeartbeatData(data: String) {
runCatching { runCatching {
if (data.length < HEARTBEAT_MIN_LENGTH) return if (data.length < ProtocolConstants.heartbeatMinLength()) return
val weightStartIndex = HEARTBEAT_WEIGHT_OFFSET val weightStartIndex = HEARTBEAT_WEIGHT_OFFSET
val weightEndIndex = weightStartIndex + SHELF_SENSOR_COUNT * BYTES_PER_WEIGHT val weightEndIndex = weightStartIndex + ProtocolConstants.shelfSensorCount() * BYTES_PER_WEIGHT
parseWeightData(start = weightStartIndex, end = weightEndIndex, data = data) parseWeightData(start = weightStartIndex, end = weightEndIndex, data = data)
val temperatureEndIndex = HEARTBEAT_TEMPERATURE_START + TEMPERATURE_FIELD_LEN val temperatureEndIndex = ProtocolConstants.heartbeatTemperatureStart() + TEMPERATURE_FIELD_LEN
parseFixDataBody(data, temperatureEndIndex) parseFixDataBody(data, temperatureEndIndex)
}.onFailure { it.printStackTrace() } }.onFailure { it.printStackTrace() }
} }
@@ -210,7 +209,11 @@ class SerialProtocolHandler(private val callback: Callback) {
firstByteBinary = "0${firstByteBinary.substring(1)}" firstByteBinary = "0${firstByteBinary.substring(1)}"
val firstByteHex = binaryToHex(firstByteBinary) val firstByteHex = binaryToHex(firstByteBinary)
weightHex = firstByteHex + weightHex.substring(2) weightHex = firstByteHex + weightHex.substring(2)
-1 * (weightHex.toLongOrNull(16)?.toInt() ?: 0) //-1 * (weightHex.toLongOrNull(16)?.toInt() ?: 0)
// 直接用 Long 解析,然后减去 2^32 得到有符号值
val unsigned = weightHex.toLongOrNull(16) ?: 0L
val signed = if (unsigned >= 0x80000000L) unsigned - 0x100000000L else unsigned
signed.toInt()
} }
callback.onWeightUpdated(index, realWeight) callback.onWeightUpdated(index, realWeight)
count += BYTES_PER_WEIGHT count += BYTES_PER_WEIGHT
@@ -0,0 +1,54 @@
package com.shuwei.intelligent.shelves.utils
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.shuwei.intelligent.shelves.App
import com.shuwei.intelligent.shelves.model.ShelfModel
import com.shuwei.intelligent.shelves.serial.ProtocolConstants
object GridLayoutTool {
/**
* 设备2列,10格
*/
private val device2Columns = ProtocolConstants.device2Columns
/**
* 设备3列,15格,左侧1列竖向编号1、2、3、4、5,右侧2列5行编号为6-7、8-9、10-11、12-13、14-15
*/
private val device3Columns = ProtocolConstants.device3Columns
fun getLayoutManager(context: Context): RecyclerView.LayoutManager? {
return if (device2Columns.contains(App.deviceId)) {
GridLayoutManager(context, 2, GridLayoutManager.VERTICAL, false)
} else if (device3Columns.contains(App.deviceId)) {
GridLayoutManager(context, 3, GridLayoutManager.VERTICAL, false)
} else {
null
}
}
/**
* 初始化list显示顺序
*/
fun initListSort(source: List<ShelfModel>): List<ShelfModel> {
val map = source.associateBy { it.deviceNo }
return if (device3Columns.contains(App.deviceId)) {
// 按全局定义的显示顺序取对应数据,找不到则跳过
ProtocolConstants.device3ColumnsOrder.mapNotNull { map[it] }
} else if (device2Columns.contains(App.deviceId)) {
ProtocolConstants.device2ColumnsOrder.mapNotNull { map[it] }
} else {
source
}
}
/**
* 提交接口list顺序
*/
fun submitListSort(source: List<ShelfModel>): List<ShelfModel> {
return source.sortedBy { it.deviceNo }
}
}
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="240dp"
android:height="240dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<!-- 由小到大排列的圆点,构成旋转加载指示器 -->
<path
android:fillColor="#ffffff"
android:pathData="M876.864,782.592c3.264,0 6.272,-3.2 6.272,-6.656c0,-3.456 -3.008,-6.592 -6.272,-6.592c-3.264,0 -6.272,3.2 -6.272,6.592c0,3.456 3.008,6.656 6.272,6.656z" />
<path
android:fillColor="#ffffff"
android:pathData="M736.32,935.936c2.304,2.432 5.568,3.84 8.768,3.84a12.16,12.16 0,0 0,8.832 -3.84a13.76,13.76 0,0 0,0 -18.56a12.224,12.224 0,0 0,-8.832 -3.84a12.16,12.16 0,0 0,-8.768 3.84a13.696,13.696 0,0 0,0 18.56z" />
<path
android:fillColor="#ffffff"
android:pathData="M552.32,1018.24c3.456,3.648 8.32,5.76 13.184,5.76a18.368,18.368 0,0 0,13.184 -5.76a20.608,20.608 0,0 0,0 -27.968a18.368,18.368 0,0 0,-13.184 -5.824a18.368,18.368 0,0 0,-13.184 5.76a20.608,20.608 0,0 0,0 28.032z" />
<path
android:fillColor="#ffffff"
android:pathData="M353.984,1012.48c4.608,4.8 11.072,7.68 17.6,7.68a24.448,24.448 0,0 0,17.536 -7.68a27.456,27.456 0,0 0,0 -37.248a24.448,24.448 0,0 0,-17.536 -7.68a24.448,24.448 0,0 0,-17.6 7.68a27.52,27.52 0,0 0,0 37.184z" />
<path
android:fillColor="#ffffff"
android:pathData="M178.304,920.64c5.76,6.08 13.824,9.6 21.952,9.6a30.592,30.592 0,0 0,22.016 -9.6a34.368,34.368 0,0 0,0 -46.592a30.592,30.592 0,0 0,-22.016 -9.6a30.592,30.592 0,0 0,-21.952 9.6a34.368,34.368 0,0 0,0 46.592z" />
<path
android:fillColor="#ffffff"
android:pathData="M57.152,761.28c6.912,7.36 16.64,11.648 26.368,11.648a36.736,36.736 0,0 0,26.432 -11.584a41.28,41.28 0,0 0,0 -55.936a36.736,36.736 0,0 0,-26.432 -11.584a36.8,36.8 0,0 0,-26.368 11.52a41.28,41.28 0,0 0,0 56z" />
<path
android:fillColor="#ffffff"
android:pathData="M12.736,564.672a42.88,42.88 0,0 0,30.784 13.44a42.88,42.88 0,0 0,30.784 -13.44a48.128,48.128 0,0 0,0 -65.216a42.88,42.88 0,0 0,-30.72 -13.44a42.88,42.88 0,0 0,-30.848 13.44a48.128,48.128 0,0 0,0 65.216z" />
<path
android:fillColor="#ffffff"
android:pathData="M52.544,369.28a48.96,48.96 0,0 0,35.2 15.36a48.96,48.96 0,0 0,35.2 -15.36a54.976,54.976 0,0 0,0 -74.56a48.96,48.96 0,0 0,-35.2 -15.424a48.96,48.96 0,0 0,-35.2 15.424a54.976,54.976 0,0 0,0 74.56z" />
<path
android:fillColor="#ffffff"
android:pathData="M168.32,212.48c10.368,11.008 24.96,17.408 39.68,17.408c14.592,0 29.184,-6.4 39.552,-17.408a61.888,61.888 0,0 0,0 -83.84a55.104,55.104 0,0 0,-39.616 -17.408c-14.656,0 -29.248,6.4 -39.616,17.408a61.888,61.888 0,0 0,0 83.84z" />
<path
android:fillColor="#ffffff"
android:pathData="M337.344,124.8c11.52,12.16 27.712,19.264 43.968,19.264c16.256,0 32.448,-7.04 43.968,-19.264a68.672,68.672 0,0 0,0 -93.184a61.248,61.248 0,0 0,-43.968 -19.264a61.248,61.248 0,0 0,-43.968 19.264a68.736,68.736 0,0 0,0 93.184z" />
<path
android:fillColor="#ffffff"
android:pathData="M526.976,123.712c12.672,13.44 30.528,21.248 48.448,21.248s35.712,-7.808 48.384,-21.248a75.584,75.584 0,0 0,0 -102.464A67.392,67.392 0,0 0,575.36 0c-17.92,0 -35.776,7.808 -48.448,21.248a75.584,75.584 0,0 0,0 102.464z" />
<path
android:fillColor="#ffffff"
android:pathData="M700.8,210.304c13.824,14.592 33.28,23.104 52.736,23.104c19.584,0 39.04,-8.512 52.8,-23.104a82.432,82.432 0,0 0,0 -111.744a73.472,73.472 0,0 0,-52.8 -23.168c-19.52,0 -38.912,8.512 -52.736,23.168a82.432,82.432 0,0 0,0 111.744z" />
<path
android:fillColor="#ffffff"
android:pathData="M824.832,368.832c14.976,15.872 36.032,25.088 57.216,25.088c21.12,0 42.24,-9.216 57.152,-25.088a89.344,89.344 0,0 0,0 -121.088a79.616,79.616 0,0 0,-57.152 -25.088c-21.184,0 -42.24,9.216 -57.216,25.088a89.344,89.344 0,0 0,0 121.088z" />
<path
android:fillColor="#ffffff"
android:pathData="M875.264,572.864c16.128,17.088 38.784,27.008 61.632,27.008c22.784,0 45.44,-9.92 61.568,-27.008a96.256,96.256 0,0 0,0 -130.432a85.76,85.76 0,0 0,-61.568 -27.072c-22.848,0 -45.44,9.984 -61.632,27.072a96.192,96.192 0,0 0,0 130.432z" />
</vector>
+1 -1
View File
@@ -23,7 +23,7 @@
android:layout_gravity="start" android:layout_gravity="start"
android:textSize="24sp" android:textSize="24sp"
android:fontFamily="sans-serif-medium" android:fontFamily="sans-serif-medium"
tools:text="1-1冷藏柜 -2°C / 87%" /> android:text=" " />
<TextView <TextView
android:id="@+id/tv_right_status" android:id="@+id/tv_right_status"
+6 -6
View File
@@ -73,9 +73,9 @@
<!-- 加载动画 --> <!-- 加载动画 -->
<ImageView <ImageView
android:id="@+id/ivLoading" android:id="@+id/ivLoading"
android:layout_width="160dp" android:layout_width="120dp"
android:layout_height="160dp" android:layout_height="120dp"
android:src="@drawable/loading_spinner" android:src="@drawable/loading"
android:contentDescription="加载中" /> android:contentDescription="加载中" />
<!-- 倒计时文字 --> <!-- 倒计时文字 -->
@@ -85,7 +85,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="60秒" android:text="60秒"
android:textSize="40sp" android:textSize="40sp"
android:textColor="@color/black" android:textColor="@color/white"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginTop="50dp" /> android:layout_marginTop="50dp" />
@@ -95,7 +95,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="网络连接检测中,请稍后……" android:text="网络连接检测中,请稍后……"
android:textSize="22sp" android:textSize="22sp"
android:textColor="#555555" android:textColor="@color/white"
android:layout_marginTop="20dp" /> android:layout_marginTop="20dp" />
</LinearLayout> </LinearLayout>
@@ -123,7 +123,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="网络连接失败" android:text="网络连接失败"
android:textSize="36sp" android:textSize="36sp"
android:textColor="@color/black" android:textColor="@color/white"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="40dp" /> android:layout_marginBottom="40dp" />
+53 -1
View File
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@@ -39,8 +44,55 @@
android:text="查找1001指令"/> android:text="查找1001指令"/>
</FrameLayout> </FrameLayout>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="8dp"
android:background="@android:color/white"
android:hint="搜索日志..."
android:paddingHorizontal="12dp"
android:textSize="14sp"
android:singleLine="true"
tools:ignore="Autofill,TextFields" />
<HorizontalScrollView
android:id="@+id/hsvTimeNav"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/llTimeNav"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingHorizontal="12dp" />
</HorizontalScrollView>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLogList" android:id="@+id/rvLogList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabJump"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@android:drawable/ic_menu_sort_by_size"
app:fabSize="normal"
tools:ignore="ContentDescription" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:elevation="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_env_switch"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="切换环境"
android:textColor="#333333"
android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E0E0E0" />
<TextView
android:id="@+id/tv_log"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="查看日志"
android:textColor="#333333"
android:textSize="14sp" />
</LinearLayout>