You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SZXBGJ/app/src/main/java/com/example/tyre/TyreLayoutActivity.java

583 lines
23 KiB
Java

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package com.example.tyre;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.android.hdhe.uhf.reader.UhfReader;
import com.android.hdhe.uhf.readerInterface.TagModel;
import com.example.tyre.entity.AjaxResult;
import com.example.tyre.entity.BaseCar;
import com.example.tyre.entity.BaseTyre;
import com.example.tyre.entity.EPC;
import com.example.tyre.util.CarSelectionDialog;
import com.example.tyre.util.CommonDialog;
import com.example.tyre.util.MyUrl;
import com.example.tyre.util.SharedPreferencesUtils;
import com.example.tyre.util.Util;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.Response;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import cn.pda.serialport.Tools;
public class TyreLayoutActivity extends AppCompatActivity {
// 1. 使用 @BindView 绑定控件
// 注意:变量名可以自定义,但 ID 必须对应 XML 中的 id
@BindView(R.id.et_plate_number)
TextInputEditText etPlateNumber;
@BindView(R.id.btn_retrieve)
Button btnRetrieve; // 因为使用的是 MaterialButton这里类型要对应
@BindView(R.id.til_plate_number)
TextInputLayout tilPlateNumber;
private ProgressDialog progressDialog;
private boolean isStart = true;
private boolean runFlag = true;
private boolean startFlag = false;
private UhfReader manager; // UHF manager,UHF Operating handle
private ArrayList<EPC> listEPC;
private ArrayList<String> listepc = new ArrayList<String>();
long lastTime;
long nextTime;
private Toast mToast;
private Toast toast;
private KeyReceiver keyReceiver;
String min = "EC0001012026010100000001"; // 左边界(包含)
String max = "EC0001012026010100100000"; // 右边界(包含)
// 1. 定义数据模型类 (对应后台返回的字段)
public static class TireData {
public String position; // 轮位名称
public String brand; // 品牌
public String spec; // 规格
public String installDate; // 安装日期
public TireData(String position, String brand, String spec, String installDate) {
this.position = position;
this.brand = brand;
this.spec = spec;
this.installDate = installDate;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tyre_layout);
ButterKnife.bind(this);
manager = MyApplication.getManager();
listEPC = new ArrayList<EPC>();
Thread thread = new InventoryThread();
thread.start();
Util.initSoundPool(this);
tilPlateNumber = findViewById(R.id.til_plate_number);
etPlateNumber.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
// 核心代码:只要用户输入了内容,就清除错误提示
tilPlateNumber.setError(null);
// 可选:如果你使用了 errorEnabled="true",可能还需要调用下面这行来彻底清除空间占用
// tilPlateNumber.setErrorEnabled(false);
}
});
tilPlateNumber.setEndIconOnClickListener(v -> {
// 在这里处理搜索逻辑
String plateNumber = etPlateNumber.getText().toString().trim();
if (!plateNumber.isEmpty()) {
// 执行搜索
showLoadingDialog();
performSearch(plateNumber);
} else {
tilPlateNumber.setError("车牌号不能为空");
}
});
}
@Override
public void onResume() {
super.onResume();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
registerReceiver();
}
@Override
public void onPause() {
startFlag = false;
super.onPause();
unregisterReceiver();
}
@Override
protected void onDestroy() {
startFlag = false;
runFlag = false;
super.onDestroy();
}
private void performSearch(String carNo) {
OkGo.<String>post(MyUrl.url + "/tyre/car/PdaQueryCarList")
.tag(this).params("carNo", carNo)
.execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
hideLoadingDialog();
String body = response.body();
Log.d("CarQuery", "Response: " + body);
Gson gson = new Gson();
try {
// 1. 将响应体解析为 JsonObject (通用 JSON 对象)
// 这样可以绕过 AjaxResult 类对 data 字段的 String 类型限制
com.google.gson.JsonObject jsonObject = gson.fromJson(body, com.google.gson.JsonObject.class);
// 2. 获取 code 字段 (注意:你的日志显示 code 是数字 0但 AjaxResult 定义是 String这里做兼容处理)
String code = "";
if (jsonObject.has("code")) {
// 尝试获取 code无论是 "0" 还是 0 都能处理
code = jsonObject.get("code").getAsString();
}
// 3. 判断成功状态 (根据你的日志0 代表成功)
if ("0".equals(code) || "200".equals(code)) {
// 4. 获取 data 元素
if (jsonObject.has("data")) {
com.google.gson.JsonElement dataElement = jsonObject.get("data");
// 5. 将 data 元素转换为 List<BaseCar>
// 使用 TypeToken 指定泛型类型
java.lang.reflect.Type type = new com.google.gson.reflect.TypeToken<List<BaseCar>>() {}.getType();
List<BaseCar> carList = gson.fromJson(dataElement, type);
if (carList != null && !carList.isEmpty()) {
// 6. 展示弹窗列表
showCarSelectionDialog(carList);
} else {
// 7. 无数据处理
new CommonDialog(TyreLayoutActivity.this).setMessage("未查询到相关车辆信息").show();
}
}
} else {
// 业务错误处理
String msg = jsonObject.has("msg") ? jsonObject.get("msg").getAsString() : "未知错误";
new CommonDialog(TyreLayoutActivity.this).setMessage(msg).show();
}
} catch (Exception e) {
e.printStackTrace();
new CommonDialog(TyreLayoutActivity.this).setMessage("数据解析异常").show();
}
}
});
}
/**
* 展示车辆选择弹窗
* @param carList 车辆列表
*/
private void showCarSelectionDialog(List<BaseCar> carList) {
if (carList.size() == 1) {
// 如果只有一辆车,直接填充,无需弹窗
BaseCar car = carList.get(0);
fillCarInfoToUI(car);
//Util.play(1, 0); // 提示音
Toast.makeText(this, "自动匹配车辆:" + car.getCarNo(), Toast.LENGTH_SHORT).show();
} else {
// 如果有多辆车,展示弹窗供用户选择
// 提取车牌号列表用于显示
List<String> licensePlates = new ArrayList<>();
for (BaseCar car : carList) {
licensePlates.add(car.getCarNo() != null ? car.getCarNo() : "未知车牌");
}
// 创建并显示弹窗
CarSelectionDialog dialog = new CarSelectionDialog(this, licensePlates);
dialog.setOnCarSelectedListener((selectedItem) -> {
// selectedItem 是 Object转成 String
String selectedPlate = selectedItem.toString().trim();
// 遍历列表找到对应的对象
for (BaseCar car : carList) {
if (selectedPlate.equals(car.getCarNo())) {
fillCarInfoToUI(car);
break;
}
}
});
dialog.show();
}
}
/**
* 将车辆信息填充到界面控件
* @param car 车辆对象
*/
private void fillCarInfoToUI(BaseCar car) {
// 填充车牌号
etPlateNumber.setText(safeGetString(car.getCarNo()));
// 如果有其他字段,例如:
// startEdit.setText(safeGetString(car.getDefaultMileage())); // 默认里程
// 视觉反馈
etPlateNumber.setTextColor(getColor(R.color.green500));
// Util.play(1, 0);
}
@OnClick({R.id.btn_retrieve})
public void onViewClicked(View view) {
String PlateNumber = etPlateNumber.getText().toString();
switch (view.getId()) {
case R.id.btn_retrieve:
if (PlateNumber == null || PlateNumber.isEmpty())
{
new CommonDialog(TyreLayoutActivity.this).setMessage("请输入车牌号!").show();
return;
}
carBangding(PlateNumber);
break;
}
}
//绑定车牌与RFID信息
private void carBangding(String PlateNumber) {
OkGo.<String>post(MyUrl.url + "/tyre/tyre/getCarBingTire")
.tag(this).params("carNo", PlateNumber)
.execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
//hideLoadingDialog();
String body = response.body();
Gson gson=new Gson();
// List<BaseTyre> baseTyreList = gson.fromJson(body, BaseTyre.class);
List<BaseTyre> baseTyreList = gson.fromJson(body, new com.google.gson.reflect.TypeToken<List<BaseTyre>>(){}.getType());
bindDataToViews(baseTyreList);
}
});
}
private void findCar(String carNumber) {
OkGo.<String>post(MyUrl.url + "/tyre/car/queryCarByRfid").tag(this).params("rfid", carNumber).execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
String body = response.body();
hideLoadingDialog();
try {
Gson gson = new Gson();
BaseCar baseCar = gson.fromJson(body, BaseCar.class);
if (baseCar != null) {
// 空值处理:若字段为 null 则显示空字符串
String carNo = safeGetString(baseCar.getCarNo());
etPlateNumber.setText(carNo);
}else {
new CommonDialog(TyreLayoutActivity.this).setMessage("请检查车辆芯片绑定数据!").show();
}
} catch (JsonSyntaxException e) {
return;
}
}
});
}
private String safeGetString(String value) {
return value == null || "null".equals(value) ? "" : value;
}
/**
* 核心方法:将数据绑定到具体的 Include 视图上
*/
private void bindDataToViews(List<BaseTyre> dataList) {
if (dataList == null) return;
// 遍历列表,根据具体位置找到对应的 View
for (BaseTyre tyre : dataList) {
View targetView = null;
// 根据后端返回的位置字段判断要更新哪个布局
// 假设 tyre.getPosition() 返回 "LF" (左前), "RF" (右前) 等
String pos = tyre.getWheelPostion();
if ("左前轮".equals(pos) || "1".equals(pos)) {
targetView = findViewById(R.id.tire_left_front);
} else if ("右前轮".equals(pos) || "2".equals(pos)) {
targetView = findViewById(R.id.tire_right_front);
} else if ("左内轮".equals(pos)) {
targetView = findViewById(R.id.tire_middle_left);
}else if ("右内轮".equals(pos)) {
targetView = findViewById(R.id.tire_middle_right);
}else if ("左外轮".equals(pos)) {
targetView = findViewById(R.id.tire_bottom_left);
}else if ("右外轮".equals(pos)) {
targetView = findViewById(R.id.tire_bottom_right);
}
// ... 其他位置
if (targetView != null) {
updateTireView(targetView, tyre);
}
}
}
// private void bindDataToViews(List<BaseTyre> dataList) {
// // 假设 dataList.get(0) 是左前轮dataList.get(1) 是右前轮...
// // 请根据你实际的列表顺序调整索引
//
// if (dataList.size() >= 2) {
// // 绑定左前轮
// updateTireView(findViewById(R.id.tire_left_front), dataList.get(0));
//
// // 绑定右前轮
// updateTireView(findViewById(R.id.tire_right_front), dataList.get(1));
//
// // 绑定其他轮胎...
// // updateTireView(findViewById(R.id.tire_left_mid), dataList.get(2));
// }
// }
/**
* 通用方法:更新单个轮胎卡片的 UI
* @param container include 标签对应的根布局 View
* @param data 该轮胎的数据对象
*/
private void updateTireView(View container, BaseTyre data) {
if (container == null) return;
// 【关键点】:必须在 container 内部查找控件,而不是在 Activity 中直接查找
TextView tvBrand = container.findViewById(R.id.tv_brand);
TextView tvSpec = container.findViewById(R.id.tv_spec);
TextView tvSelfNo = container.findViewById(R.id.tv_self_no); // 或者是 tv_install_date
TextView tvDate = container.findViewById(R.id.tv_install_date); // 或者是 tv_install_date
// 设置数据
if (tvBrand != null) tvBrand.setText("品牌: " + data.getTyreBrand());
if (tvSpec != null) tvSpec.setText("规格: " + data.getTyreModel());
if (tvSelfNo != null) tvSelfNo.setText("自编号: " + data.getSelfNo());
if (tvDate != null) tvDate.setText("日期: " + data.getCreateTime());
}
private void addToList(final List<EPC> list, final String epc, final byte rssi) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// The epc for the first time
if (list.isEmpty()) {
EPC epcTag = new EPC();
epcTag.setEpc(epc);
epcTag.setCount(1);
epcTag.setRssi(rssi);
list.add(epcTag);
listepc.add(epc);
}else {
for (int i = 0; i < list.size(); i++) {
EPC mEPC = list.get(i);
// list contain this epc
if (epc.equals(mEPC.getEpc())) {
mEPC.setCount(mEPC.getCount() + 1);
mEPC.setRssi(rssi);
list.set(i, mEPC);
break;
} else if (i == (list.size() - 1)) {
// list doesn't contain this epc
EPC newEPC = new EPC();
newEPC.setEpc(epc);
newEPC.setCount(1);
newEPC.setRssi(rssi);
list.add(newEPC);
listepc.add(epc);
}
}
}
// play sound
lastTime = SystemClock.elapsedRealtime();
long time = lastTime - nextTime;
if (time >= 60) {
Util.play(1, 0);
nextTime = lastTime;
Log.e("TAG", "run: " + time);
}
if (listepc != null && !listepc.isEmpty()){
startFlag = false;
String currentEpc = listepc.get(0);
if (isInRange(currentEpc, min, max)) {
//请求后台 查询车辆信息
// car.setText(currentEpc);
//请求后台
debounceHandler.removeMessages(1);
lastScannedEpc = currentEpc;
debounceHandler.sendEmptyMessageDelayed(1, DEBOUNCE_DELAY);
}
}
clearData();
}
});
Log.e("EPC", "listepc:+ " + listepc);
}
/**
* 判断字符串是否在指定的区间内(包含边界)
* @param target 要判断的目标字符串
* @param min 区间左边界
* @param max 区间右边界
* @return true=在区间内false=不在
*/
public static boolean isInRange(String target, String min, String max) {
// 空值校验,避免空指针异常
if (target == null || min == null || max == null) {
return false;
}
// compareTo规则
// 1. 字符串相等返回0
// 2. 目标字符串 > 对比字符串 返回正数;
// 3. 目标字符串 < 对比字符串 返回负数。
// 因此target >= min 等价于 target.compareTo(min) >= 0
// target <= max 等价于 target.compareTo(max) <= 0。
return target.compareTo(min) >= 0 && target.compareTo(max) <= 0;
}
// 防抖延迟时间 (毫秒)
private static final long DEBOUNCE_DELAY = 500;
// 用于存储最后一次扫描到的 EPC
private String lastScannedEpc;
// 用于处理防抖逻辑的 Handler
private Handler debounceHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 1 && lastScannedEpc != null) {
// 延迟时间到,执行查询
showLoadingDialog();
//查询车辆数据方法
findCar(lastScannedEpc);
lastScannedEpc = null; // 清空,等待下一次扫描
}
return true;
}
});
private void clearData() {
listEPC.removeAll(listEPC);
listepc.removeAll(listepc);
}
private class KeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int keyCode = intent.getIntExtra("keyCode", 0);
if (keyCode == 0) {
keyCode = intent.getIntExtra("keycode", 0);
}
boolean keyDown = intent.getBooleanExtra("keydown", false);
if (keyDown) {
if (toast == null) {
// toast = Toast.makeText(OutStoreHouseActivity.this, "KeyReceiver:keyCode = down" + keyCode, Toast.LENGTH_SHORT);
} else {
// toast.setText("KeyReceiver:keyCode = down" + keyCode);
}
// toast.show();
switch (keyCode) {
case KeyEvent.KEYCODE_F1:
case KeyEvent.KEYCODE_F2:
case KeyEvent.KEYCODE_F3:
case KeyEvent.KEYCODE_F4:
case KeyEvent.KEYCODE_F5:
//扫描
startFlag = true;
break;
}
}
}
}
private void registerReceiver() {
keyReceiver = new KeyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.rfid.FUN_KEY");
filter.addAction("android.intent.action.FUN_KEY");
registerReceiver(keyReceiver, filter);
}
private void unregisterReceiver() {
unregisterReceiver(keyReceiver);
}
class InventoryThread extends Thread {
private List<TagModel> tagList;
byte[] accessPassword = Tools.HexString2Bytes("00000000");
@Override
public void run() {
super.run();
while (runFlag) {
if (startFlag) {
tagList = manager.inventoryRealTime(); //实时盘存
if (tagList != null && !tagList.isEmpty()) {
//播放提示音
Util.play(1, 0);
for (TagModel tag : tagList) {
if (tag == null) {
String epcStr = "";
// String epcStr = new String(epc);
addToList(listEPC, epcStr, (byte) -1);
} else {
String epcStr = Tools.Bytes2HexString(tag.getmEpcBytes(), tag.getmEpcBytes().length);
// String epcStr = new String(epc);
byte rssi = tag.getmRssi();
addToList(listEPC, epcStr, rssi);
}
}
}
tagList = null;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
private void showLoadingDialog() {
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("数据加载中...");
progressDialog.setCancelable(false);
progressDialog.show();
}
private void hideLoadingDialog() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
}