NativeFastPrinterModule.java 9.6 KB
package com.foodlabel.nativeprinter;

import com.alibaba.fastjson.JSONObject;
import com.foodlabel.nativeprinter.debug.NativePrintDebugState;
import com.foodlabel.nativeprinter.support.PluginResult;
import com.foodlabel.nativeprinter.support.SafeJson;
import com.foodlabel.nativeprinter.support.ThrowableUtils;
import com.foodlabel.nativeprinter.template.NativeTemplateCommandBuilder;
import com.foodlabel.nativeprinter.transport.GprinterBluetoothTransport;
import com.taobao.weex.annotation.JSMethod;
import com.taobao.weex.bridge.JSCallback;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.util.Base64;

import io.dcloud.feature.uniapp.common.UniModule;

public class NativeFastPrinterModule extends UniModule {
    private static final String BACKEND = "gprinter-sdk";
    private static final String PLUGIN_VERSION = "1.2.0";
    private static final Object LOCK = new Object();
    private static final ExecutorService PRINT_EXECUTOR = Executors.newSingleThreadExecutor();
    private static final NativePrintDebugState DEBUG_STATE = new NativePrintDebugState(BACKEND, PLUGIN_VERSION);
    private static final GprinterBluetoothTransport BLUETOOTH_TRANSPORT = new GprinterBluetoothTransport();

    @JSMethod(uiThread = false)
    public void connect(JSONObject params, JSCallback callback) {
        String deviceId = SafeJson.getString(params, "deviceId", "");
        String deviceName = SafeJson.getString(params, "deviceName", "");
        PluginResult result = ensureConnected(deviceId, deviceName);
        if (callback != null) {
            callback.invoke(result.toJsonString());
        }
    }

    @JSMethod(uiThread = false)
    public void disconnect(JSCallback callback) {
        synchronized (LOCK) {
            BLUETOOTH_TRANSPORT.disconnect();
            DEBUG_STATE.clearCurrentDevice();
            DEBUG_STATE.setStage("disconnect:ok");
            DEBUG_STATE.clearError();
        }
        if (callback != null) {
            callback.invoke(debugResult(PluginResult.ok(false, "", "", "disconnect:ok")).toJsonString());
        }
    }

