completed intent code

This commit is contained in:
Gokul Erusappan 2026-07-01 11:57:40 +05:30
parent 26d310b278
commit 4191458e15
17 changed files with 2800 additions and 428 deletions

115
README.md
View File

@ -29,9 +29,13 @@ npx cap sync
* [`requestPermissions()`](#requestpermissions)
* [`scanBluetooth()`](#scanbluetooth)
* [`scanUsb()`](#scanusb)
* [`connectUsb()`](#connectusb)
* [`scanNetwork()`](#scannetwork)
* [`printBluetooth(...)`](#printbluetooth)
* [`printNetwork(...)`](#printnetwork)
* [`printUsb(...)`](#printusb)
* [`printIntent(...)`](#printintent)
* [`checkPrinterStatus(...)`](#checkprinterstatus)
* [Interfaces](#interfaces)
</docgen-index>
@ -76,6 +80,32 @@ Scan connected USB devices
--------------------
### connectUsb()
```typescript
connectUsb() => Promise<UsbConnectionResult>
```
Connect to USB printer
**Returns:** <code>Promise&lt;<a href="#usbconnectionresult">UsbConnectionResult</a>&gt;</code>
--------------------
### scanNetwork()
```typescript
scanNetwork() => Promise<{ devices: NetworkPrinter[]; }>
```
Scan Wi-Fi / LAN printers
**Returns:** <code>Promise&lt;{ devices: NetworkPrinter[]; }&gt;</code>
--------------------
### printBluetooth(...)
```typescript
@ -127,6 +157,36 @@ Print via USB OTG
--------------------
### printIntent(...)
```typescript
printIntent(options: PrintIntentOptions) => Promise<void>
```
| Param | Type |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#printintentoptions">PrintIntentOptions</a></code> |
--------------------
### checkPrinterStatus(...)
```typescript
checkPrinterStatus(options: PrinterStatusOptions) => Promise<PrinterStatusResult>
```
Check printer online/offline status
| Param | Type |
| ------------- | --------------------------------------------------------------------- |
| **`options`** | <code><a href="#printerstatusoptions">PrinterStatusOptions</a></code> |
**Returns:** <code>Promise&lt;<a href="#printerstatusresult">PrinterStatusResult</a>&gt;</code>
--------------------
### Interfaces
@ -146,6 +206,24 @@ Print via USB OTG
| **`address`** | <code>string</code> |
#### UsbConnectionResult
| Prop | Type |
| --------------- | -------------------- |
| **`name`** | <code>string</code> |
| **`address`** | <code>string</code> |
| **`connected`** | <code>boolean</code> |
#### NetworkPrinter
| Prop | Type |
| ------------- | ------------------- |
| **`name`** | <code>string</code> |
| **`address`** | <code>string</code> |
| **`port`** | <code>number</code> |
#### BluetoothPrintOptions
| Prop | Type |
@ -170,4 +248,41 @@ Print via USB OTG
| **`data`** | <code>number[]</code> |
| **`deviceId`** | <code>string</code> |
#### PrintIntentOptions
| Prop | Type |
| ----------------------- | ------------------- |
| **`receiptText`** | <code>string</code> |
| **`estimateText`** | <code>string</code> |
| **`logoImage`** | <code>string</code> |
| **`tokenDataSales`** | <code>any[]</code> |
| **`tokenDataEstimate`** | <code>any[]</code> |
| **`orderId`** | <code>string</code> |
| **`estimateOrderId`** | <code>string</code> |
| **`footerPrint`** | <code>string</code> |
| **`upiId`** | <code>string</code> |
| **`totalAmount`** | <code>string</code> |
| **`estimateAmount`** | <code>string</code> |
#### PrinterStatusResult
| Prop | Type |
| --------------- | ---------------------------------- |
| **`connected`** | <code>boolean</code> |
| **`status`** | <code>'online' \| 'offline'</code> |
| **`type`** | <code>string</code> |
| **`address`** | <code>string</code> |
| **`name`** | <code>string</code> |
| **`message`** | <code>string</code> |
#### PrinterStatusOptions
| Prop | Type |
| ------------- | -------------------------------------------- |
| **`type`** | <code>'USB' \| 'Bluetooth' \| 'Wi-Fi'</code> |
| **`address`** | <code>string</code> |
</docgen-api>

View File

@ -52,6 +52,9 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation 'com.google.code.gson:gson:2.14.0'
implementation 'com.github.dantsu:escpos-thermalprinter-android:3.4.0'
implementation "com.google.zxing:core:3.5.4"
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"

View File

@ -1,11 +1,25 @@
package com.pozo.printer;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.core.content.ContextCompat;
import com.getcapacitor.JSObject;
import com.pozo.printer.helper.LogoCache;
import com.pozo.printer.model.TokenItem;
import com.pozo.printer.utils.EscPosUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -13,72 +27,455 @@ import java.util.UUID;
public class BluetoothPrinter {
private static final UUID SPP_UUID =
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private static byte[] CUT_PAPER = new byte[] { 0x1D, 0x56, 0x00 };
public static List<JSObject> scanDevices(Context context) {
List<JSObject> result = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || !adapter.isEnabled()) return result;
Set<BluetoothDevice> paired = adapter.getBondedDevices();
for (BluetoothDevice device : paired) {
JSObject obj = new JSObject();
obj.put("name", device.getName() != null ? device.getName() : "Unknown");
obj.put("address", device.getAddress());
result.add(obj);
private static final UUID PRINTER_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
public static List<JSObject> scanDevices(Context context) throws Exception {
List<JSObject> result = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
return result;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED) {
throw new Exception("Bluetooth permission not granted");
}
Set<BluetoothDevice> paired = adapter.getBondedDevices();
for (BluetoothDevice device : paired) {
JSObject obj = new JSObject();
obj.put(
"name",
device.getName() != null
? device.getName()
: "Unknown");
obj.put(
"address",
device.getAddress());
result.add(obj);
}
return result;
}
return result;
}
public static void print(String address, byte[] data) throws Exception {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || !adapter.isEnabled()) {
throw new Exception("Bluetooth is not available or not enabled");
}
public static void print(String address, byte[] data) throws Exception {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null || !adapter.isEnabled()) {
throw new Exception("Bluetooth is not available or not enabled");
}
BluetoothDevice device = adapter.getRemoteDevice(address);
adapter.cancelDiscovery();
BluetoothDevice device = adapter.getRemoteDevice(address);
adapter.cancelDiscovery();
// Retry up to 3 times
int maxRetries = 3;
int attempt = 0;
Exception lastError = null;
// Retry up to 3 times
int maxRetries = 3;
int attempt = 0;
Exception lastError = null;
while (attempt < maxRetries) {
BluetoothSocket socket = null;
try {
socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
socket.connect();
while (attempt < maxRetries) {
BluetoothSocket socket = null;
try {
socket = device.createRfcommSocketToServiceRecord(PRINTER_UUID);
socket.connect();
OutputStream out = socket.getOutputStream();
int chunkSize = 512;
int offset = 0;
while (offset < data.length) {
int size = Math.min(chunkSize, data.length - offset);
out.write(data, offset, size);
out.flush();
offset += size;
Thread.sleep(10);
}
Thread.sleep(500);
return; // success, exit
OutputStream out = socket.getOutputStream();
int chunkSize = 512;
int offset = 0;
while (offset < data.length) {
int size = Math.min(chunkSize, data.length - offset);
out.write(data, offset, size);
out.flush();
offset += size;
Thread.sleep(10);
}
Thread.sleep(500);
return; // success, exit
} catch (Exception e) {
lastError = e;
attempt++;
if (socket != null) {
try { socket.close(); } catch (Exception ignored) {}
}
if (attempt < maxRetries) {
Thread.sleep(1000); // wait before retry
}
} finally {
if (socket != null) {
try { socket.close(); } catch (Exception ignored) {}
}
} catch (Exception e) {
lastError = e;
attempt++;
if (socket != null) {
try {
socket.close();
} catch (Exception ignored) {
}
}
if (attempt < maxRetries) {
Thread.sleep(1000); // wait before retry
}
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception ignored) {
}
}
}
}
throw new Exception("Failed after " + maxRetries + " attempts: " + lastError.getMessage());
}
}
throw new Exception("Failed after " + maxRetries + " attempts: " + lastError.getMessage());
}
public static void printBluetooth(
Context context,
String macAddress,
String receiptText,
String estimateText,
List<List<TokenItem>> salesTokens,
List<List<TokenItem>> estimateTokens,
String logoImage,
String totalAmount,
String estimateAmount,
String footerPrint,
String upiId,
String orderId,
String estimateOrderId,
String salesId,
String salesIdEst) throws Exception {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(macAddress);
BluetoothSocket socket = connectToDevice(context, device);
OutputStream outputStream = socket.getOutputStream();
long printStartTime = System.currentTimeMillis();
Log.d("PRINT_TIME", "Printing Started");
if (logoImage != null && !logoImage.isEmpty()) {
byte[] imageData = LogoCache.getBluetoothLogo(logoImage);
if (imageData != null) {
writeInChunks(outputStream, imageData);
Thread.sleep(2);
}
}
if (receiptText != null && !receiptText.isEmpty()) {
byte[] parsedText = parseFormattedTextToEscPos(receiptText, footerPrint);
writeInChunks(outputStream, parsedText);
Thread.sleep(2);
byte[] qrData = generateQRCodeByte(upiId, totalAmount, footerPrint);
writeInChunks(outputStream, qrData);
outputStream.write(CUT_PAPER);
}
if (estimateText != null && !estimateText.isEmpty()) {
byte[] parsedText = parseFormattedTextToEscPos(estimateText, footerPrint);
writeInChunks(outputStream, parsedText);
Thread.sleep(2);
byte[] qrData = generateQRCodeByte(upiId, estimateAmount, footerPrint);
writeInChunks(outputStream, qrData);
outputStream.write(CUT_PAPER);
}
if (salesTokens != null && !salesTokens.isEmpty()) {
printBluetoothTokens(
outputStream,
salesTokens,
orderId,
"Sales",
salesId);
}
if (estimateTokens != null && !estimateTokens.isEmpty()) {
printBluetoothTokens(
outputStream,
estimateTokens,
estimateOrderId,
"Estimate",
salesIdEst);
}
outputStream.flush();
outputStream.close();
socket.close();
long printEndTime = System.currentTimeMillis();
Log.d("PRINT_TIME", "Printing Completed");
Log.d("PRINT_TIME",
"Total Print Time = "
+ (printEndTime - printStartTime)
+ " ms");
}
private static BluetoothSocket connectToDevice(
Context context,
BluetoothDevice device) {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Log.e("PrinterLog", "Bluetooth not supported");
return null;
}
try {
// Android 12+ permission check
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("BLUETOOTH_CONNECT permission not granted");
}
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) {
bluetoothAdapter.cancelDiscovery();
}
} else {
bluetoothAdapter.cancelDiscovery();
}
Log.d("BT_TIME", "Before create socket");
BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(PRINTER_UUID);
Log.d("BT_TIME", "Before connect");
socket.connect();
Log.d("BT_TIME", "After connect");
Log.d("PrinterLog", "Bluetooth Connected Successfully");
return socket;
} catch (SecurityException e) {
Log.e("PrinterLog", "Bluetooth permission missing", e);
} catch (IOException e) {
Log.e("PrinterLog", "Bluetooth connection failed", e);
} catch (Exception e) {
Log.e("PrinterLog", "Unexpected Bluetooth error", e);
}
return null;
}
private static void printBluetoothTokens(OutputStream outputStream, List<List<TokenItem>> tokensList,
String orderId, String SalesorEstimate,
String salesId) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (List<TokenItem> token : tokensList) {
buffer.reset();
String tokenText = EscPosUtils.buildTokenDesign(token, orderId, salesId, SalesorEstimate);
byte[] tokenBytes = parseFormattedTextToEscPos(tokenText, "");
writeInChunks(outputStream, tokenBytes);
outputStream.write(new byte[]{0x0A, 0x0A, 0x0A});
outputStream.write(CUT_PAPER);
}
}
private static byte[] parseFormattedTextToEscPos(String text, String footerPrint) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
String[] lines = text.split("\n");
buffer.write(new byte[] { 0x1B, 0x40 }); /* Reset the printer */
for (String line : lines) {
// Skip empty lines
if (line.trim().isEmpty()) {
buffer.write("\n".getBytes());
continue;
}
// Skip HTML comments
if (line.trim().startsWith("<!--") || line.trim().contains("<!--")) {
continue;
}
if (line.contains("[L]") && line.contains("[R]")) {
String left = line.substring(line.indexOf("[L]") + 3, line.indexOf("[R]"));
String right = line.substring(line.indexOf("[R]") + 3);
left = left.replaceAll("<[^>]*>", "").trim();
right = right.replaceAll("<[^>]*>", "").trim();
String formatted = String.format("%-24s%24s", left, right);
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
buffer.write(formatted.getBytes(StandardCharsets.UTF_8));
buffer.write(0x0A);
continue;
}
// Check and apply alignment
byte alignment = 0x00; // Default left
if (line.startsWith("[C]")) {
alignment = 0x01; // Center
line = line.substring(3);
} else if (line.startsWith("[R]")) {
alignment = 0x02; // Right
line = line.substring(3);
} else if (line.startsWith("[L]")) {
alignment = 0x00; // Left
line = line.substring(3);
}
buffer.write(new byte[] { 0x1B, 0x61, alignment }); // ESC a n - Set alignment
// Check for formatting tags
boolean isBold = line.contains("<b>");
boolean isUnderline = line.contains("<u>");
boolean isBigFont = line.contains("<font size='big'>") || line.contains("<font size=\"big\">");
boolean isNormalFont = line.contains("<font size='normal'>") || line.contains("<font size=\"normal\">");
// Apply bold
if (isBold) {
buffer.write(new byte[] { 0x1B, 0x45, 0x01 }); // ESC E 1 - Bold ON
}
// Apply underline
if (isUnderline) {
buffer.write(new byte[] { 0x1B, 0x2D, 0x01 }); // ESC - 1 - Underline ON
}
// Apply font size
if (isBigFont) {
buffer.write(new byte[] { 0x1D, 0x21, 0x11 }); // GS ! 17 - Double width and height
} else if (isNormalFont) {
buffer.write(new byte[] { 0x1D, 0x21, 0x00 }); // GS ! 0 - Normal size
}
/* remove the HTML line And the tags */
String cleanLine = line
.replaceAll("<!--.*?-->", "")
.replaceAll("<b>|</b>", "")
.replaceAll("<u>|</u>", "")
.replaceAll("<font[^>]*>|</font>", "")
.replace("[L]", "")
.replace("[R]", "")
.replace("[C]", "")
.replace("", "-")
.trim();
// Only write if there's content after cleaning
if (!cleanLine.isEmpty()) {
buffer.write(cleanLine.getBytes(StandardCharsets.UTF_8));
buffer.write(0x0A);
buffer.write(new byte[] { 0x1B, 0x45, 0x00 }); // Bold OFF /*This one For reset a bold. bold off*/
buffer.write(new byte[] { 0x1B, 0x2D, 0x00 }); // Underline OFF /* This one for reset the Underline
// to off*/
buffer.write(new byte[] { 0x1D, 0x21, 0x00 }); // Normal size /*This One for normal size*/
}
}
// Reset to left alignment at end
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
} catch (Exception e) {
Log.e("ParseError", "Error parsing formatted text", e);
}
return buffer.toByteArray();
}
private static byte[] generateQRCodeByte(String upiId, String amount, String footerPrint) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
if (upiId != null && !upiId.isEmpty()) {
buffer.write(new byte[] { 0x1B, 0x61, 0x01 });
String upiString = "upi://pay?pa=" + upiId + "&pn=Merchant&am=" + amount + "&cu=INR";
buffer.write(EscPosUtils.generateQRCodeEscPos(upiString));
buffer.write("Scan To Pay\n".getBytes(StandardCharsets.UTF_8));
buffer.write(("Rs: " + amount + "\n").getBytes(StandardCharsets.UTF_8));
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
}
buffer.write(EscPosUtils.generateFooter(footerPrint));
} catch (Exception e) {
Log.e("QR", "QR Error", e);
}
return buffer.toByteArray();
}
private static void writeInChunks(OutputStream out, byte[] data) throws Exception {
int offset = 0;
int chunkSize = 512;
while (offset < data.length) {
int len = Math.min(chunkSize, data.length - offset);
out.write(data, offset, len);
out.flush();
Log.d("BT_CHUNK", "Sent: " + offset + " to " + (offset + len));
offset += len;
Thread.sleep(2);
}
}
}

View File

@ -1,19 +1,346 @@
package com.pozo.printer;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.format.Formatter;
import android.util.Log;
import com.dantsu.escposprinter.EscPosPrinter;
import com.dantsu.escposprinter.connection.tcp.TcpConnection;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.pozo.printer.helper.ReceiptBuilder;
import com.pozo.printer.model.TokenItem;
import com.pozo.printer.utils.EscPosUtils;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NetworkPrinter {
public static void print(String host, int port, byte[] data) throws Exception {
Socket socket = new Socket(host, port);
socket.setSoTimeout(10000);
try {
OutputStream out = socket.getOutputStream();
out.write(data);
out.flush();
} finally {
socket.close();
private static final String TAG = "NETWORK_PRINTER";
private static final int PORT = 9100;
private static final int TIMEOUT = 1000;
private static final int THREAD_COUNT = 20;
private static final int RETRY_COUNT = 2;
/**
* Scan current subnet for ESC/POS printers.
*/
public static JSArray scan(Context context) throws Exception {
JSArray printers = new JSArray();
WifiManager wifiManager =
(WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
throw new Exception("WifiManager unavailable");
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo == null) {
throw new Exception("Not connected to Wi-Fi");
}
String myIp = Formatter.formatIpAddress(wifiInfo.getIpAddress());
if (myIp == null || myIp.equals("0.0.0.0")) {
throw new Exception("Invalid IP Address");
}
Log.d(TAG, "My IP : " + myIp);
String subnet =
myIp.substring(0, myIp.lastIndexOf('.') + 1);
Log.d(TAG, "Subnet : " + subnet);
ExecutorService executor =
Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch =
new CountDownLatch(254);
Set<String> foundHosts =
Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i <= 254; i++) {
final String host = subnet + i;
executor.execute(() -> {
try {
boolean connected = false;
for (int retry = 0; retry < RETRY_COUNT; retry++) {
Socket socket = new Socket();
try {
socket.connect(
new InetSocketAddress(host, PORT),
TIMEOUT);
connected = true;
break;
} catch (Exception ignored) {
} finally {
try {
socket.close();
} catch (Exception ignored) {
}
}
Thread.sleep(100);
}
if (connected && foundHosts.add(host)) {
Log.d(TAG, "Printer Found : " + host);
synchronized (printers) {
JSObject obj = new JSObject();
obj.put("name", "Network Printer");
obj.put("address", host);
obj.put("port", PORT);
printers.put(obj);
}
}
} catch (Exception ignored) {
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
Log.d(TAG, "Total Printers : " + printers.length());
return printers;
}
/**
* Print raw ESC/POS data.
*/
public static void print(
String host,
int port,
byte[] data) throws Exception {
Socket socket = new Socket();
socket.connect(
new InetSocketAddress(host, port),
5000);
socket.setSoTimeout(10000);
try {
OutputStream out =
socket.getOutputStream();
out.write(data);
out.flush();
} finally {
socket.close();
}
}
/**
* Check whether a printer is reachable.
*/
public static boolean isReachable(
String host,
int port) {
try {
Socket socket = new Socket();
socket.connect(
new InetSocketAddress(host, port),
500);
socket.close();
return true;
} catch (Exception e) {
return false;
}
}
public static void printNetwork(
String host,
int port,
String receiptText,
String estimateText,
List<List<TokenItem>> salesTokens,
List<List<TokenItem>> estimateTokens,
String logoImage,
String totalAmount,
String estimateAmount,
String footerPrint,
String upiId,
String orderId,
String estimateOrderId,
String salesId,
String salesIdEst) throws Exception {
Log.d("NETWORK_PRINT", "========== START ==========");
Log.d("NETWORK_PRINT", "Host : " + host);
Log.d("NETWORK_PRINT", "Port : " + port);
try {
Log.d("NETWORK_PRINT", "Creating TcpConnection...");
TcpConnection connection = new TcpConnection(host, port, 5000);
Log.d("NETWORK_PRINT", "Creating EscPosPrinter...");
EscPosPrinter printer = new EscPosPrinter(
connection,
203,
80f,
45);
Log.d("NETWORK_PRINT", "Printer Connected");
// ---------------- Receipt ----------------
if (receiptText != null && !receiptText.isEmpty()) {
Log.d("NETWORK_PRINT", "Building Receipt...");
String receipt = ReceiptBuilder.buildReceipt(
printer,
receiptText,
logoImage,
upiId,
totalAmount,
footerPrint);
Log.d("NETWORK_PRINT", "Receipt Length : " + receipt.length());
Log.d("NETWORK_PRINT", "Printing Receipt...");
printer.printFormattedTextAndCut(receipt);
Log.d("NETWORK_PRINT", "Receipt Printed");
}
// ---------------- Estimate ----------------
if (estimateText != null && !estimateText.isEmpty()) {
Log.d("NETWORK_PRINT", "Building Estimate...");
String estimate = ReceiptBuilder.buildReceipt(
printer,
estimateText,
logoImage,
upiId,
estimateAmount,
footerPrint);
Log.d("NETWORK_PRINT", "Estimate Length : " + estimate.length());
Log.d("NETWORK_PRINT", "Printing Estimate...");
printer.printFormattedTextAndCut(estimate);
Log.d("NETWORK_PRINT", "Estimate Printed");
}
// ---------------- Sales Tokens ----------------
if (salesTokens != null && !salesTokens.isEmpty()) {
Log.d("NETWORK_PRINT",
"Printing Sales Tokens : " + salesTokens.size());
EscPosUtils.printUsbOrNetworkTokens(
printer,
salesTokens,
orderId,
salesId,
"Sales");
Log.d("NETWORK_PRINT", "Sales Tokens Printed");
}
// ---------------- Estimate Tokens ----------------
if (estimateTokens != null && !estimateTokens.isEmpty()) {
Log.d("NETWORK_PRINT",
"Printing Estimate Tokens : " + estimateTokens.size());
EscPosUtils.printUsbOrNetworkTokens(
printer,
estimateTokens,
estimateOrderId,
salesIdEst,
"Estimate");
Log.d("NETWORK_PRINT", "Estimate Tokens Printed");
}
Log.d("NETWORK_PRINT", "Disconnecting Printer...");
printer.disconnectPrinter();
Log.d("NETWORK_PRINT", "Printer Disconnected");
} catch (Exception e) {
Log.e("NETWORK_PRINT", "Print Failed", e);
throw e;
}
Log.d("NETWORK_PRINT", "=========== END ===========");
}
}
}

View File

@ -1,7 +1,18 @@
package com.pozo.printer;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.util.Log;
import androidx.core.content.ContextCompat;
import com.dantsu.escposprinter.connection.bluetooth.BluetoothConnection;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.PermissionState;
@ -11,197 +22,530 @@ import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.pozo.printer.model.TokenItem;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@CapacitorPlugin(
name = "Printer",
permissions = {
@Permission(
strings = {
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
},
alias = "bluetooth"
),
@Permission(
strings = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE
},
alias = "network"
)
}
)
@CapacitorPlugin(name = "Printer", permissions = {
@Permission(strings = {
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
}, alias = "bluetooth"),
@Permission(strings = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE
}, alias = "network")
})
public class PrinterPlugin extends Plugin {
// Request Permissions
// Request Permissions
@PluginMethod
public void requestPermissions(PluginCall call) {
requestAllPermissions(call, "permissionsCallback");
}
@PermissionCallback
private void permissionsCallback(PluginCall call) {
JSObject result = new JSObject();
result.put("bluetooth", getPermissionState("bluetooth").toString().toLowerCase());
result.put("network", getPermissionState("network").toString().toLowerCase());
call.resolve(result);
}
// Bluetooth Scan
@PluginMethod
public void scanBluetooth(PluginCall call) {
if (
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
getContext().checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED
) {
requestPermissionForAlias("bluetooth", call, "bluetoothScanCallback");
return;
@PluginMethod
public void requestPermissions(PluginCall call) {
requestAllPermissions(call, "permissionsCallback");
}
performBluetoothScan(call);
}
@PermissionCallback
private void bluetoothScanCallback(PluginCall call) {
if (getPermissionState("bluetooth") == PermissionState.GRANTED) {
performBluetoothScan(call);
} else {
call.reject("Bluetooth permission denied");
}
}
private void performBluetoothScan(PluginCall call) {
try {
List<JSObject> devices = BluetoothPrinter.scanDevices(getContext());
JSObject result = new JSObject();
JSArray arr = new JSArray();
for (JSObject d : devices) arr.put(d);
result.put("devices", arr);
call.resolve(result);
} catch (Exception e) {
call.reject("Bluetooth scan failed: " + e.getMessage());
}
}
// Bluetooth Print
@PluginMethod
public void printBluetooth(PluginCall call) {
if (
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
getContext().checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
!= PackageManager.PERMISSION_GRANTED
) {
requestPermissionForAlias("bluetooth", call, "bluetoothPrintCallback");
return;
}
performBluetoothPrint(call);
}
@PermissionCallback
private void bluetoothPrintCallback(PluginCall call) {
if (getPermissionState("bluetooth") == PermissionState.GRANTED) {
performBluetoothPrint(call);
} else {
call.reject("Bluetooth permission denied");
}
}
private void performBluetoothPrint(PluginCall call) {
String address = call.getString("address");
JSArray dataArray = call.getArray("data");
if (address == null || dataArray == null) {
call.reject("Missing address or data");
return;
}
new Thread(() -> {
try {
byte[] data = jsArrayToBytes(dataArray);
BluetoothPrinter.print(address, data);
@PermissionCallback
private void permissionsCallback(PluginCall call) {
JSObject result = new JSObject();
result.put("success", true);
result.put("bluetooth", getPermissionState("bluetooth").toString().toLowerCase());
result.put("network", getPermissionState("network").toString().toLowerCase());
call.resolve(result);
} catch (Exception e) {
call.reject("Bluetooth print failed: " + e.getMessage());
}
}).start();
}
// USB Scan
@PluginMethod
public void scanUsb(PluginCall call) {
try {
List<JSObject> devices = UsbPrinter.scanDevices(getContext());
JSObject result = new JSObject();
JSArray arr = new JSArray();
for (JSObject d : devices) arr.put(d);
result.put("devices", arr);
call.resolve(result);
} catch (Exception e) {
call.reject("USB scan failed: " + e.getMessage());
}
}
// USB Print
// Bluetooth Scan
@PluginMethod
public void printUsb(PluginCall call) {
JSArray dataArray = call.getArray("data");
String deviceId = call.getString("deviceId", null);
if (dataArray == null) {
call.reject("Missing data");
return;
@PluginMethod
public void scanBluetooth(PluginCall call) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
getContext().checkSelfPermission(
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
requestPermissionForAlias("bluetooth", call, "bluetoothScanCallback");
return;
}
performBluetoothScan(call);
}
new Thread(() -> {
try {
byte[] data = jsArrayToBytes(dataArray);
UsbPrinter.print(getContext(), data, deviceId);
@PermissionCallback
private void bluetoothScanCallback(PluginCall call) {
if (getPermissionState("bluetooth") == PermissionState.GRANTED) {
performBluetoothScan(call);
} else {
call.reject("Bluetooth permission denied");
}
}
private void performBluetoothScan(PluginCall call) {
try {
List<JSObject> devices = BluetoothPrinter.scanDevices(getContext());
JSObject result = new JSObject();
JSArray arr = new JSArray();
for (JSObject d : devices)
arr.put(d);
result.put("devices", arr);
call.resolve(result);
} catch (Exception e) {
call.reject("Bluetooth scan failed: " + e.getMessage());
}
}
// Bluetooth Print
@PluginMethod
public void printBluetooth(PluginCall call) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S &&
getContext().checkSelfPermission(
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
requestPermissionForAlias("bluetooth", call, "bluetoothPrintCallback");
return;
}
performBluetoothPrint(call);
}
@PluginMethod
public void scanNetwork(PluginCall call) {
new Thread(() -> {
try {
JSObject ret = new JSObject();
ret.put("devices", NetworkPrinter.scan(getContext()));
call.resolve(ret);
} catch (Exception e) {
call.reject(e.getMessage(), e);
}
}).start();
}
@PermissionCallback
private void bluetoothPrintCallback(PluginCall call) {
if (getPermissionState("bluetooth") == PermissionState.GRANTED) {
performBluetoothPrint(call);
} else {
call.reject("Bluetooth permission denied");
}
}
private void performBluetoothPrint(PluginCall call) {
String address = call.getString("address");
JSArray dataArray = call.getArray("data");
if (address == null || dataArray == null) {
call.reject("Missing address or data");
return;
}
new Thread(() -> {
try {
byte[] data = jsArrayToBytes(dataArray);
BluetoothPrinter.print(address, data);
JSObject result = new JSObject();
result.put("success", true);
call.resolve(result);
} catch (Exception e) {
call.reject("Bluetooth print failed: " + e.getMessage());
}
}).start();
}
// USB Scan
@PluginMethod
public void scanUsb(PluginCall call) {
try {
List<JSObject> devices = UsbPrinter.scanDevices(getContext());
JSObject result = new JSObject();
JSArray arr = new JSArray();
for (JSObject d : devices)
arr.put(d);
result.put("devices", arr);
call.resolve(result);
} catch (Exception e) {
call.reject("USB scan failed: " + e.getMessage());
}
}
// USB Connect
@PluginMethod
public void connectUsb(PluginCall call) {
try {
Log.d("USB_CONNECT" , "USB" + "connectUsb Triggered");
UsbPrinter.connectUsb(
getContext(),
new UsbPrinter.ConnectCallback() {
@Override
public void onConnected(JSObject printer) {
call.resolve(printer);
}
@Override
public void onError(String message) {
call.reject(message);
}
});
} catch (Exception e) {
call.reject(e.getMessage(), e);
}
}
// USB Print
@PluginMethod
public void printUsb(PluginCall call) {
JSArray dataArray = call.getArray("data");
String deviceId = call.getString("deviceId", null);
if (dataArray == null) {
call.reject("Missing data");
return;
}
byte[] data;
try {
data = jsArrayToBytes(dataArray);
} catch (Exception e) {
call.reject("Invalid data");
return;
}
UsbPrinter.printWithPermission(getContext(), data, deviceId, new UsbPrinter.PrintCallback() {
@Override
public void onSuccess() {
JSObject result = new JSObject();
result.put("success", true);
call.resolve(result);
}
@Override
public void onError(String message) {
call.reject("USB print failed: " + message);
}
});
}
// Network Print
@PluginMethod
public void printNetwork(PluginCall call) {
String host = call.getString("host");
int port = call.getInt("port", 9100);
JSArray dataArray = call.getArray("data");
if (host == null || dataArray == null) {
call.reject("Missing host or data");
return;
}
new Thread(() -> {
try {
byte[] data = jsArrayToBytes(dataArray);
NetworkPrinter.print(host, port, data);
JSObject result = new JSObject();
result.put("success", true);
call.resolve(result);
} catch (Exception e) {
call.reject("Network print failed: " + e.getMessage());
}
}).start();
}
// Helper
private byte[] jsArrayToBytes(JSArray arr) throws Exception {
byte[] bytes = new byte[arr.length()];
for (int i = 0; i < arr.length(); i++) {
bytes[i] = (byte) arr.getInt(i);
}
return bytes;
}
// Intent
@PluginMethod
public void printIntent(PluginCall call) {
String receiptText = call.getString("receiptText", "");
String estimateText = call.getString("estimateText", "");
String logoImage = call.getString("logoImage", "");
JSArray tokenDataSales = call.getArray("tokenDataSales");
JSArray tokenDataEstimate = call.getArray("tokenDataEstimate");
String orderId = call.getString("orderId", "");
String estimateOrderId = call.getString("estimateOrderId", "");
String salesId = call.getString("salesIdNormal", "");
String salesIdEst = call.getString("salesIdEst", "");
String footerPrint = call.getString("footerPrint", "");
String upiId = call.getString("upiId", "");
String totalAmount = call.getString("totalAmount", "");
String estimateAmount = call.getString("estimateAmount", "");
String macAddress = call.getString("macAddress", "");
String usbDeviceId = call.getString("usbAddress", "");
String wifiAddress = call.getString("wifiAddress", "");
Log.d("AddressUI", "macAddress" + macAddress);
Log.d("AddressUI", "usbAddress" + usbDeviceId);
Log.d("AddressUI", "wifiAddress" + wifiAddress);
Log.d("AddressUI", "totalAmount" + totalAmount);
Log.d("PluginData", "receiptText = " + receiptText);
Log.d("PluginData", "estimateText = " + estimateText);
Log.d("PluginData", "orderId = " + orderId);
Log.d("PluginData", "estimateOrderId = " + estimateOrderId);
Gson gson = new Gson();
Type tokenType = new TypeToken<List<List<TokenItem>>>() {
}.getType();
List<List<TokenItem>> salesTokens = new ArrayList<>();
List<List<TokenItem>> estimateTokens = new ArrayList<>();
if (tokenDataSales != null) {
salesTokens = gson.fromJson(tokenDataSales.toString(), tokenType);
Log.d("TokenDebug", "Sales Tokens Count: " + salesTokens.size());
}
Log.d("TokenDebug", "tokenDataEstimate: " + tokenDataEstimate);
if (tokenDataEstimate != null) {
estimateTokens = gson.fromJson(tokenDataEstimate.toString(), tokenType);
Log.d("TokenDebug", "Estimate Tokens Count: " + estimateTokens.size());
}
try {
if (macAddress != null && !macAddress.trim().isEmpty()) {
BluetoothPrinter.printBluetooth(
getContext(),
macAddress,
receiptText,
estimateText,
salesTokens,
estimateTokens,
logoImage,
totalAmount,
estimateAmount,
footerPrint,
upiId,
orderId,
estimateOrderId,
salesId,
salesIdEst);
} else if (usbDeviceId != null && !usbDeviceId.trim().isEmpty()) {
UsbPrinter.printUsb(
getContext(),
usbDeviceId,
receiptText,
estimateText,
logoImage,
totalAmount,
estimateAmount,
footerPrint,
upiId,
salesTokens,
estimateTokens,
orderId,
estimateOrderId,
salesId,
salesIdEst);
} else if (wifiAddress != null && !wifiAddress.trim().isEmpty()) {
String[] parts = wifiAddress.split(":");
String host = parts[0];
int port = Integer.parseInt(parts[1]);
NetworkPrinter.printNetwork(host, port, receiptText, estimateText, salesTokens, estimateTokens,
logoImage, totalAmount,
estimateAmount, footerPrint, upiId, orderId, estimateOrderId, salesId, salesIdEst);
} else {
call.reject("No printer selected");
return;
}
call.resolve();
} catch (Exception e) {
call.reject(e.getMessage(), e);
}
}
@PluginMethod
public void checkPrinterStatus(PluginCall call) {
JSObject result = new JSObject();
result.put("success", true);
call.resolve(result);
} catch (Exception e) {
call.reject("USB print failed: " + e.getMessage());
}
}).start();
}
// Network Print
try {
@PluginMethod
public void printNetwork(PluginCall call) {
String host = call.getString("host");
int port = call.getInt("port", 9100);
JSArray dataArray = call.getArray("data");
if (host == null || dataArray == null) {
call.reject("Missing host or data");
return;
String type = call.getString("type", "");
String address = call.getString("address", "");
if (type.isEmpty() || address.isEmpty()) {
call.reject("Type and address are required.");
return;
}
// --------------------------------------------------
// USB
// --------------------------------------------------
if ("USB".equalsIgnoreCase(type)) {
UsbManager usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
for (UsbDevice device : devices.values()) {
if (String.valueOf(device.getDeviceId()).equals(address)) {
result.put("connected", true);
result.put("status", "online");
result.put("type", "USB");
result.put("address", address);
result.put("name", device.getProductName());
call.resolve(result);
return;
}
}
result.put("connected", false);
result.put("status", "offline");
result.put("type", "USB");
result.put("address", address);
call.resolve(result);
return;
}
// --------------------------------------------------
// Bluetooth
// --------------------------------------------------
if ("Bluetooth".equalsIgnoreCase(type)) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
result.put("connected", false);
result.put("status", "offline");
result.put("message", "Bluetooth not supported");
call.resolve(result);
return;
}
BluetoothDevice device = adapter.getRemoteDevice(address);
try {
BluetoothConnection connection = new BluetoothConnection(device);
connection.connect();
connection.disconnect();
result.put("connected", true);
result.put("status", "online");
} catch (Exception e) {
result.put("connected", false);
result.put("status", "offline");
}
result.put("type", "Bluetooth");
result.put("address", address);
String name = "";
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S ||
ContextCompat.checkSelfPermission(
getContext(),
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED) {
name = device.getName();
}
result.put("name", name);
call.resolve(result);
return;
}
// --------------------------------------------------
// Wi-Fi
// --------------------------------------------------
if ("Wi-Fi".equalsIgnoreCase(type)) {
String[] split = address.split(":");
Socket socket = new Socket();
try {
socket.connect(
new InetSocketAddress(split[0], Integer.parseInt(split[1])),
1500);
socket.close();
result.put("connected", true);
result.put("status", "online");
} catch (Exception e) {
result.put("connected", false);
result.put("status", "offline");
}
result.put("type", "Wi-Fi");
result.put("address", address);
call.resolve(result);
return;
}
call.reject("Unsupported printer type.");
} catch (Exception e) {
call.reject(e.getMessage());
}
}
new Thread(() -> {
try {
byte[] data = jsArrayToBytes(dataArray);
NetworkPrinter.print(host, port, data);
JSObject result = new JSObject();
result.put("success", true);
call.resolve(result);
} catch (Exception e) {
call.reject("Network print failed: " + e.getMessage());
}
}).start();
}
// Helper
@Override
public void load() {
private byte[] jsArrayToBytes(JSArray arr) throws Exception {
byte[] bytes = new byte[arr.length()];
for (int i = 0; i < arr.length(); i++) {
bytes[i] = (byte) arr.getInt(i);
super.load();
UsbPrinter.initialize(getContext());
}
return bytes;
}
}

View File

@ -1,90 +1,662 @@
package com.pozo.printer;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.*;
import android.os.Build;
import android.util.Log;
import androidx.core.content.ContextCompat;
import com.dantsu.escposprinter.EscPosPrinter;
import com.dantsu.escposprinter.connection.usb.UsbConnection;
import com.getcapacitor.JSObject;
import com.pozo.printer.helper.ReceiptBuilder;
import com.pozo.printer.model.TokenItem;
import com.pozo.printer.utils.EscPosUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class UsbPrinter {
public static List<JSObject> scanDevices(Context context) {
List<JSObject> result = new ArrayList<>();
UsbManager manager =
(UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null) return result;
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (UsbDevice device : devices.values()) {
JSObject obj = new JSObject();
obj.put(
"name",
device.getProductName() != null ? device.getProductName() : "USB Device"
);
obj.put("address", String.valueOf(device.getDeviceId()));
result.add(obj);
}
return result;
}
private static final String ACTION_USB_PERMISSION =
"com.pozo.printer.USB_PERMISSION";
public static void print(
Context context,
byte[] data,
String deviceIdStr
) throws Exception {
UsbManager manager =
(UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null) throw new Exception("USB service not available");
public static UsbDevice connectedUsbDevice;
HashMap<String, UsbDevice> devices = manager.getDeviceList();
UsbDevice targetDevice = null;
private static ConnectCallback pendingConnectCallback;
if (deviceIdStr != null) {
int deviceId = Integer.parseInt(deviceIdStr);
for (UsbDevice device : devices.values()) {
if (device.getDeviceId() == deviceId) {
targetDevice = device;
break;
private static final BroadcastReceiver usbReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!ACTION_USB_PERMISSION.equals(intent.getAction())) {
return;
}
if (pendingConnectCallback == null) {
return;
}
boolean granted =
intent.getBooleanExtra(
UsbManager.EXTRA_PERMISSION_GRANTED,
false);
UsbDevice device;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
device = intent.getParcelableExtra(
UsbManager.EXTRA_DEVICE,
UsbDevice.class);
} else {
device = intent.getParcelableExtra(
UsbManager.EXTRA_DEVICE);
}
if (granted && device != null) {
connectedUsbDevice = device;
JSObject obj = new JSObject();
obj.put(
"name",
device.getProductName() != null
? device.getProductName()
: "USB Printer");
obj.put(
"address",
String.valueOf(device.getDeviceId()));
obj.put("connected", true);
pendingConnectCallback.onConnected(obj);
} else {
pendingConnectCallback.onError(
"USB Permission Denied");
}
pendingConnectCallback = null;
}
};
public static void initialize(Context context) {
IntentFilter filter =
new IntentFilter(ACTION_USB_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(
usbReceiver,
filter,
Context.RECEIVER_NOT_EXPORTED);
} else {
context.registerReceiver(
usbReceiver,
filter);
}
}
} else {
if (!devices.isEmpty()) {
targetDevice = devices.values().iterator().next();
}
}
if (targetDevice == null) throw new Exception("USB printer not found");
if (!manager.hasPermission(targetDevice))
throw new Exception("USB permission denied — prompt user first");
public static UsbDevice findUsbPrinter(Context context) {
UsbInterface intf = targetDevice.getInterface(0);
UsbEndpoint endpoint = null;
for (int i = 0; i < intf.getEndpointCount(); i++) {
UsbEndpoint ep = intf.getEndpoint(i);
if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
endpoint = ep;
break;
}
UsbManager manager =
(UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null)
return null;
for (UsbDevice device : manager.getDeviceList().values()) {
for (int i = 0; i < device.getInterfaceCount(); i++) {
if (device.getInterface(i).getInterfaceClass()
== UsbConstants.USB_CLASS_PRINTER) {
return device;
}
}
}
return null;
}
if (endpoint == null) throw new Exception("No OUT endpoint on USB device");
UsbDeviceConnection connection = manager.openDevice(targetDevice);
if (connection == null) throw new Exception("Cannot open USB device");
// Callback interface
connection.claimInterface(intf, true);
try {
int chunkSize = endpoint.getMaxPacketSize();
int offset = 0;
while (offset < data.length) {
int size = Math.min(chunkSize, data.length - offset);
byte[] chunk = new byte[size];
System.arraycopy(data, offset, chunk, 0, size);
connection.bulkTransfer(endpoint, chunk, size, 5000);
offset += size;
}
} finally {
connection.releaseInterface(intf);
connection.close();
public interface ConnectCallback {
void onConnected(JSObject printer);
void onError(String message);
}
}
public interface PrintCallback {
void onSuccess();
void onError(String message);
}
// Scan
public static List<JSObject> scanDevices(Context context) {
List<JSObject> result = new ArrayList<>();
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null) return result;
for (UsbDevice device : manager.getDeviceList().values()) {
boolean isPrinter = false;
for (int i = 0; i < device.getInterfaceCount(); i++) {
UsbInterface usbInterface = device.getInterface(i);
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) {
isPrinter = true;
break;
}
}
if (isPrinter) {
JSObject obj = new JSObject();
obj.put("name", device.getProductName() != null ?
device.getProductName() : "USB Printer");
obj.put("address", String.valueOf(device.getDeviceId()));
result.add(obj);
}
}
return result;
}
private static final String TAG = "USB_CONNECT";
public static void connectUsb(
Context context,
ConnectCallback callback) throws Exception {
Log.d(TAG, "===== connectUsb() called =====");
UsbManager manager =
(UsbManager) context.getSystemService(Context.USB_SERVICE);
Log.d(TAG, "UsbManager: " + manager);
if (manager == null) {
Log.e(TAG, "UsbManager unavailable");
throw new Exception("UsbManager unavailable");
}
Log.d(TAG, "Searching for USB printer...");
UsbDevice printer = findUsbPrinter(context);
Log.d(TAG, "Printer found: " + printer);
if (printer == null) {
Log.e(TAG, "No USB printer found");
throw new Exception("No USB printer found");
}
Log.d(TAG, "Device Name: " + printer.getDeviceName());
Log.d(TAG, "Product Name: " + printer.getProductName());
Log.d(TAG, "Vendor ID: " + printer.getVendorId());
Log.d(TAG, "Product ID: " + printer.getProductId());
Log.d(TAG, "Device ID: " + printer.getDeviceId());
boolean hasPermission = manager.hasPermission(printer);
Log.d(TAG, "Has USB Permission: " + hasPermission);
// Already has permission
if (hasPermission) {
Log.d(TAG, "Permission already granted.");
connectedUsbDevice = printer;
JSObject obj = new JSObject();
obj.put(
"name",
printer.getProductName() != null
? printer.getProductName()
: "USB Printer");
obj.put(
"address",
String.valueOf(printer.getDeviceId()));
obj.put("connected", true);
Log.d(TAG, "Calling callback.onConnected()");
callback.onConnected(obj);
Log.d(TAG, "connectUsb() completed successfully.");
return;
}
Log.d(TAG, "Permission NOT granted. Requesting permission...");
// Save callback until permission result arrives
pendingConnectCallback = callback;
int flags;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags =
PendingIntent.FLAG_UPDATE_CURRENT
| PendingIntent.FLAG_MUTABLE;
Log.d(TAG, "Using FLAG_MUTABLE");
} else {
flags = PendingIntent.FLAG_UPDATE_CURRENT;
Log.d(TAG, "Using FLAG_UPDATE_CURRENT");
}
PendingIntent permissionIntent =
PendingIntent.getBroadcast(
context,
0,
new Intent(ACTION_USB_PERMISSION),
flags);
Log.d(TAG, "PendingIntent created.");
manager.requestPermission(
printer,
permissionIntent);
Log.d(TAG, "requestPermission() called. Waiting for BroadcastReceiver...");
}
// Find device
public static UsbDevice findDevice(UsbManager manager, String deviceIdStr) {
HashMap<String, UsbDevice> devices = manager.getDeviceList();
if (deviceIdStr != null) {
int id = Integer.parseInt(deviceIdStr);
for (UsbDevice d : devices.values()) {
if (d.getDeviceId() == id) return d;
}
return null;
}
return devices.isEmpty() ? null : devices.values().iterator().next();
}
// Print with auto permission
public static void printWithPermission(
Context context,
byte[] data,
String deviceIdStr,
PrintCallback callback
) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
if (manager == null) {
callback.onError("USB service not available");
return;
}
UsbDevice target = findDevice(manager, deviceIdStr);
if (target == null) {
callback.onError("USB printer not found");
return;
}
if (manager.hasPermission(target)) {
// Already permitted print immediately
new Thread(() -> {
try {
sendData(manager, target, data);
callback.onSuccess();
} catch (Exception e) {
callback.onError(e.getMessage());
}
}).start();
} else {
// No permission show dialog, then print on grant
PendingIntent pi = PendingIntent.getBroadcast(
context, 0,
new Intent(ACTION_USB_PERMISSION),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
if (!ACTION_USB_PERMISSION.equals(intent.getAction())) return;
context.unregisterReceiver(this);
boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
if (granted) {
new Thread(() -> {
try {
sendData(manager, target, data);
callback.onSuccess();
} catch (Exception e) {
callback.onError(e.getMessage());
}
}).start();
} else {
callback.onError("USB permission denied by user");
}
}
};
context.registerReceiver(receiver, new IntentFilter(ACTION_USB_PERMISSION));
manager.requestPermission(target, pi);
}
}
// Raw data transfer
private static void sendData(UsbManager manager, UsbDevice device, byte[] data) throws Exception {
Log.d("Gokullog", "sendData: " + device.getProductName());
UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = null;
for (int i = 0; i < intf.getEndpointCount(); i++) {
UsbEndpoint ep = intf.getEndpoint(i);
if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
endpoint = ep;
break;
}
}
if (endpoint == null) throw new Exception("No OUT endpoint on USB device");
UsbDeviceConnection connection = manager.openDevice(device);
if (connection == null) throw new Exception("Cannot open USB device");
connection.claimInterface(intf, true);
try {
int chunkSize = endpoint.getMaxPacketSize();
int offset = 0;
while (offset < data.length) {
int size = Math.min(chunkSize, data.length - offset);
byte[] chunk = new byte[size];
System.arraycopy(data, offset, chunk, 0, size);
int transferred = connection.bulkTransfer(endpoint, chunk, size, 5000);
if (transferred < 0) throw new Exception("Transfer failed at offset " + offset);
offset += size;
}
} finally {
connection.releaseInterface(intf);
connection.close();
}
}
public static void printUsb(
Context context,
String usbDeviceId,
String receiptText,
String estimateText,
String logoImage,
String totalAmount,
String estimateAmount,
String footerPrint,
String upiId,
List<List<TokenItem>> salesTokens,
List<List<TokenItem>> estimateTokens,
String orderId,
String estimateOrderId,
String salesId,
String salesIdEst) throws Exception {
UsbManager usbManager =
(UsbManager) context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList =
usbManager.getDeviceList();
UsbDevice selectedDevice = null;
for (UsbDevice device : deviceList.values()) {
if (String.valueOf(device.getDeviceId()).equals(usbDeviceId)) {
selectedDevice = device;
break;
}
}
if (selectedDevice == null) {
throw new Exception("USB printer not found");
}
if (!usbManager.hasPermission(selectedDevice)) {
PendingIntent permissionIntent =
PendingIntent.getBroadcast(
context,
0,
new Intent(ACTION_USB_PERMISSION),
PendingIntent.FLAG_IMMUTABLE);
BroadcastReceiver usbReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(
Context ctx,
Intent intent) {
if (!ACTION_USB_PERMISSION.equals(
intent.getAction())) {
return;
}
try {
ctx.unregisterReceiver(this);
} catch (Exception ignored) {
}
UsbDevice device;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
device = intent.getParcelableExtra(
UsbManager.EXTRA_DEVICE,
UsbDevice.class);
} else {
device = intent.getParcelableExtra(
UsbManager.EXTRA_DEVICE);
}
boolean granted =
intent.getBooleanExtra(
UsbManager.EXTRA_PERMISSION_GRANTED,
false);
if (!granted || device == null) {
Log.e("USB", "Permission denied");
return;
}
try {
printUsbInternal(
usbManager,
device,
receiptText,
estimateText,
logoImage,
totalAmount,
estimateAmount,
footerPrint,
upiId,
salesTokens,
estimateTokens,
orderId,
estimateOrderId,
salesId,
salesIdEst);
} catch (Exception e) {
Log.e("USB", "Print failed", e);
}
}
};
ContextCompat.registerReceiver(
context,
usbReceiver,
new IntentFilter(ACTION_USB_PERMISSION),
ContextCompat.RECEIVER_NOT_EXPORTED);
usbManager.requestPermission(
selectedDevice,
permissionIntent);
return;
}
printUsbInternal(
usbManager,
selectedDevice,
receiptText,
estimateText,
logoImage,
totalAmount,
estimateAmount,
footerPrint,
upiId,
salesTokens,
estimateTokens,
orderId,
estimateOrderId,
salesId,
salesIdEst);
}
private static void printUsbInternal(
UsbManager usbManager,
UsbDevice selectedDevice,
String receiptText,
String estimateText,
String logoImage,
String totalAmount,
String estimateAmount,
String footerPrint,
String upiId,
List<List<TokenItem>> salesTokens,
List<List<TokenItem>> estimateTokens,
String orderId,
String estimateOrderId,
String salesId,
String salesIdEst) throws Exception {
UsbConnection connection =
new UsbConnection(
usbManager,
selectedDevice);
long t = System.currentTimeMillis();
EscPosPrinter printer =
new EscPosPrinter(
connection,
203,
80f,
45);
Log.d("USB_TIME",
"Printer = "
+ (System.currentTimeMillis() - t));
if (receiptText != null &&
!receiptText.isEmpty()) {
t = System.currentTimeMillis();
String receipt =
ReceiptBuilder.buildReceipt(
printer,
receiptText,
logoImage,
upiId,
totalAmount,
footerPrint);
Log.d("USB_TIME",
"Receipt = "
+ (System.currentTimeMillis() - t));
t = System.currentTimeMillis();
printer.printFormattedTextAndCut(receipt);
Log.d("USB_TIME",
"Print = "
+ (System.currentTimeMillis() - t));
}
if (estimateText != null &&
!estimateText.isEmpty()) {
String estimate =
ReceiptBuilder.buildReceipt(
printer,
estimateText,
logoImage,
upiId,
estimateAmount,
footerPrint);
printer.printFormattedTextAndCut(estimate);
}
if (salesTokens != null &&
!salesTokens.isEmpty()) {
EscPosUtils.printUsbOrNetworkTokens(
printer,
salesTokens,
orderId,
salesId,
"Sales");
}
if (estimateTokens != null &&
!estimateTokens.isEmpty()) {
EscPosUtils.printUsbOrNetworkTokens(
printer,
estimateTokens,
estimateOrderId,
salesIdEst,
"Estimate");
}
printer.disconnectPrinter();
Log.d("USB", "Print Success");
}
}

View File

@ -0,0 +1,155 @@
package com.pozo.printer.helper;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.dantsu.escposprinter.EscPosPrinter;
import com.dantsu.escposprinter.textparser.PrinterTextParserImg;
import com.pozo.printer.utils.EscPosUtils;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class LogoCache {
private static byte[] cachedBluetoothLogo;
// USB (DantSu) hex string
private static String cachedUsbLogoHex;
private static String cachedLogoUrl;
public static Bitmap cachedBitmap;
public static Bitmap getCachedBitmap(String url) {
try {
if (cachedBitmap != null
&& !cachedBitmap.isRecycled()
&& url.equals(cachedLogoUrl)) {
Log.d("LOGO_CACHE", "Using cached bitmap");
return cachedBitmap;
}
Log.d("LOGO_CACHE", "Downloading logo");
URL logoUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) logoUrl.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(input);
input.close();
connection.disconnect();
cachedBitmap = bitmap;
cachedLogoUrl = url;
// invalidate converted caches
cachedBluetoothLogo = null;
cachedUsbLogoHex = null;
return cachedBitmap;
} catch (Exception e) {
Log.e("Bitmap", "Download failed", e);
return null;
}
}
public static byte[] getBluetoothLogo(String logoUrl) throws Exception {
if (cachedBluetoothLogo != null &&
logoUrl.equals(cachedLogoUrl)) {
Log.d("LOGO_CACHE", "Bluetooth logo cache");
return cachedBluetoothLogo;
}
Bitmap bitmap = getCachedBitmap(logoUrl);
if (bitmap == null)
return null;
Bitmap scaled = Bitmap.createScaledBitmap(
bitmap,
220,
220,
true);
Bitmap rgb565 = scaled.copy(
Bitmap.Config.RGB_565,
false);
Bitmap centered = EscPosUtils.centerBitmap(
rgb565,
576);
cachedBluetoothLogo = EscPosUtils.convertBitmapToEscPos(centered);
// recycle temporary bitmaps only
if (scaled != bitmap)
scaled.recycle();
if (rgb565 != scaled)
rgb565.recycle();
if (centered != rgb565)
centered.recycle();
return cachedBluetoothLogo;
}
public static String getUsbLogo(
EscPosPrinter printer,
String logoUrl) throws Exception {
if (cachedUsbLogoHex != null &&
logoUrl.equals(cachedLogoUrl)) {
Log.d("LOGO_CACHE", "USB logo cache");
return cachedUsbLogoHex;
}
Bitmap bitmap = getCachedBitmap(logoUrl);
if (bitmap == null)
return "";
Bitmap scaled = Bitmap.createScaledBitmap(
bitmap,
170,
170,
true);
// Center on 576-dot canvas (80mm printer)
Bitmap centered = EscPosUtils.centerBitmap(scaled, 576);
cachedUsbLogoHex = PrinterTextParserImg.bitmapToHexadecimalString(
printer,
centered);
if (centered != scaled) {
centered.recycle();
}
if (scaled != bitmap) {
scaled.recycle();
}
return cachedUsbLogoHex;
}
}

View File

@ -0,0 +1,158 @@
package com.pozo.printer.helper;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;
import com.dantsu.escposprinter.EscPosPrinter;
import com.dantsu.escposprinter.textparser.PrinterTextParserImg;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
public class ReceiptBuilder {
public static String buildReceipt(
EscPosPrinter printer,
String receiptText,
String logoImage,
String upiId,
String amount,
String footerPrint) {
String imageDisplay = "";
String qrCodeSection = "";
String footerVisible = "";
// -----------------------------
// LOGO
// -----------------------------
if (logoImage != null && !logoImage.trim().isEmpty()) {
try {
long t = System.currentTimeMillis();
String logoHex = LogoCache.getUsbLogo(printer, logoImage);
Log.d("USB_TIME", "Logo = " + (System.currentTimeMillis() - t));
if (logoHex != null && !logoHex.isEmpty()) {
imageDisplay = "[C]<img>"
+ logoHex
+ "</img>\n";
}
} catch (Exception e) {
Log.e("Printer", "Logo Error", e);
}
}
// -----------------------------
// UPI QR
// -----------------------------
Log.d("GokulLog", "Upi" + upiId);
Log.d("GokulLog", "amount" + amount);
if (upiId != null && !upiId.trim().isEmpty()
&& amount != null
&& !amount.trim().isEmpty()) {
try {
String upiString = "upi://pay?pa=" +
upiId +
"&pn=Merchant" +
"&am=" +
amount +
"&cu=INR";
Bitmap qrBitmap = generateQRCode(upiString);
qrCodeSection = "[C]<img>"
+ PrinterTextParserImg.bitmapToHexadecimalString(
printer,
qrBitmap)
+ "</img>\n"
+ "[C]<b><font size='small'>Scan To Pay:</font></b>\n"
+ "[C]Rs :- " + amount + "\n";
qrBitmap.recycle();
} catch (WriterException e) {
Log.e("Receipt", "QR Generation Error", e);
} catch (Exception e) {
Log.e("Receipt", "Unexpected QR Error", e);
}
}
// -----------------------------
// FOOTER
// -----------------------------
if ("Yes".equalsIgnoreCase(footerPrint)) {
footerVisible = "[C]<b>Thank You! Visit Again</b>\n";
} else {
footerVisible = "";
}
// -----------------------------
// FINAL RECEIPT
// -----------------------------
return imageDisplay
+ receiptText
+ qrCodeSection
+ footerVisible
+ "[L]\n\n\n";
}
private static Bitmap generateQRCode(String text) throws WriterException {
BitMatrix bitMatrix = new MultiFormatWriter().encode(
text,
BarcodeFormat.QR_CODE,
450,
450);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
bitmap.setPixel(
x,
y,
bitMatrix.get(x, y)
? Color.BLACK
: Color.WHITE);
}
}
return bitmap;
}
public static String trimText(String text, int maxLength) {
if (text == null) {
return "";
}
if (text.length() <= maxLength) {
return text;
}
return text.substring(0, maxLength);
}
}

View File

@ -0,0 +1,52 @@
package com.pozo.printer.model;
public class TokenItem {
public int slNo;
public String foodName;
public double qty;
public double rate;
public double price;
public int getSlNo() {
return slNo;
}
public void setSlNo(int slNo) {
this.slNo = slNo;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public double getQty() {
return qty;
}
public void setQty(double qty) {
this.qty = qty;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}

View File

@ -0,0 +1,230 @@
package com.pozo.printer.utils;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import com.dantsu.escposprinter.EscPosPrinter;
import com.pozo.printer.helper.ReceiptBuilder;
import com.pozo.printer.model.TokenItem;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class EscPosUtils {
public static Bitmap centerBitmap(Bitmap bitmap, int printerWidth) {
Bitmap output = Bitmap.createBitmap(
printerWidth,
bitmap.getHeight(),
Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(output);
// White background
canvas.drawColor(Color.WHITE);
int left = (printerWidth - bitmap.getWidth()) / 2;
canvas.drawBitmap(bitmap, left, 0, null);
return output;
}
public static byte[] generateFooter(String footerPrint) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
if ("Yes".equalsIgnoreCase(footerPrint)) {
buffer.write(new byte[] { 0x1B, 0x61, 0x01 });
buffer.write("\n".getBytes(StandardCharsets.UTF_8));
buffer.write("Thank You! Visit Again\n\n".getBytes(StandardCharsets.UTF_8));
buffer.write("\n\n".getBytes(StandardCharsets.UTF_8));
buffer.write(new byte[] { 0x1B, 0x61, 0x00 });
}
} catch (Exception e) {
Log.e("Footer", "Footer Error", e);
}
return buffer.toByteArray();
}
public static byte[] convertBitmapToEscPos(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int bytesPerLine = (width + 7) / 8;
byte[] imageData = new byte[bytesPerLine * height];
int index = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < bytesPerLine; x++) {
byte slice = 0;
for (int b = 0; b < 8; b++) {
int pixelX = x * 8 + b;
if (pixelX < width) {
int pixel = bitmap.getPixel(pixelX, y);
int r = Color.red(pixel);
int g = Color.green(pixel);
int bl = Color.blue(pixel);
int gray = (r + g + bl) / 3;
if (gray < 128) {
slice |= (byte) (1 << (7 - b));
}
}
}
imageData[index++] = slice;
}
}
// Center align
stream.write(new byte[] { 0x1B, 0x61, 0x01 });
// GS v 0 command
stream.write(new byte[] {
0x1D,
0x76,
0x30,
0x00,
(byte) (bytesPerLine % 256),
(byte) (bytesPerLine / 256),
(byte) (height % 256),
(byte) (height / 256)
});
Log.d("IMAGE_DEBUG", "ImageData Size : " + imageData.length);
stream.write(imageData);
stream.write(new byte[] { 0x0A, 0x0A });
// Left align
stream.write(new byte[] { 0x1B, 0x61, 0x00 });
} catch (Exception e) {
Log.e("BitmapError", "Error converting bitmap", e);
}
return stream.toByteArray();
}
public static byte[] generateQRCodeEscPos(String data) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
int dataLength = dataBytes.length;
// QR Code model
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x04, 0x00, 0x31, 0x41, 0x32, 0x00 }); // Model 2
// QR Code size (1-8, 5 is good for mobile)
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, 0x05 }); // Size 5
// QR Code error correction level (L=48, M=49, Q=50, H=51)
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, 0x31 }); // Level M
// Store QR code data
int pL = (dataLength + 3) % 256;
int pH = (dataLength + 3) / 256;
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, (byte) pL, (byte) pH, 0x31, 0x50, 0x30 });
buffer.write(dataBytes);
// Print QR code
buffer.write(new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 });
} catch (Exception e) {
Log.e("QRCodeError", "Error generating QR code", e);
}
return buffer.toByteArray();
}
public static void printUsbOrNetworkTokens(EscPosPrinter printer,
List<List<TokenItem>> salesTokens,
String orderId,
String salesId,
String salesOrEstimate) throws Exception {
Log.d("TokenPrint", "salesTokens" + salesTokens);
Log.d("TokenPrint", "orderId" + orderId);
if (salesTokens == null || salesTokens.isEmpty()) {
return;
}
for (List<TokenItem> token : salesTokens) {
String tokenText = EscPosUtils.buildTokenDesign(token, orderId, salesId, salesOrEstimate);
printer.printFormattedTextAndCut(tokenText);
}
}
public static String buildTokenDesign(
List<TokenItem> token,
String orderId,
String salesId,
String salesOrEstimate) {
String currentDate = new SimpleDateFormat(
"dd-MM-yyyy",
Locale.ENGLISH).format(new Date());
String currentTime = new SimpleDateFormat(
"hh:mm a",
Locale.ENGLISH).format(new Date());
StringBuilder bookingData = new StringBuilder();
for (TokenItem item : token) {
bookingData.append(String.format(Locale.ENGLISH,
"%2d %-20s %4.0f %6.0f %7.0f\n",
item.slNo,
ReceiptBuilder.trimText(item.foodName, 20),
item.qty,
item.rate,
item.price));
}
String tokenHeading = "";
if ("Estimate".equalsIgnoreCase(salesOrEstimate)) {
tokenHeading = " (" + salesOrEstimate + ")";
}
return "[C]<b><font size='big'><u>Token" + tokenHeading + "</u></font></b>\n" +
"[C]\n" +
"[L]<b>Bill No : " + orderId + "</b>" +
"[R]<b>" + currentDate + ", " + currentTime + "</b>\n" +
"[C]------------------------------------------------\n" +
"[L]<b>S.No Item Qty Rate Price</b>\n" +
"[C]------------------------------------------------\n" +
bookingData +
"[C]<b>------------------------------------------------</b>\n" +
"[C]\n" +
"[C]<font size='normal'>YOUR ORDER NO : " + salesId + "</font>\n";
}
}

154
package-lock.json generated
View File

@ -117,50 +117,6 @@
"@capacitor/core": "^8.2.0"
}
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@ -1326,9 +1282,9 @@
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1426,35 +1382,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
}
},
"node_modules/chevrotain-allstar": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz",
"integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lodash-es": "^4.17.21"
},
"peerDependencies": {
"chevrotain": "^11.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -2361,9 +2288,9 @@
}
},
"node_modules/flatted": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz",
"integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
@ -2578,9 +2505,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3285,18 +3212,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/java-parser": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/java-parser/-/java-parser-3.0.1.tgz",
"integrity": "sha512-sDIR7u9b7O2JViNUxiZRhnRz7URII/eE7g2B+BmGxDeS6Ex3OYAcCyz5oh0H4LQ+hL/BS8OJTz8apMy9xtGmrQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"chevrotain": "11.0.3",
"chevrotain-allstar": "0.3.1",
"lodash": "4.17.21"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3305,10 +3220,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@ -3418,20 +3343,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -3823,9 +3734,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@ -3873,13 +3784,13 @@
}
},
"node_modules/prettier-plugin-java": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.8.1.tgz",
"integrity": "sha512-tkteH5OSCEb0E7wKnhhUSitr1pGUCUt9M//CwerSNhoalL/qv0jXTeSVBPZ36KC+kZl3nbq4dxh144NuGchACg==",
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.10.0.tgz",
"integrity": "sha512-lDmVgJU/TCBri3kITMcNyr712Ac5Totb1fOp9GW1KCUdv4SsPfbFP7I+mUdRFEoPzE72ymf8MOgIsd/v0yA9CA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"java-parser": "3.0.1"
"web-tree-sitter": "0.26.9"
},
"peerDependencies": {
"prettier": "^3.0.0"
@ -4756,6 +4667,13 @@
"punycode": "^2.1.0"
}
},
"node_modules/web-tree-sitter": {
"version": "0.26.9",
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz",
"integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==",
"dev": true,
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -24,6 +24,50 @@ export interface PermissionResult {
network: 'granted' | 'denied' | 'prompt';
}
export interface NetworkPrinter {
name: string;
address: string;
port: number;
}
export interface PrintIntentOptions {
receiptText: string;
estimateText: string;
logoImage: string;
tokenDataSales: any[];
tokenDataEstimate: any[];
orderId: string;
estimateOrderId: string;
footerPrint: string;
upiId: string;
totalAmount: string;
estimateAmount: string;
}
export interface UsbConnectionResult {
name: string;
address: string;
connected: boolean;
}
export interface PrinterStatusOptions {
type: 'USB' | 'Bluetooth' | 'Wi-Fi';
address: string;
}
export interface PrinterStatusResult {
connected: boolean;
status: 'online' | 'offline';
type: string;
address: string;
name?: string;
message?: string;
}
/**
* @capacitor-pozo-printer
*/
@ -39,6 +83,16 @@ export interface PrinterPlugin {
*/
scanUsb(): Promise<{ devices: PrinterDevice[] }>;
/**
* Connect to USB printer
*/
connectUsb(): Promise<UsbConnectionResult>;
/**
* Scan Wi-Fi / LAN printers
*/
scanNetwork(): Promise<{ devices: NetworkPrinter[] }>;
/**
* Print via Bluetooth SPP
*/
@ -54,5 +108,13 @@ export interface PrinterPlugin {
*/
printUsb(options: UsbPrintOptions): Promise<{ success: boolean }>;
printIntent(options: PrintIntentOptions): Promise<void>;
/**
* Check printer online/offline status
*/
checkPrinterStatus(
options: PrinterStatusOptions
): Promise<PrinterStatusResult>;
}

View File

@ -2,10 +2,14 @@ import { WebPlugin } from '@capacitor/core';
import type {
PrinterPlugin,
PrinterDevice,
NetworkPrinter,
UsbConnectionResult,
BluetoothPrintOptions,
NetworkPrintOptions,
UsbPrintOptions,
PermissionResult
PermissionResult,
PrinterStatusOptions,
PrinterStatusResult
} from './definitions';
export class PrinterWeb extends WebPlugin implements PrinterPlugin {
@ -19,6 +23,21 @@ export class PrinterWeb extends WebPlugin implements PrinterPlugin {
return { devices: [] };
}
async connectUsb(): Promise<UsbConnectionResult> {
console.warn('USB connection not available on web');
return {
name: '',
address: '',
connected: false
};
}
async scanNetwork(): Promise<{ devices: NetworkPrinter[] }> {
console.warn('Network scan not available on web');
return { devices: [] };
}
async printBluetooth(_options: BluetoothPrintOptions): Promise<{ success: boolean }> {
console.warn('Bluetooth printing not available on web');
return { success: false };
@ -37,4 +56,24 @@ export class PrinterWeb extends WebPlugin implements PrinterPlugin {
async requestPermissions(): Promise<PermissionResult> {
return { bluetooth: 'granted', network: 'granted' };
}
async printIntent(_options: any): Promise<void> {
console.warn('printIntent not available on web');
}
async checkPrinterStatus(
_options: PrinterStatusOptions
): Promise<PrinterStatusResult> {
console.warn("Printer status not available on web");
return {
connected: false,
status: "offline",
type: _options.type,
address: _options.address,
message: "Not supported on web"
};
}
}