fix(scale): 优化秤数据同步及格口显示顺序

- 增加秤号到列表索引的映射,提升心跳回调数据定位效率
- 调整重量数据处理逻辑,减少索引查找开销
- 按设备物理布局重排格口列表,解决多列设备显示错乱问题
- 定义同步间隔和首次同步延迟常量,统一定时任务配置
- 调整重量上报心跳间隔为1秒,提升秤数据实时性
- 清空列表时同步清理秤号映射,防止脏数据影响展示
- 优化空格口清理逻辑,改用映射位置快速更新对应项
This commit is contained in:
mazengfei
2026-07-02 17:58:46 +08:00
parent 3757b851be
commit 1e44cd2765
2 changed files with 80 additions and 15 deletions
@@ -76,8 +76,8 @@ object ScaleManager {
/** 当前设备激活码,非空时收到激活请求自动应答 */ /** 当前设备激活码,非空时收到激活请求自动应答 */
private var activateCode: String = "" private var activateCode: String = ""
/** 心跳应答:心跳上报间隔(秒),默认5秒 */ /** 心跳应答:心跳上报间隔(秒),默认1秒,保证重量更新实时性 */
var heartbeatInterval: Int = 5 var heartbeatInterval: Int = 1
/** 心跳应答:开锁后未开门自动落锁时间(秒),默认6秒 */ /** 心跳应答:开锁后未开门自动落锁时间(秒),默认6秒 */
var lockTimeout: Int = 6 var lockTimeout: Int = 6
@@ -46,12 +46,20 @@ import org.greenrobot.eventbus.ThreadMode
class HomeV3Activity : BaseActivity() { class HomeV3Activity : BaseActivity() {
companion object {} companion object {
/** 重量同步后台间隔(秒) */
private const val SYNC_INTERVAL_SECONDS = 30L
/** 首次同步延迟(秒) */
private const val SYNC_INITIAL_DELAY_SECONDS = 10L
}
override val enableRightStatusMenu: Boolean = true override val enableRightStatusMenu: Boolean = true
private val list: MutableList<SlotModel> = mutableListOf() private val list: MutableList<SlotModel> = mutableListOf()
/** 秤号(slotNo 数字部分)→ list 索引 的快速查找表,心跳回调中 O(1) 定位 */
private val scaleIndexToPosition = mutableMapOf<Int, Int>()
private var shelfIndex = 0 private var shelfIndex = 0
private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751" private fun isNeedOpenLock() = App.deviceId == "4787e213-90ab-3e32-88e0-ac271a937751"
@@ -72,15 +80,12 @@ class HomeV3Activity : BaseActivity() {
.init(activateCode = ProtocolConstants.ACTIVE_MAP[App.deviceId] ?: "") .init(activateCode = ProtocolConstants.ACTIVE_MAP[App.deviceId] ?: "")
.onHeartbeat { _, data -> .onHeartbeat { _, data ->
data.weights.forEach { weight -> data.weights.forEach { weight ->
val shelfNo = weight.index val pos = scaleIndexToPosition[weight.index] ?: return@forEach
val pos = list.indexOfFirst { val model = list[pos]
it.slotNo.filter { c -> c.isDigit() }.toIntOrNull() == shelfNo
}
val model = list.getOrNull(pos) ?: return@forEach
model.weight = weight.grams / 1000.0 model.weight = weight.grams / 1000.0
shelfAdapter.notifyItemChanged(pos) shelfAdapter.notifyItemChanged(pos)
log("getWeightInfo: slotNo=${model.slotNo}, realWeight=${weight.grams}, temp=${data.temperature}") log("getWeightInfo: slotNo=${model.slotNo}, realWeight=${weight.grams}, temp=${data.temperature}")
EventBus.getDefault().post(SendWeightEvent(shelfNo, weight.grams)) EventBus.getDefault().post(SendWeightEvent(weight.index, weight.grams))
} }
} }
.onTempReport { frame -> .onTempReport { frame ->
@@ -158,7 +163,8 @@ class HomeV3Activity : BaseActivity() {
log("updateUI: slots=${slots.size}, rows=$rows, columns=$columns") log("updateUI: slots=${slots.size}, rows=$rows, columns=$columns")
list.clear() list.clear()
list.addAll(slots) list.addAll(reorderSlotsByDeviceLayout(slots, columns, rows))
buildScaleIndexMap()
val displayMetrics = resources.displayMetrics val displayMetrics = resources.displayMetrics
// 减去 RecyclerView + item 自身的 margin // 减去 RecyclerView + item 自身的 margin
@@ -172,6 +178,64 @@ class HomeV3Activity : BaseActivity() {
} }
} }
/**
* 按设备物理布局重排格口列表。
*
* API 返回的 slots 按 slotNo 升序排列。FlexboxLayoutManager(FlexDirection.COLUMN)
* 按列填充(先填满第一列 5 行,再换第二列),3 列设备需要重排才能正确显示:
*
* 期望显示: 列填充需要的列表序:
* [1] [6] [7] [1, 2, 3, 4, 5, ← 第1列
* [2] [8] [9] 6, 8, 10, 12, 14, ← 第2列
* [3][10] [11] 7, 9, 11, 13, 15] ← 第3列
* [4][12] [13]
* [5][14] [15]
*
* 2 列设备 API 原序 [1..10] 列填充后恰好正确,无需重排。
*
* @param slots API 返回的格口列表(按 slotNo 升序)
* @param columns 列数(API horizontalRows
* @param rows 行数(API verticalCount
*/
private fun reorderSlotsByDeviceLayout(
slots: List<SlotModel>,
columns: Int,
rows: Int
): List<SlotModel> {
// 2 列及以下无需重排,API 原序列填充后恰好正确
if (columns <= 2) return ArrayList(slots)
// 按 slotNo 数字部分建立查找表
val slotMap = HashMap<Int, SlotModel>(slots.size)
for (slot in slots) {
val num = slot.slotNo.filter { it.isDigit() }.toIntOrNull() ?: continue
slotMap[num] = slot
}
val result = ArrayList<SlotModel>(slots.size)
for (displayCol in 0 until columns) {
for (displayRow in 0 until rows) {
// 物理布局 → FlexDirection.COLUMN 列填充所需的列表位置
val slotNo = when (displayCol) {
0 -> displayRow + 1 // 左列: 1,2,3,4,5
1 -> rows + 1 + displayRow * 2 // 中列: 6,8,10,12,14
else -> rows + 2 + displayRow * 2 // 右列: 7,9,11,13,15
}
slotMap[slotNo]?.let { result.add(it) }
}
}
return result
}
/** 预建秤号→列表索引映射表,供心跳回调 O(1) 查找 */
private fun buildScaleIndexMap() {
scaleIndexToPosition.clear()
list.forEachIndexed { pos, model ->
val scaleNo = model.slotNo.filter { it.isDigit() }.toIntOrNull() ?: return@forEachIndexed
scaleIndexToPosition[scaleNo] = pos
}
}
private fun showError(message: String) { private fun showError(message: String) {
Loading.dismiss() Loading.dismiss()
toast(message) toast(message)
@@ -302,6 +366,7 @@ class HomeV3Activity : BaseActivity() {
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun loadEmptyView() { private fun loadEmptyView() {
list.clear() list.clear()
scaleIndexToPosition.clear()
shelfAdapter.notifyDataSetChanged() shelfAdapter.notifyDataSetChanged()
binding.include?.let { binding.include?.let {
it.root.visible() it.root.visible()
@@ -319,10 +384,7 @@ class HomeV3Activity : BaseActivity() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun clearEmptyShelf(event: ClearShelfEvent) { fun clearEmptyShelf(event: ClearShelfEvent) {
val index = list.indexOfFirst { val index = scaleIndexToPosition[event.shelfNo] ?: return
it.slotNo?.filter { c -> c.isDigit() }?.toIntOrNull() == event.shelfNo
}
if (index < 0) return
list[index] = list[index].copy(materName = "") list[index] = list[index].copy(materName = "")
shelfAdapter.notifyItemChanged(index) shelfAdapter.notifyItemChanged(index)
} }
@@ -332,7 +394,10 @@ class HomeV3Activity : BaseActivity() {
private fun saveGoodsTask() { private fun saveGoodsTask() {
saveTaskJob = saveTaskJob =
taskExecutor.startIntervalTaskWithInitialDelay(10 * 1000L, 1 * 60 * 1000L) { taskExecutor.startIntervalTaskWithInitialDelay(
SYNC_INITIAL_DELAY_SECONDS * 1000L,
SYNC_INTERVAL_SECONDS * 1000L
) {
syncShelfGoodsToServer() syncShelfGoodsToServer()
} }
} }