    @JSMethod(uiThread = false)
    public void isConnected(JSCallback callback) {
        boolean connected;
        synchronized (LOCK) {
            connected = BLUETOOTH_TRANSPORT.isConnected();
        }
        if (callback != null) {
            callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "isConnected:ok")).toJsonString());
        }
    }

    @JSMethod(uiThread = false)
    public void getDebugInfo(JSCallback callback) {
        boolean connected;
        synchronized (LOCK) {
            connected = BLUETOOTH_TRANSPORT.isConnected();
        }
        if (callback != null) {
            callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "debug:ok")).toJsonString());
        }
    }

    @JSMethod(uiThread = false)
    public void printTemplate(JSONObject params, JSCallback callback) {
        String deviceId = SafeJson.getString(params, "deviceId", "");
        String deviceName = SafeJson.getString(params, "deviceName", "");
        String templateJson = SafeJson.getString(params, "templateJson", "");
        String dataJson = SafeJson.getString(params, "dataJson", "{}");
        int dpi = SafeJson.getInt(params, "dpi", 203);
        int printQty = Math.max(1, SafeJson.getInt(params, "printQty", 1));

        if (templateJson == null || templateJson.trim().isEmpty()) {
            if (callback != null) {
                callback.invoke(errorResult(9011006, "Template json is empty.").toJsonString());
            }
            return;
        }

        PluginResult connectResult = ensureConnected(deviceId, deviceName);
        if (!connectResult.success) {
            if (callback != null) callback.invoke(connectResult.toJsonString());
            return;
        }

        DEBUG_STATE.setStage("printTemplate:queued");
        if (callback != null) {
            callback.invoke(debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "printTemplate:queued")).toJsonString());
        }

        PRINT_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    DEBUG_STATE.resetBuildMetrics();
                    DEBUG_STATE.setStage("build-command");
                    long buildStarted = System.currentTimeMillis();
                    NativeTemplateCommandBuilder.BuildResult buildResult =
                            NativeTemplateCommandBuilder.buildWithStats(templateJson, dataJson, dpi, printQty);
                    DEBUG_STATE.setBuildMs(Math.max(0L, System.currentTimeMillis() - buildStarted));
                    DEBUG_STATE.setBuildResult(buildResult);

                    long writeStarted = System.currentTimeMillis();
                    synchronized (LOCK) {
                        if (!BLUETOOTH_TRANSPORT.isConnected()) {
                            errorResult(9011005, "Bluetooth printer transport is not ready.");
                            return;
                        }
                        DEBUG_STATE.setStage("write-command");
                        boolean ok = BLUETOOTH_TRANSPORT.write(buildResult.bytes);
                        if (!ok) {
                            errorResult(9011011, "Printer writeDataImmediately returned false.");
                            return;
                        }
                    }
                    DEBUG_STATE.setWriteMs(Math.max(0L, System.currentTimeMillis() - writeStarted));
                    DEBUG_STATE.markPrinted(System.currentTimeMillis());
                    DEBUG_STATE.setStage("printTemplate:ok");
                    DEBUG_STATE.clearError();
                } catch (Throwable e) {
                    DEBUG_STATE.setError(ThrowableUtils.unwrap(e));
                    DEBUG_STATE.setStage("printTemplate:error");
                }
            }
        });
    }

    /**
     * 将 JS 侧已生成的 TSC/指令字节(Base64)经佳博 SDK 写出。
     * 用于整页光栅等路径,避免再走 JS 经典蓝牙 socket(慢、易超时)。
     */
    @JSMethod(uiThread = false)
    public void printCommandBytes(JSONObject params, JSCallback callback) {
        String deviceId = SafeJson.getString(params, "deviceId", "");
        String deviceName = SafeJson.getString(params, "deviceName", "");
        String base64 = SafeJson.getString(params, "base64", "");

        if (base64 == null || base64.trim().isEmpty()) {
            if (callback != null) {
                callback.invoke(errorResult(9011012, "base64 is empty.").toJsonString());
            }
            return;
        }

        PluginResult connectResult = ensureConnected(deviceId, deviceName);
        if (!connectResult.success) {
            if (callback != null) {
                callback.invoke(connectResult.toJsonString());
            }
            return;
        }

        DEBUG_STATE.setStage("printCommandBytes:queued");
        if (callback != null) {
            callback.invoke(debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "printCommandBytes:queued")).toJsonString());
        }

        PRINT_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] bytes = Base64.decode(base64, Base64.DEFAULT);
                    if (bytes == null || bytes.length == 0) {
                        DEBUG_STATE.setError("decoded bytes empty");
                        DEBUG_STATE.setStage("printCommandBytes:error");
                        return;
                    }
                    long writeStarted = System.currentTimeMillis();
                    synchronized (LOCK) {
                        if (!BLUETOOTH_TRANSPORT.isConnected()) {
                            errorResult(9011005, "Bluetooth printer transport is not ready.");
                            return;
                        }
                        DEBUG_STATE.setStage("write-raw-command");
                        boolean ok = BLUETOOTH_TRANSPORT.write(bytes);
                        if (!ok) {
                            errorResult(9011011, "Printer writeDataImmediately returned false.");
                            return;
                        }
                    }
                    DEBUG_STATE.setWriteMs(Math.max(0L, System.currentTimeMillis() - writeStarted));
                    DEBUG_STATE.markPrinted(System.currentTimeMillis());
                    DEBUG_STATE.setStage("printCommandBytes:ok");
                    DEBUG_STATE.clearError();
                } catch (Throwable e) {
                    DEBUG_STATE.setError(ThrowableUtils.unwrap(e));
                    DEBUG_STATE.setStage("printCommandBytes:error");
                }
            }
        });
    }

    private PluginResult ensureConnected(String deviceId, String deviceName) {
        synchronized (LOCK) {
            PluginResult result = BLUETOOTH_TRANSPORT.ensureConnected(deviceId, deviceName, DEBUG_STATE);
            if (!result.success) {
                return debugResult(result);
            }
            return debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "connect:ok"));
        }
    }

    private static PluginResult errorResult(int code, String message) {
        DEBUG_STATE.setError(message == null ? "" : message);
        return debugResult(PluginResult.error(code, message));
    }

    private static PluginResult debugResult(PluginResult result) {
        return DEBUG_STATE.attachTo(result);
    }
}