From e4e62de5b8fc9a7c217400504d27e4d7fe68b4f9 Mon Sep 17 00:00:00 2001 From: sky121113 Date: Thu, 25 Jun 2026 09:11:55 +0800 Subject: [PATCH] =?UTF-8?q?[FIX]=20=E4=BF=AE=E5=BE=A9=E9=81=A0=E7=AB=AF?= =?UTF-8?q?=E9=87=8D=E5=95=9F=E5=88=B7=E5=8D=A1=E6=A9=9F/=E7=B5=90?= =?UTF-8?q?=E5=B8=B3=E5=A4=B1=E6=95=97=EF=BC=88=E6=8C=87=E4=BB=A4=E5=90=8D?= =?UTF-8?q?=E7=A8=B1=E4=B8=8D=E7=AC=A6=EF=BC=89=E4=B8=A6=E8=A3=9C=E5=88=B7?= =?UTF-8?q?=E5=8D=A1=E6=A9=9F=E5=BA=8F=E5=88=97=E5=9F=A0=E5=81=A5=E5=A3=AF?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- app/build.gradle | 2 +- .../unibuy/smartdevice/devices/Rs232Dev.java | 24 ++++--- .../smartdevice/service/MqttService.java | 70 +++++++++++++------ 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 17d117c..c29ade1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 2 -def verPatch = 21 +def verPatch = 22 diff --git a/app/src/main/java/com/unibuy/smartdevice/devices/Rs232Dev.java b/app/src/main/java/com/unibuy/smartdevice/devices/Rs232Dev.java index 86e2c09..8bfe34a 100644 --- a/app/src/main/java/com/unibuy/smartdevice/devices/Rs232Dev.java +++ b/app/src/main/java/com/unibuy/smartdevice/devices/Rs232Dev.java @@ -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 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(); diff --git a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java index 6ab0b95..27592ca 100644 --- a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java +++ b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java @@ -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_card、App 舊名 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)) { + // 後台送 checkout、App 舊名 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...");