package com.shlb.comb.activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; import com.shlb.comb.R; import com.shlb.comb.base.BaseActivity; import com.shlb.comb.event.UpdateEvent; import com.shlb.comb.manager.BleGlobalManager; import com.shlb.comb.util.CMD; import com.shlb.comb.util.CRCutil; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; public class ParameterSettingActivity extends BaseActivity { private Spinner sp_threshold_mode; private EditText et_threshold_value; private Button btn_threshold_read; private Button btn_threshold_write; private Spinner sp_sensitivity_mode; private EditText et_sensitivity_value; private Button btn_sensitivity_read; private Button btn_sensitivity_write; private Button btn_start_check; private Button btn_end_check; private Button btn_reset_mainboard; private Button btn_reset_sensor; private TextView tv_log; private TextView tv_right_text; // New components private RecyclerView rvGrid; private QMUIRoundButton btnReadData; private QMUIRoundButton btnClearLog; private ScrollView svLog; private GridAdapter mAdapter; private List boxStatusList = new ArrayList<>(); private StringBuilder logBuilder = new StringBuilder(); private boolean isSelfCheckMode = false; private String pendingSettingCmd = ""; private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); @Override protected void contentView() { setContentView(R.layout.activity_parameter_setting); } @Override protected void initView() { super.initHead(); if (tv_center != null) { tv_center.setText("参数设定"); } if (iv_left != null) { iv_left.setImageResource(R.mipmap.icon_back); } sp_threshold_mode = findViewById(R.id.sp_threshold_mode); et_threshold_value = findViewById(R.id.et_threshold_value); btn_threshold_read = findViewById(R.id.btn_threshold_read); btn_threshold_write = findViewById(R.id.btn_threshold_write); sp_sensitivity_mode = findViewById(R.id.sp_sensitivity_mode); et_sensitivity_value = findViewById(R.id.et_sensitivity_value); btn_sensitivity_read = findViewById(R.id.btn_sensitivity_read); btn_sensitivity_write = findViewById(R.id.btn_sensitivity_write); btn_start_check = findViewById(R.id.btn_start_check); btn_end_check = findViewById(R.id.btn_end_check); btn_reset_mainboard = findViewById(R.id.btn_reset_mainboard); btn_reset_sensor = findViewById(R.id.btn_reset_sensor); tv_log = findViewById(R.id.tv_log); tv_right_text = findViewById(R.id.tv_right_text); // Init new components rvGrid = findViewById(R.id.rv_grid); // btnReadData was removed from layout btnClearLog = findViewById(R.id.btn_clear_log); svLog = findViewById(R.id.sv_log); // Log setup if (svLog != null) { svLog.setOnTouchListener((v, event) -> { v.getParent().requestDisallowInterceptTouchEvent(true); if ((event.getAction() & android.view.MotionEvent.ACTION_MASK) == android.view.MotionEvent.ACTION_UP) { v.getParent().requestDisallowInterceptTouchEvent(false); } return false; }); } if (logBuilder.length() > 0) { tv_log.setText(Html.fromHtml(logBuilder.toString())); } else { logBuilder.append("日志记录:
"); tv_log.setText(Html.fromHtml(logBuilder.toString())); } } @Override protected void initData() { updateBluetoothStatus(); // Initialize box status list boxStatusList.clear(); for (int i = 1; i <= 30; i++) { boxStatusList.add(new BoxStatus(i)); } // Grid Setup rvGrid.setLayoutManager(new GridLayoutManager(this, 10)); mAdapter = new GridAdapter(); rvGrid.setAdapter(mAdapter); if (BleGlobalManager.getInstance().isConnected()) { sendCmdWithCrc(CMD.ENTER_SETTING); // sendCmdWithCrc(CMD.READ_DATA); } } @Override protected void onDestroy() { if (BleGlobalManager.getInstance().isConnected()) { sendCmdWithCrc(CMD.EXIT_SETTING); } super.onDestroy(); } @Override protected void initEvent() { View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { int id = v.getId(); String action = ""; if (id == R.id.btn_threshold_read) { action = "读取阈值"; Toast.makeText(ParameterSettingActivity.this, "该功能暂未开放", Toast.LENGTH_SHORT).show(); } else if (id == R.id.btn_threshold_write) { handleThresholdWrite(); } else if (id == R.id.btn_sensitivity_read) { action = "读取灵敏度"; Toast.makeText(ParameterSettingActivity.this, "该功能暂未开放", Toast.LENGTH_SHORT).show(); } else if (id == R.id.btn_sensitivity_write) { handleSensitivityWrite(); } else if (id == R.id.btn_start_check) { action = "开始自检"; sendCmdWithCrc(CMD.WRITE_START_CHECK); isSelfCheckMode = true; } else if (id == R.id.btn_end_check) { action = "结束自检"; sendCmdWithCrc(CMD.WRITE_END_CHECK); isSelfCheckMode = false; } else if (id == R.id.btn_reset_mainboard) { showResetConfirmDialog("确定要执行主板复位吗?", CMD.WRITE_BOARD_RESET); } else if (id == R.id.btn_reset_sensor) { showResetConfirmDialog("确定要执行传感器复位吗?", CMD.WRITE_SENSOR_RESET); } if (!action.isEmpty()) { // Toast.makeText(ParameterSettingActivity.this, action, Toast.LENGTH_SHORT).show(); // appendLog(action); } } }; btn_threshold_read.setOnClickListener(listener); btn_threshold_write.setOnClickListener(listener); btn_sensitivity_read.setOnClickListener(listener); btn_sensitivity_write.setOnClickListener(listener); btn_start_check.setOnClickListener(listener); btn_end_check.setOnClickListener(listener); btn_reset_mainboard.setOnClickListener(listener); btn_reset_sensor.setOnClickListener(listener); btnClearLog.setOnClickListener(v -> { logBuilder.setLength(0); logBuilder.append("日志记录:
"); tv_log.setText(Html.fromHtml(logBuilder.toString())); }); } private String getCmdDescription(String hex, Boolean isSent) { if (hex == null) return ""; // Special handling for 3B (Enter/Exit Setting) if (hex.startsWith(CMD.ENTER_SETTING.substring(0, 8))) { if (hex.startsWith(CMD.ENTER_SETTING)) return "-进入设定"; if (hex.startsWith(CMD.EXIT_SETTING)) return "-退出设定"; } // 发送指令匹配 if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING)) return "-阈值设定"; if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING)) return "-灵敏度设定"; if (hex.startsWith(CMD.WRITE_BOARD_RESET)) return "-主板复位"; if (hex.startsWith(CMD.WRITE_SENSOR_RESET)) return "-传感器复位"; if (hex.startsWith(CMD.WRITE_START_CHECK)) return "-开始自检"; if (hex.startsWith(CMD.WRITE_END_CHECK)) return "-结束自检"; if (hex.startsWith(CMD.READ_DATA.substring(0, 8))) return "-读数据"; // 接收数据匹配 if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING.substring(0, 8))) return "-阈值返回"; if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING.substring(0, 8))) return "-灵敏度返回"; if (hex.startsWith(CMD.WRITE_BOARD_RESET.substring(0, 8))) return "-主板复位返回"; if (hex.startsWith(CMD.WRITE_SENSOR_RESET.substring(0, 8))) return "-传感器复位返回"; if (hex.startsWith(CMD.WRITE_START_CHECK.substring(0, 8))) return "-自检返回"; if (hex.startsWith(CMD.WRITE_SUFFIX)) return "-写入返回"; return ""; } private void appendLog(String msg) { appendLog(msg, null); } private void appendLog(String msg, Boolean isSent) { String time = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(new Date()); String displayMsg = msg; String cmdDesc = ""; // 如果是 hex 指令 (纯 0-9 A-F a-f),加空格格式化 if (msg.matches("^[0-9A-Fa-f]+$")) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < msg.length(); i += 2) { if (i + 2 <= msg.length()) { sb.append(msg.substring(i, i + 2)).append(" "); } else { sb.append(msg.substring(i)); } } displayMsg = sb.toString().trim(); // 获取指令描述 cmdDesc = getCmdDescription(msg, isSent); } String logLine; if (isSent != null) { String color = isSent ? "#1890ff" : "#05aa87"; // Blue for sent, Green for received // 拼接到前缀后面: "发送-读站号: " 或 "收到-读站号: " String prefix = (isSent ? "发送" : "收到") + cmdDesc + ": "; logLine = time + " " + prefix + displayMsg + "
"; } else { // 普通日志 logLine = time + " " + displayMsg + "
"; } logBuilder.append(logLine); // Update UI if (tv_log != null) { tv_log.setText(Html.fromHtml(logBuilder.toString())); if (svLog != null) { svLog.post(() -> svLog.fullScroll(View.FOCUS_DOWN)); } } } // Kept for backward compatibility if needed, but redirected to appendLog private void addLog(String message) { appendLog(message); } @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(UpdateEvent event) { if (event.getType() == UpdateEvent.Type.CONN_STATU) { updateBluetoothStatus(); } else if (event.getType() == UpdateEvent.Type.DEVICE_INFO) { String hex = event.getMsg(); appendLog(hex, false); // false for received (Green) parseAndRefresh(hex); } } private void parseAndRefresh(String hex) { if (hex == null) return; // 写入指令的返回 (A55A06开头) if (hex.startsWith("A55A06") && hex.length() >= 12) { String statusHex = hex.substring(10, 12); boolean isSuccess = "01".equals(statusHex); if (hex.startsWith(CMD.ENTER_SETTING.substring(0, 8))) { if (isSuccess) { appendLog("进入设定模式成功"); } else { appendLog("进入设定模式失败"); } return; } if (hex.startsWith(CMD.EXIT_SETTING.substring(0, 8))) { appendLog("退出设定模式成功"); return; } if (hex.startsWith(CMD.WRITE_THRESHOLD_SETTING.substring(0, 8))) { if (isSuccess) { appendLog("阈值写入成功"); Toast.makeText(this, "阈值写入成功", Toast.LENGTH_SHORT).show(); } else { appendLog("阈值写入失败"); Toast.makeText(this, "阈值写入失败", Toast.LENGTH_SHORT).show(); } } else if (hex.startsWith(CMD.WRITE_SENSITIVITY_SETTING.substring(0, 8))) { if (isSuccess) { appendLog("灵敏度写入成功"); Toast.makeText(this, "灵敏度写入成功", Toast.LENGTH_SHORT).show(); } else { appendLog("灵敏度写入失败"); Toast.makeText(this, "灵敏度写入失败", Toast.LENGTH_SHORT).show(); } } else if (hex.startsWith(CMD.WRITE_BOARD_RESET.substring(0, 8))) { boolean isBoardReset = pendingSettingCmd.equals(CMD.WRITE_BOARD_RESET); String name = isBoardReset ? "主板复位" : "传感器复位"; if (isSuccess) { appendLog(name + "成功"); Toast.makeText(this, name + "成功", Toast.LENGTH_SHORT).show(); } else { appendLog(name + "失败"); Toast.makeText(this, name + "失败", Toast.LENGTH_SHORT).show(); } } } // Check for Self Check command response if (hex.startsWith(CMD.WRITE_START_CHECK.substring(0, 8)) && hex.length() >= 12) { String statusHex = hex.substring(10, 12); boolean isSuccess = "01".equals(statusHex); String opName = isSelfCheckMode ? "开始自检" : "结束自检"; if (isSuccess) { appendLog(opName + "成功"); Toast.makeText(this, opName + "成功", Toast.LENGTH_SHORT).show(); // If success and isSelfCheckMode, wait 5s then read data if (isSelfCheckMode) { appendLog("5秒后读取自检结果..."); new android.os.Handler().postDelayed(() -> { sendCmdWithCrc(CMD.READ_DATA); }, 5000); } } else { appendLog(opName + "失败"); Toast.makeText(this, opName + "失败", Toast.LENGTH_SHORT).show(); } } // Handle READ_DATA response for Self Check results // A55A0301... if (hex.startsWith(CMD.READ_DATA.substring(0, 8)) && hex.length() >= 26) { parseSelfCheckData(hex); } } private void parseSelfCheckData(String hex) { try { // 11th hex -> index 10-18 (8 chars) String part1Hex = hex.substring(10, 18); long part1Bits = Long.parseLong(part1Hex, 16); // 18th hex -> index 18-26 (8 chars) String part2Hex = hex.substring(18, 26); long part2Bits = Long.parseLong(part2Hex, 16); appendLog("自检数据解析: " + part1Hex + ", " + part2Hex); boolean hasChange = false; for (BoxStatus box : boxStatusList) { int bitIndex = box.id - 1; if (bitIndex >= 0 && bitIndex < 32) { // Determine success based on both bits // Assuming both need to be 1, or following specific logic. // Based on user request "11th and 18th hex", we check both. // Usually part1 is Glass (Detection), part2 is Online. // We'll assume success means bit is set in both (Online and Detected?) // OR maybe just Online? // Let's stick to combining them to be safe as user mentioned both. // But if Self Check is just checking if it's working, maybe Online is enough? // Let's use: Success = (part1 & 1) && (part2 & 1) ? // Let's use bitwise AND of the two parts for the result. boolean bit1 = ((part1Bits >> bitIndex) & 1) == 1; boolean bit2 = ((part2Bits >> bitIndex) & 1) == 1; // If user meant "11th OR 18th", or "11th is this, 18th is that". // Given "Success/Fail", I'll assume both must be valid. // However, in normal operation: // Glass=0, Online=1 -> Empty box, but sensor working. // Glass=1, Online=1 -> Full box, sensor working. // Online=0 -> Sensor broken/offline. // If "Self Check" puts the sensor in a state where it should report "1" for Glass? // If so, then we expect Glass=1 AND Online=1. // If "Self Check" just checks health, then Glass might be 0. // "确定哪些格子自检成功" (Determine which grids passed self-check). // If I use bit1 && bit2: // If a sensor is working but empty (Glass=0), it fails self-check? // This implies Self Check expects a "1" signal. // This is common in self-checks (force a signal). if (bit1 && bit2) { box.checkResult = 1; // Green } else { box.checkResult = 0; // Red } hasChange = true; } } if (hasChange && mAdapter != null) { mAdapter.notifyDataSetChanged(); appendLog("自检结果已更新"); } } catch (Exception e) { e.printStackTrace(); appendLog("自检数据解析异常: " + e.getMessage()); } } private void sendCmdWithCrc(String cmd) { if (!BleGlobalManager.getInstance().isConnected()) { Toast.makeText(this, "请先连接蓝牙", Toast.LENGTH_SHORT).show(); appendLog("错误: 蓝牙未连接"); return; } byte[] cmdBytes = BleGlobalManager.hexStringToBytes(cmd); if (cmdBytes != null) { String crc = CRCutil.getCRC(cmdBytes); // Pad CRC to 4 chars if needed while (crc.length() < 4) { crc = "0" + crc; } String fullCmd = cmd + crc.toUpperCase(); appendLog(fullCmd, true); // true for sent (Blue) BleGlobalManager.getInstance().sendCmd(fullCmd); } else { appendLog("错误: 指令转换失败"); } } private void handleThresholdWrite() { int position = sp_threshold_mode.getSelectedItemPosition(); String inputValue = et_threshold_value.getText().toString().trim(); String modeHex = ""; String valueHex = ""; if (position == 0) { // 一键设定 modeHex = "00"; valueHex = "00"; } else if (position == 1) { // 单层设定 modeHex = "01"; if (inputValue.isEmpty()) { Toast.makeText(this, "请输入阈值(1-30)", Toast.LENGTH_SHORT).show(); return; } try { int val = Integer.parseInt(inputValue); if (val < 1 || val > 30) { Toast.makeText(this, "阈值范围1-30", Toast.LENGTH_SHORT).show(); return; } valueHex = String.format("%02X", val); } catch (NumberFormatException e) { Toast.makeText(this, "输入格式错误", Toast.LENGTH_SHORT).show(); return; } } else { return; } // Construct pending command: CMD (32) + Len (02) + Mode + Value // CMD.WRITE_THRESHOLD_SETTING is "A55A063202" pendingSettingCmd = CMD.WRITE_THRESHOLD_SETTING + modeHex + valueHex; sendCmdWithCrc(pendingSettingCmd); } private void handleSensitivityWrite() { int position = sp_sensitivity_mode.getSelectedItemPosition(); String inputValue = et_sensitivity_value.getText().toString().trim(); String modeHex = ""; String valueHex = ""; // 0: 批量增加(00), 1: 批量减少(01), 2: 单层增加(02), 3: 单层减少(03) if (position == 0) { modeHex = "00"; valueHex = "00"; } else if (position == 1) { modeHex = "01"; valueHex = "00"; } else if (position == 2) { modeHex = "02"; } else if (position == 3) { modeHex = "03"; } else { return; } if (position == 2 || position == 3) { if (inputValue.isEmpty()) { Toast.makeText(this, "请输入数值(1-30)", Toast.LENGTH_SHORT).show(); return; } try { int val = Integer.parseInt(inputValue); if (val < 1 || val > 30) { Toast.makeText(this, "数值范围1-30", Toast.LENGTH_SHORT).show(); return; } valueHex = String.format("%02X", val); } catch (NumberFormatException e) { Toast.makeText(this, "输入格式错误", Toast.LENGTH_SHORT).show(); return; } } // Construct pending command: CMD (33) + Len (02) + Mode + Value // CMD.WRITE_SENSITIVITY_SETTING is "A55A063302" pendingSettingCmd = CMD.WRITE_SENSITIVITY_SETTING + modeHex + valueHex; sendCmdWithCrc(pendingSettingCmd); } private void updateBluetoothStatus() { if (tv_right_text == null) return; if (BleGlobalManager.getInstance().isConnected()) { String name = "未知设备"; if (BleGlobalManager.getInstance().getBluetoothLeDevice() != null) { String deviceName = BleGlobalManager.getInstance().getBluetoothLeDevice().getName(); if (deviceName != null && !deviceName.isEmpty()) { name = deviceName; } } tv_right_text.setText(name); } else { tv_right_text.setText("未连接"); } } private void showResetConfirmDialog(String message, final String cmd) { new AlertDialog.Builder(this) .setTitle("提示") .setMessage(message) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { pendingSettingCmd = cmd; sendCmdWithCrc(cmd); } }) .setNegativeButton("取消", null) .show(); } // Inner classes private static class BoxStatus { int id; // -1: Unknown/Initial (White), 0: Fail (Red), 1: Success (Green) int checkResult = -1; public BoxStatus(int id) { this.id = id; this.checkResult = -1; } } private class GridAdapter extends RecyclerView.Adapter { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_box, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { // Calculate Box ID based on 3 rows of 10, Right to Left logic as seen in image // Row 1 (pos 0-9): 10 ... 1 // Row 2 (pos 10-19): 20 ... 11 // Row 3 (pos 20-29): 30 ... 21 int row = position / 10; int col = position % 10; int boxId = (row + 1) * 10 - col; holder.tvBoxNumber.setText(String.valueOf(boxId)); // Find status for this box BoxStatus status = null; for (BoxStatus s : boxStatusList) { if (s.id == boxId) { status = s; break; } } if (status != null) { if (status.checkResult == 1) { // Success holder.viewBox.setBackgroundResource(R.drawable.bg_box_full); // Green } else if (status.checkResult == 0) { // Fail holder.viewBox.setBackgroundResource(R.drawable.bg_box_error); // Red } else { // Unknown / Initial holder.viewBox.setBackgroundResource(R.drawable.bg_box_empty); // White } } } @Override public int getItemCount() { return 30; } class ViewHolder extends RecyclerView.ViewHolder { TextView tvBoxNumber; View viewBox; public ViewHolder(@NonNull View itemView) { super(itemView); tvBoxNumber = itemView.findViewById(R.id.tv_box_number); viewBox = itemView.findViewById(R.id.view_box); } } } }