[FIX] 修復遠端重啟刷卡機/結帳失敗(指令名稱不符)並補刷卡機序列埠健壯性

1. 指令名稱對齊後台:MqttService 的 nfc_reset 相容 reboot_card、nfc_checkout 相容 checkout。
   後台(star-cloud)按鈕送出 reboot_card / checkout,App 原本只認 nfc_reset / nfc_checkout,
   導致一律落入 Unknown command → failed,此功能自串接首日(df8dae9)即從未成功;改別名後相容兩種。
2. 遠端 nfc_reset / nfc_checkout 執行前加 Busy Check:getBusyReason() 非空(現場交易中)即拒收並回報
   Machine busy,避免與現場刷卡爭用 comPort2。
3. 遠端指令送完電文改用 finally 強制 comPort2.closePort(),杜絕序列埠洩漏害下一筆現場交易開不了埠。
4. Rs232Dev 的 isMockMode 由 static 改為 instance 欄位,另立 static suMissing 表「su 缺失」全域事實;
   單一埠連線失敗只標記該實例,不再污染全 App 的 send 全部空轉(原 bug 須重啟才復原)。
5. verPatch 21→22(versionName 21_02_22_D / versionCode 210222;21 已 Release,避免 OTA 撞號)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-25 09:11:55 +08:00
parent 377da0a2da
commit e4e62de5b8
3 changed files with 63 additions and 33 deletions

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 2
def verPatch = 21
def verPatch = 22

View File

