feat(sale): 進付款前確認下位機(XinYuan)連線,失連自動重連 (移植 Honlifeng)

點商品進付款(onBuyRequested)前先 ping 主櫃下位機;失連→checkAndReconnectDev 重連→再確認,
連上才開 BuyDialog、連不上跳大字提示擋住。檢查期間顯示「下位機連線判斷中」轉圈框。
MyApp 新增 pingXinYuanOnce/isXinYuanAlive/ensureXinYuanConnected。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
terrylee 2026-06-26 14:52:33 +08:00
parent 54b44f5cf4
commit c1da062d1d
2 changed files with 109 additions and 1 deletions

View File

@ -643,6 +643,40 @@ public class MyApp extends Application {
return true;
}
/**
* 進支付前用送同步包確認主櫃(XinYuan)下位機是否真的有回應
* 先清掉舊 buffer 再送避免讀到先前殘留的回應誤判為已連線
* 注意 Thread.sleep請在背景執行緒呼叫
*/
private static boolean pingXinYuanOnce(long waitMs) {
if (devXinYuanController == null) return false;
try {
devXinYuanController.clearReadBuffers();
devXinYuanController.addSendBuffer(
devXinYuanController.getDevXinYuan().syncPackNo((byte) 0x01));
Thread.sleep(waitMs);
return !devXinYuanController.getReadBufferByMax().isEmpty();
} catch (Exception e) {
return false;
}
}
/** 連送同步包兩次,任一次有回應即視為連線,降低偶發誤判(單次沒回不代表失連)。 */
public static boolean isXinYuanAlive() {
return pingXinYuanOnce(300) || pingXinYuanOnce(300);
}
/**
* 進支付前用確認主櫃下位機連線若失連則主動重連後再確認
* 回傳最終是否連線成功false=連不上呼叫端應擋住不進支付
* 注意 Thread.sleep請在背景執行緒呼叫
*/
public static boolean ensureXinYuanConnected() {
if (isXinYuanAlive()) return true;
checkAndReconnectDev(); // 主動重連
return isXinYuanAlive(); // 重連後再確認
}
public void initMqtt() {
String machineId = MyApp.getInstance().getMachine().getMachineID();
String apiKey = MyApp.getInstance().getMachine().getApiKey();

View File

@ -28,7 +28,81 @@ public class BasicSaleFlow extends SaleFlowHandler {
@Override
public void onBuyRequested() {
new BuyDialog(context, handlerMain).show();
// 進支付前先確認主櫃下位機(XinYuan)連線
// 僅當購物清單含主櫃(VMC)商品才檢查失連主動重連仍連不上則擋住不進支付
if (!buyListHasMainCabinetItem()) {
new BuyDialog(context, handlerMain).show();
return;
}
// 顯示連線判斷中轉圈提示避免客戶以為當機
final android.app.AlertDialog progress = buildConnectingDialog();
try { progress.show(); } catch (Exception ignore) {}
// ensureXinYuanConnected 內含 Thread.sleep必須在背景執行緒跑
new Thread(() -> {
boolean connected = MyApp.ensureXinYuanConnected();
new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> {
try { if (progress.isShowing()) progress.dismiss(); } catch (Exception ignore) {}
if (!connected) {
showNotConnectedDialog();
return;
}
new BuyDialog(context, handlerMain).show();
});
}).start();
}
/** 購物清單是否含主櫃(VMC)商品;非主櫃商品不需檢查主櫃下位機連線。 */
private boolean buyListHasMainCabinetItem() {
for (BuyStructure b : MyApp.getInstance().getBuyList()) {
if (b.getField() == com.unibuy.smartdevice.devices.SlotField.VMC.getField()) {
return true;
}
}
return false;
}
/** 連線判斷中:轉圈圈 + 提示文字(不可取消)。 */
private android.app.AlertDialog buildConnectingDialog() {
android.widget.LinearLayout layout = new android.widget.LinearLayout(context);
layout.setOrientation(android.widget.LinearLayout.HORIZONTAL);
layout.setGravity(android.view.Gravity.CENTER_VERTICAL);
int pad = dp(32);
layout.setPadding(pad, pad, pad, pad);
android.widget.ProgressBar spinner = new android.widget.ProgressBar(context);
android.widget.LinearLayout.LayoutParams sp =
new android.widget.LinearLayout.LayoutParams(dp(56), dp(56));
sp.rightMargin = dp(24);
layout.addView(spinner, sp);
android.widget.TextView tv = new android.widget.TextView(context);
tv.setText("下位機連線判斷中......");
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 28);
tv.setTextColor(android.graphics.Color.BLACK);
layout.addView(tv);
return new android.app.AlertDialog.Builder(context)
.setView(layout)
.setCancelable(false)
.create();
}
/** 主櫃未連線:跳出大字提示框。 */
private void showNotConnectedDialog() {
android.app.AlertDialog dialog = new android.app.AlertDialog.Builder(context)
.setMessage("主櫃尚未連線,請稍後再試")
.setCancelable(false)
.setPositiveButton("確定", (d, w) -> d.dismiss())
.create();
try { dialog.show(); } catch (Exception ignore) { return; }
android.widget.TextView msg = dialog.findViewById(android.R.id.message);
if (msg != null) msg.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 28);
android.widget.Button btn = dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE);
if (btn != null) btn.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 24);
}
private int dp(int v) {
return Math.round(v * context.getResources().getDisplayMetrics().density);
}
/**