@ -21,7 +21,11 @@ import java.util.List;
public class Rs232Dev {
private Logs logs;
private boolean showLogs = false;
private static boolean isMockMode = false;
// su 權限缺失整機都無法操作序列埠屬全域事實維持 static
private static boolean suMissing = false;
// 本連線是否進入 Mock連線失敗只標記這一個實例不再用 static 污染其他實例
//修正先前 static 導致遠端指令撞埠失敗一次就讓全 App send 全部空轉須重啟才復原
private boolean isMockMode = false;
private static List<String> rs232DeviceList;
private SerialPort serialPort;
private InputStream inputStream;
@ -57,11 +61,11 @@ public class Rs232Dev {
try {
suPath = searchSuPath();
SerialPort.setSuPath(suPath);
isMockMode = false;
suMissing = false;
} catch (LogsSecurityException e) {
Log.e("Rs232Dev", "Root permission (su) not found, entering Mock Mode: " + e.getMessage());
suPath = null;
isMockMode = true;
suMissing = true;
}
}
@ -91,8 +95,8 @@ public class Rs232Dev {
}
public void clearBuffer() throws LogsIOException {
if (isMockMode || inputStream == null) {
if (showLogs) logs.info(isMockMode ? "[MockMode] Skip clearBuffer" : "InputStream 為 null無法清除緩衝區");
if (suMissing || isMockMode || inputStream == null) {
if (showLogs) logs.info((suMissing || isMockMode) ? "[MockMode] Skip clearBuffer" : "InputStream 為 null無法清除緩衝區");
return;
}
@ -175,7 +179,7 @@ public class Rs232Dev {
}
public void connectPort(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
if (isMockMode) {
if (suMissing || isMockMode) {
Log.i("Rs232Dev", "[MockMode] Faking connection to: " + comPort.getFilepath());
return;
}
@ -258,7 +262,7 @@ public class Rs232Dev {
}
public int read(byte[] buffer) throws LogsIOException {
if (isMockMode || this.serialPort == null) return 0;
if (suMissing || isMockMode || this.serialPort == null) return 0;
try {
InputStream inputStream = this.serialPort.getInputStream();
int size = inputStream.read(buffer);
@ -270,7 +274,7 @@ public class Rs232Dev {
}
public void send(byte[] buffer) throws LogsIOException {
if (isMockMode || this.serialPort == null) {
if (suMissing || isMockMode || this.serialPort == null) {
Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(buffer));
return;
}
@ -285,7 +289,7 @@ public class Rs232Dev {
}
public int transmission(byte[] sendBuffer, byte[] readBuffer) throws LogsIOException {
if (isMockMode || this.serialPort == null) {
if (suMissing || isMockMode || this.serialPort == null) {
Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(sendBuffer));
return 0;
}
@ -326,7 +330,7 @@ public class Rs232Dev {
// 新增真正非阻塞讀取方法
public byte[] readNonBlocking() throws LogsIOException {
if (isMockMode || this.serialPort == null) return new byte[0];
if (suMissing || isMockMode || this.serialPort == null) return new byte[0];
try {
InputStream inputStream = serialPort.getInputStream();
int available = inputStream.available();

View File

@ -190,31 +190,57 @@ public class MqttService extends Service {
System.exit(0);
});
}), 500);
} else if ("nfc_reset".equals(cmd)) {
} else if ("nfc_reset".equals(cmd) || "reboot_card".equals(cmd)) {
// 後台送 reboot_cardApp 舊名 nfc_reset兩者皆相容
logs.info("Executing remote NFC reset command...");
try {
com.unibuy.smartdevice.devices.Rs232Dev comPort2 = new com.unibuy.smartdevice.devices.Rs232Dev(
com.unibuy.smartdevice.MyApp.getInstance().getComPort2ByNFCPay());
com.unibuy.smartdevice.devices.DevNFCPay devNFCPay = new com.unibuy.smartdevice.devices.DevNFCPay(
getApplicationContext());
comPort2.send(devNFCPay.reset());
mqttManager.publishCommandAck(cmdId, "success", "NFC reset command sent");
} catch (Exception e) {
logs.info("NFC reset failed: " + e.getMessage());
mqttManager.publishCommandAck(cmdId, "failed", "NFC reset failed: " + e.getMessage());
String busyReason = com.unibuy.smartdevice.MyApp.getInstance().getBusyReason();
if (!busyReason.isEmpty()) {
// 現場正在交易拒收避免與現場刷卡撞 comPort2撞埠會觸發 Rs232Dev mock
logs.info("NFC reset rejected, machine busy: " + busyReason);
mqttManager.publishCommandAck(cmdId, "failed", "Machine busy: " + busyReason);
} else {
com.unibuy.smartdevice.devices.Rs232Dev comPort2 = null;
try {
comPort2 = new com.unibuy.smartdevice.devices.Rs232Dev(
com.unibuy.smartdevice.MyApp.getInstance().getComPort2ByNFCPay());
com.unibuy.smartdevice.devices.DevNFCPay devNFCPay = new com.unibuy.smartdevice.devices.DevNFCPay(
getApplicationContext());
comPort2.send(devNFCPay.reset());
mqttManager.publishCommandAck(cmdId, "success", "NFC reset command sent");
} catch (Exception e) {
logs.info("NFC reset failed: " + e.getMessage());
mqttManager.publishCommandAck(cmdId, "failed", "NFC reset failed: " + e.getMessage());
} finally {
// 送完務必關埠避免序列埠洩漏導致下一筆現場交易開不了埠
if (comPort2 != null) {
try { comPort2.closePort(); } catch (Exception ignore) {}
}
}
}
} else if ("nfc_checkout".equals(cmd)) {
} else if ("nfc_checkout".equals(cmd) || "checkout".equals(cmd)) {
// 後台送 checkoutApp 舊名 nfc_checkout兩者皆相容
logs.info("Executing remote NFC checkout command...");
try {
com.unibuy.smartdevice.devices.Rs232Dev comPort2 = new com.unibuy.smartdevice.devices.Rs232Dev(
com.unibuy.smartdevice.MyApp.getInstance().getComPort2ByNFCPay());
com.unibuy.smartdevice.devices.DevNFCPay devNFCPay = new com.unibuy.smartdevice.devices.DevNFCPay(
getApplicationContext());
comPort2.send(devNFCPay.checkout());
mqttManager.publishCommandAck(cmdId, "success", "NFC checkout command sent");
} catch (Exception e) {
logs.info("NFC checkout failed: " + e.getMessage());
mqttManager.publishCommandAck(cmdId, "failed", "NFC checkout failed: " + e.getMessage());
String busyReason = com.unibuy.smartdevice.MyApp.getInstance().getBusyReason();
if (!busyReason.isEmpty()) {
logs.info("NFC checkout rejected, machine busy: " + busyReason);
mqttManager.publishCommandAck(cmdId, "failed", "Machine busy: " + busyReason);
} else {
com.unibuy.smartdevice.devices.Rs232Dev comPort2 = null;
try {
comPort2 = new com.unibuy.smartdevice.devices.Rs232Dev(
com.unibuy.smartdevice.MyApp.getInstance().getComPort2ByNFCPay());
com.unibuy.smartdevice.devices.DevNFCPay devNFCPay = new com.unibuy.smartdevice.devices.DevNFCPay(
getApplicationContext());
comPort2.send(devNFCPay.checkout());
mqttManager.publishCommandAck(cmdId, "success", "NFC checkout command sent");
} catch (Exception e) {
logs.info("NFC checkout failed: " + e.getMessage());
mqttManager.publishCommandAck(cmdId, "failed", "NFC checkout failed: " + e.getMessage());
} finally {
if (comPort2 != null) {
try { comPort2.closePort(); } catch (Exception ignore) {}
}
}
}
} else if ("change".equals(cmd)) {
logs.info("Executing remote change command...");