Unity and Bluetooth BLE Thermal Printer

Hello Devs and Devs alike is the no clean way to interact with Bluetooth devices from unity? I have a bluetooth thermal printer that I want to auto connect to and print when a new order is received, I managed to get code to receive orders and play sound, now the next steps it to send receipt to thermal printer,

The printer name is GoTabless -R- , thermal bluetooth printer it runs ESC/POS

I want to connect using the macAddress -i know it’s very possible because I downloaded this app by the name of Simple Bluetooth Thermal Print , it instantly can connect to the printer and print , even without using permissions because it’s directly connecting with macAddress,

thing is all refs, and example and coding I’m finding seems to work for those using java and kotlin or other languages . I’m in unity and using c#,

I also tried to use the InTheHand Library , no luck just yet…

what I want to do

// order received…//
now send that order data along with proper formatting to the printer to print

I have everything working except for connecting and sending jobs to the printer…

Anyone out-there willing or knowledgeable to this , I have immediate compensation for anyone who help me achieve this goal.

I also have the script that’s currently working up to the point of the bluetooth auto detect connect print

What library are you using now ? and what target platform for the application ?

I’m currently using InTheHand.Bluetooth.Permissions.dll , InTheHand.BluetoothLE.dll , InTheHand.Net.Bluetooth.dll , Xamarin.Essentials.dll and .net 2.1 , building on Android

everything works fine except for the bluetooth connection and printing

these are the scripts -

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

public class SoundNotification : MonoBehaviour
{
    public string WooCommerceBaseUrl = "YOUR_WOOCOMMERCE_API_URL";
    public string ConsumerKey = "YOUR_CONSUMER_KEY";
    public string ConsumerSecret = "YOUR_CONSUMER_SECRET";



    public AudioSource alertSoundSource;
    public TMP_Text orderIdText;
    public TMP_Text customerNameText;
    public TMP_Text dateCreatedText;
    public TMP_Text statusText;
    public TMP_Text totalAmountText;
    public TMP_Text shippingAddressText;
    public TMP_Text shippingOptionsText;
    public TMP_Text lineItemsText;
    public TMP_Text phoneText;
    public TMP_Text emailText;
    public TMP_Text paymentMethodText;
    public TMP_Text subtotalText;
    public TMP_Text discountsText;
    public TMP_Text tipsText;
    public TMP_Text taxText;
    public GameObject buttonIconTransform;
    public DateTime orderTime;
    private DisplayAndPrintReceipt displayAndPrintReceipt;

    private const float targetRotation = 0f;
    private const float targetRotationRange = 30f;
    private const float targetRotationRangeDelay = 0.1f;
    private const float targetRotationRangeDelayEase = 0.5f;
    private const float orderCheckDelay = 13.0f;

    private bool shouldPlayAlertSound = true;
    private int latestOrderId = 0;

    private void Start()
    {
        displayAndPrintReceipt = FindObjectOfType<DisplayAndPrintReceipt>();
        StartCoroutine(OrderCheckingCoroutine());
    }

    private IEnumerator OrderCheckingCoroutine()
    {
        while (true)
        {
            yield return StartCoroutine(GetLastOrder());
            yield return new WaitForSeconds(orderCheckDelay);
        }
    }
    private bool PayPalVal(JObject order, decimal totalAmount, string paymentMethod) {

        
        
        if (totalAmount == 0 && paymentMethod.ToLower() == "paypal")
        {
            Debug.Log("Didn't Pay.");
            return false;
        }
        else
        {
            Debug.Log("Paid.");
            return true;    
        }


        
    }
    private IEnumerator GetLastOrder()
    {
        string requestUrl = $"{WooCommerceBaseUrl}/orders?consumer_key={ConsumerKey}&consumer_secret={ConsumerSecret}";
        UnityWebRequest request = UnityWebRequest.Get(requestUrl);
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError("Error retrieving order data: " + request.error);
            yield break;
        }

        string jsonResponse = request.downloadHandler.text;
        Debug.Log(jsonResponse);
        JArray orderDataArray = JArray.Parse(jsonResponse);

        if (orderDataArray.Count > 0)
        {
            JObject lastOrder = (JObject)orderDataArray[0];
            int orderId = lastOrder.Value<int>("id");

            // Check if the orderId is different from the latestOrderId
            if (orderId != latestOrderId  )
            {
                latestOrderId = orderId; // Update the latestOrderId
                shouldPlayAlertSound = true;
                DisplayOrderDetails(lastOrder);

            } 
            else
            {
                Debug.Log("Same order as the last displayed order. Skipping display.");
            }
        }

    }

    private void DisplayOrderDetails(JObject order)
    {
        ClearOrderDetails();
        int orderId = order.Value<int>("id");
        string customerName = $"{order["billing"]["first_name"]} {order["billing"]["last_name"]}";
        string phoneNumber = FormatPhoneNumber(order["billing"]["phone"].Value<string>());
        string customerEmail = order["billing"]["email"].Value<string>();
        string paymentMethod = order["payment_method"].Value<string>();
        DateTime dateCreated = DateTime.Parse(order["date_created"].Value<string>());
        decimal totalAmount = order["total"].Value<decimal>();
        string lineItemsInfo = "Items:\n";
        decimal tax = order["total_tax"].Value<decimal>();
        string shippingOptionsTextgo = "";

        foreach (var shippingData in order["meta_data"])
        {
            if (shippingData["key"].Value<string>() == "options_")
            {
                shippingOptionsTextgo = shippingData["value"].Value<string>();
                break;
            }
        }

        foreach (var item in order["line_items"])
        {
            string formattedPrice = item["price"].Value<decimal>().ToString("F2");
            string formattedTax = item["total_tax"].Value<decimal>().ToString("F2");
            string orderNoteText = ""; // Initialize order note text

            // Handle order notes for the current line item
            JArray orderNotesArray = item["meta_data"] as JArray;
            if (orderNotesArray != null)
            {
                foreach (JObject noteObject in orderNotesArray)
                {
                    if (noteObject["key"].Value<string>() == "Order Notes")
                    {
                        orderNoteText = noteObject["value"].Value<string>();
                        break;
                    }
                }
            }

            // Continue building line items info string
            lineItemsInfo += $"{item["name"]} - Qty: {item["quantity"]}, Price: ${formattedPrice}, Tax: ${formattedTax} \n";
            if (!string.IsNullOrEmpty(orderNoteText))
            {
                lineItemsInfo += $"Order Note: {orderNoteText} \n";
            }
            lineItemsInfo += "\n";
        }

        // Set UI elements with retrieved order details
        orderIdText.text = $"Order Number: {orderId}";
        phoneText.text = $"{phoneNumber}";
        customerNameText.text = $"{customerName}";
        emailText.text = $"{customerEmail}";
        paymentMethodText.text = $"Payment: {paymentMethod}";
        dateCreatedText.text = $"{dateCreated:MM dd yyyy hh:mm tt}";
        statusText.text = $"Status: {order["status"]}";
        shippingOptionsText.text = shippingOptionsTextgo;
        taxText.text = $"Tax: ${tax:F2}";
        totalAmountText.text = $"Total: ${totalAmount:F2}";
        shippingAddressText.text = $"{order["billing"]["address_1"]}, {order["billing"]["city"]}, {order["billing"]["state"]}, {order["billing"]["country"]}";
        lineItemsText.text = $"{lineItemsInfo}";
        // Call the method to get discount details
        string discountDetails = GetDiscountDetails(order);
        discountsText.text = discountDetails;
        UpdateSubtotal(order);
        UpdateTips(order);


        if (shouldPlayAlertSound)
        {
            PlayAlertSound();
        }



        string CompanyFirstname = "";
        string CompanySecondname = "";
        string Companythirdname = "";
        string CompanyAddress = "";
        string CompanyPhone = "";
        string CompanyEmail = "";
        string FTTxT = "Thank you for dining with us!";
        string CompanyUL = "";



        // Prepare receipt data
        ReceiptData newReceiptData = new ReceiptData
        {
            // Fill in with the order details as required
            companyFristName = CompanyFirstname,
            companySecondName = CompanySecondname,
            companyThirdName = Companythirdname,
            companyAddress = CompanyAddress,
            companyPhone = CompanyPhone,
            companyEmail = CompanyEmail,
            footerMessage = FTTxT,
            companyURL = CompanyUL,
            customerName = customerNameText.text,
            customerAddress = shippingAddressText.text,
            customerPhone = phoneText.text,
            receiptOrderNumber = orderIdText.text,
            date = DateTime.Parse(order["date_created"].ToString()),
            orderNotes = string.Join(", ", order["line_items"].Select(i => i["name"].ToString() + " x" + i["quantity"].ToString())),
            items = order["line_items"].Select(i => new ReceiptItem
            {
                itemName = i["name"].ToString(),
                quantity = int.Parse(i["quantity"].ToString()),
                price = float.Parse(i["total"].ToString()),
                itemOrderNotes = i["meta_data"].FirstOrDefault(m => m["key"].ToString() == "notes")?["value"].ToString()
            }).ToArray(),
            subtotal = float.Parse(subtotalText.text.Trim('$')),
            tax = float.Parse(taxText.text.Trim('$')),
            discount = float.Parse(discountsText.text.Trim('$')),
            tips = float.Parse(tipsText.text.Trim('$')),
            totalAmount = float.Parse(totalAmountText.text.Trim('$'))
        };

        // Update receipt data in the display and print script
        displayAndPrintReceipt.UpdateReceiptData(newReceiptData);



    }



    private void UpdateSubtotal(JObject order)
    {
        decimal subtotal = CalculateOverallSubtotal(order);
        subtotalText.text = $"Subtotal: ${subtotal:F2}";
    }

    private string GetDiscountDetails(JObject order)
    {
        JArray couponLines = order["coupon_lines"] as JArray;
        StringBuilder discountsBuilder = new StringBuilder();

        if (couponLines != null && couponLines.Count > 0)
        {
            foreach (JObject coupon in couponLines)
            {
                string couponCode = coupon["code"].Value<string>();
                decimal discountAmount = coupon["discount"].Value<decimal>();
                discountsBuilder.AppendLine($"{couponCode} ${discountAmount:F2}");
            }
        }
        else
        {
            discountsBuilder.AppendLine("None Applied");
        }

        return discountsBuilder.ToString();
    }

    private decimal CalculateOverallSubtotal(JObject order)
    {
        decimal overallSubtotal = 0;
        JArray lineItems = (JArray)order["line_items"];

        foreach (var item in lineItems)
        {
            overallSubtotal += (decimal)item["subtotal"];
        }

        return overallSubtotal;
    }

    private void UpdateTips(JObject order)
    {
        decimal tips = 0m;

        if (order["fee_lines"] is JArray feeLinesArray)
        {
            foreach (var item in feeLinesArray)
            {
                if (item["name"]?.Value<string>() == "Tip")
                {
                    if (decimal.TryParse(item["total"]?.ToString(), out decimal parsedTips))
                    {
                        tips = parsedTips;
                    }
                    break;
                }
            }
        }

        tipsText.text = $"Tips: ${tips:F2}";
    }

    public void CheckOrderById(int orderId)
    {
        StartCoroutine(GetOrderById(orderId));
    }

    private IEnumerator GetOrderById(int orderId)
    {
        string requestUrl = $"{WooCommerceBaseUrl}/orders/{orderId}?consumer_key={ConsumerKey}&consumer_secret={ConsumerSecret}";
        UnityWebRequest request = UnityWebRequest.Get(requestUrl);
        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError($"Error retrieving order details for ID {orderId}: " + request.error);
            yield break;
        }

        string jsonResponse = request.downloadHandler.text;
        Debug.Log(jsonResponse);

        JObject orderData = JObject.Parse(jsonResponse);

        if (orderData != null)
        {
            DisplayOrderDetails(orderData);
        }
        else
        {
            Debug.LogError($"Order details not found for ID {orderId}.");
        }
    }

    private void ClearOrderDetails()
    {
        orderIdText.text = "";
        customerNameText.text = "";
        phoneText.text = "";
        emailText.text = "";
        dateCreatedText.text = "";
        statusText.text = "";
        totalAmountText.text = "";
        shippingAddressText.text = "";
        shippingOptionsText.text = "";
        lineItemsText.text = "";
        paymentMethodText.text = "";
        subtotalText.text = "";
        discountsText.text = "";
        taxText.text = "";
        tipsText.text = "";
    }
   
    private void PlayAlertSound()
    {
        StartCoroutine(RotateButtonIcon());
        PlaySound();
    }

    private IEnumerator RotateButtonIcon()
    {
        while (shouldPlayAlertSound)
        {
            float targetRotationSide1 = targetRotation + targetRotationRange;
            iTween.RotateTo(buttonIconTransform, iTween.Hash("z", targetRotationSide1, "time", targetRotationRangeDelayEase, "easetype", "linear"));
            yield return new WaitForSeconds(targetRotationRangeDelay);

            iTween.RotateTo(buttonIconTransform, iTween.Hash("z", targetRotation, "time", targetRotationRangeDelayEase, "easetype", "linear"));
            yield return new WaitForSeconds(targetRotationRangeDelay);

            float targetRotationSide2 = targetRotation - targetRotationRange;
            iTween.RotateTo(buttonIconTransform, iTween.Hash("z", targetRotationSide2, "time", targetRotationRangeDelayEase, "easetype", "linear"));
            yield return new WaitForSeconds(targetRotationRangeDelay);

            iTween.RotateTo(buttonIconTransform, iTween.Hash("z", targetRotation, "time", targetRotationRangeDelayEase, "easetype", "linear"));
            yield return new WaitForSeconds(targetRotationRangeDelay);
        }

        iTween.RotateTo(buttonIconTransform, iTween.Hash("z", targetRotation, "time", targetRotationRangeDelayEase, "easetype", "linear"));
    }

    private void PlaySound()
    {
        AudioClip soundClip = Resources.Load<AudioClip>("order-music");

        if (soundClip != null)
        {
            alertSoundSource.clip = soundClip;
            alertSoundSource.Play();
        }
        else
        {
            Debug.LogError("Alert sound not found in Resources folder.");
        }
    }

   



    public void StopAlertSound()
    {
        shouldPlayAlertSound = false;
        if (alertSoundSource.isPlaying)
        {
            alertSoundSource.Stop();
        }
        StopRotationAnimation();
        ClearOrderDetails();
    }

    private void StopRotationAnimation()
    {
        iTween.Stop(buttonIconTransform);
    }

    private string FormatPhoneNumber(string phoneNumber)
    {
        string digitsOnly = new string(phoneNumber.Where(char.IsDigit).ToArray());
        if (digitsOnly.Length >= 10)
        {
            string areaCode = digitsOnly.Substring(digitsOnly.Length - 10, 3);
            string firstThreeDigits = digitsOnly.Substring(digitsOnly.Length - 7, 3);
            string lastFourDigits = digitsOnly.Substring(digitsOnly.Length - 4, 4);
            return $"({areaCode}) {firstThreeDigits}-{lastFourDigits}";
        }
        else
        {
            return phoneNumber;
        }
    }
}
using System;
using System.Text;
using UnityEngine;
using TMPro;
using System.IO;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using System.Linq;
using System.Net.Sockets;
using System.Collections;
using System.Collections.Generic;

public class DisplayAndPrintReceipt : MonoBehaviour
{
    [SerializeField] private ReceiptData receiptData;
    [SerializeField] private TMP_Text receiptText;
    private BluetoothClient bluetoothClient;
    private BluetoothDeviceInfo printerDevice;
    [SerializeField] private string printerMacAddress = ""; // printer's MAC address

    void Start()
    {
        Display();
        Print();
    }
    public void UpdateReceiptData(ReceiptData newReceiptData)
    {
        receiptData = newReceiptData;
        Display();
        Print();
    }

    private void Display()
    {
        string receiptContent = $"{receiptData.companyFristName}\n" +
                                $"{receiptData.companySecondName}\n" +
                                $"{receiptData.companyThirdName}\n" +
                                $"{receiptData.companyAddress}\n" +
                                $"{receiptData.companyPhone}\n"+
                                $"{receiptData.companyEmail}\n" ;

        if (receiptData.companyLogo != null)
        {
            // Display logo if available
            // Handle this as per your UI framework
        }

        receiptContent += $"\nCustomer: {receiptData.customerName}\n" +
                          $"Address: {receiptData.customerAddress}\n" +
                          $"Phone: {receiptData.customerPhone}\n\n" +
                          $"Order Number: {receiptData.receiptOrderNumber}\n" +
                          $"Time: {receiptData.date.ToShortTimeString()}\n" +
                          $"Date: {receiptData.date.ToShortDateString()}\n\n" +
                          $"{receiptData.separator}\n" +
                          $"Order Notes: {receiptData.orderNotes}\n" +
                          $"Items:\n";

        foreach (var item in receiptData.items)
        {
            receiptContent += $"{item.itemName} x{item.quantity} -{item.itemOrderNotes} - ${item.price}\n";
        }

        receiptContent += $"\n{receiptData.separator}\n" +
                          $"Subtotal: ${receiptData.subtotal}\n" +
                          $"Tax: ${receiptData.tax}\n" +
                          $"Discount: ${receiptData.discount}\n" +
                          $"Tips: ${receiptData.tips}\n" +  // Display tips
                          $"Total: ${receiptData.totalAmount}\n" +
                          $"{receiptData.separator}\n\n" +
                          $"{receiptData.footerMessage}"+
                          $"{receiptData.companyURL}";

        receiptText.text = receiptContent;
    }

    private void Print()
    {
        List<byte> command = new List<byte>();

        // Add logo if available
        if (receiptData.companyLogo != null)
        {
            command.AddRange(PrintImage(receiptData.companyLogo));
        }

        // Add barcode
        if (!string.IsNullOrEmpty(receiptData.BarcodeData))
        {
            command.AddRange(Encoding.ASCII.GetBytes("\x1D\x68\x32")); // Barcode height
            command.AddRange(Encoding.ASCII.GetBytes("\x1D\x77\x02")); // Barcode width
            command.AddRange(Encoding.ASCII.GetBytes("\x1D\x6B\x04")); // CODE39
            command.AddRange(Encoding.ASCII.GetBytes(receiptData.BarcodeData + "\x00")); // Data + null terminator
        }

        // Set alignment
        command.AddRange(SetAlignment(receiptData.alignment));

        // Add receipt content
        command.AddRange(Encoding.ASCII.GetBytes(receiptData.companyFristName + "\n" +
                                                 receiptData.companySecondName + "\n" +
                                                 receiptData.companyThirdName + "\n" +
                                                 receiptData.companyAddress + "\n" +
                                                 receiptData.companyPhone + "\n" +
                                                 receiptData.companyEmail + "\n" +
                                                 receiptData.separator + "\n"));

        command.AddRange(Encoding.ASCII.GetBytes("Customer: " + receiptData.customerName + "\n" +
                                                 "Address: " + receiptData.customerAddress + "\n" +
                                                 "Phone: " + receiptData.customerPhone + "\n\n" +
                                                 "Order Number: " + receiptData.receiptOrderNumber + "\n" +
                                                 "Time: " + receiptData.orderTime.ToShortTimeString() + "\n" +
                                                 "Date: " + receiptData.date.ToShortDateString() + "\n" +
                                                 "Order Notes: " + receiptData.orderNotes + "\n\n" +
                                                 "Items:\n"));

        foreach (var item in receiptData.items)
        {
            command.AddRange(Encoding.ASCII.GetBytes(item.itemName + " x" + item.quantity + " - " + item.itemOrderNotes +" - $" + item.price + "\n"));
        }

        command.AddRange(Encoding.ASCII.GetBytes("\n" + receiptData.separator + "\n" +
                                                 "Subtotal: $" + receiptData.subtotal + "\n" +
                                                 "Tax: $" + receiptData.tax + "\n" +
                                                 "Discount: $" + receiptData.discount + "\n" +
                                                 "Tips: $" + receiptData.tips + "\n" +
                                                 "Total: $" + receiptData.totalAmount + "\n" +
                                                 receiptData.separator + "\n\n" +
                                                 receiptData.footerMessage + "\n" +receiptData.companyURL));

        // Line feed
        command.AddRange(new byte[] { 0x1B, 0x64, (byte)receiptData.lineFeedAfterItems });

        // Cut paper
        if (receiptData.enableCut)
        {
            command.AddRange(new byte[] { 0x1D, 0x56, 0x42, 0x00 }); // ESC/POS cut command
        }

        // Send the command to the printer
        SendToPrinter(command.ToArray());

    }

    private byte[] SetAlignment(string alignment)
    {
        switch (alignment.ToUpper())
        {
            case "CENTER":
                return new byte[] { 0x1B, 0x61, 0x01 }; // ESC a 1 (center alignment)
            case "RIGHT":
                return new byte[] { 0x1B, 0x61, 0x02 }; // ESC a 2 (right alignment)
            default:
                return new byte[] { 0x1B, 0x61, 0x00 }; // ESC a 0 (left alignment)
        }
    }

    private byte[] PrintImage(Texture2D image)
    {
        // Example of converting a Texture2D to ESC/POS bitmap format (monochrome)
        // This will depend on your specific printer's capabilities and requirements
        List<byte> command = new List<byte>();

        // Convert image to monochrome bitmap
        int width = Mathf.Min(image.width, 384); // Printers usually have a max width (e.g., 384 pixels)
        int height = Mathf.Min(image.height, 256); // Printers usually have a max height (e.g., 256 pixels)

        byte[] pixels = new byte[width * height / 8];

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                Color color = image.GetPixel(x, y);
                int pixelIndex = (y * width + x) / 8;
                int bitIndex = x % 8;

                if (color.grayscale < 0.5f) // Assuming black color is represented by grayscale value < 0.5
                {
                    pixels[pixelIndex] |= (byte)(0x80 >> bitIndex);
                }
            }
        }

        // Start of the bitmap command
        command.Add(0x1D); // ESC/POS command
        command.Add(0x76); // Bitmap command
        command.Add(0x00); // Mode (0 = normal, 1 = reverse)
        command.Add((byte)(width % 256)); // Bitmap width (LSB)
        command.Add((byte)(width / 256)); // Bitmap width (MSB)
        command.Add((byte)(height % 256)); // Bitmap height (LSB)
        command.Add((byte)(height / 256)); // Bitmap height (MSB)
        command.AddRange(pixels); // Bitmap data

        return command.ToArray();
    }


    private void SendToPrinter(byte[] command)
    {
        try
        {
            // Initialize Bluetooth client and discover devices
            bluetoothClient = new BluetoothClient();
            var devices = bluetoothClient.DiscoverDevices();

            // Find the printer device by MAC address
            printerDevice = devices.FirstOrDefault(d => d.DeviceAddress.ToString() == printerMacAddress);

            if (printerDevice == null)
            {
                Debug.LogError("Printer not found.");
                return;
            }

            // Attempt to connect to the printer
            bluetoothClient.Connect(printerDevice.DeviceAddress, BluetoothService.SerialPort);

            using (NetworkStream stream = bluetoothClient.GetStream())
            {
                // Write the command to the printer
                stream.Write(command, 0, command.Length);
                stream.Flush();
                Debug.Log("Data sent to printer.");
            }
        }
        catch (SocketException ex)
        {
            Debug.LogError("Bluetooth socket exception: " + ex.Message);
        }
        catch (IOException ex)
        {
            Debug.LogError("IO exception: " + ex.Message);
        }
        catch (Exception ex)
        {
            Debug.LogError("Failed to send data to printer: " + ex.Message);
        }
        finally
        {
            // Ensure the Bluetooth client is closed
            bluetoothClient?.Close();
        }
    }



}
using System;
using UnityEngine;

[CreateAssetMenu(fileName = "NewReceiptData", menuName = "Receipts/ReceiptData")]
public class ReceiptData : ScriptableObject
{
    [Header("Company Info")]
    public string companyFristName;
    public string companySecondName;
    public string companyThirdName;
    public string companyAddress;
    public string companyPhone;
    public string companyEmail;
    public Texture2D companyLogo;

    [Header("Customer Info")]
    public string customerName;
    public string customerAddress;
    public string customerPhone;
 
    [Header("Receipt Info")]
    public DateTime date;
    public DateTime orderTime;
    public string receiptOrderNumber;
    public string orderNotes;
    public string BarcodeData;

    [Header("Items")]
    public ReceiptItem[] items;
  

    [Header("Totals")]
    public float subtotal;
    public float tax;
    public float discount;
    public float tips;  // New field for tips
    public float totalAmount;
   
    [Header("Printer Settings")]
    public  string alignment = "LEFT"; // LEFT, CENTER, RIGHT
    public int lineFeedAfterItems = 1;
    public  int lineFeedBeforeCut = 3;
    public string separator = "------------------------------";
    public bool enableCut = true; // Automatic paper cut after printing

    [Header("Additional Info")]
    public string footerMessage;
    public string companyURL;
    public void CalculateTotalAmount()
    {
        totalAmount = subtotal + tax - discount + tips;
    }
}

[Serializable]
public class ReceiptItem
{
   public string itemName;
   public int quantity;
   public float price;
   public string itemOrderNotes;
}

and it’s not connecting and printing,

I opened a empty new scene and tried this script -

using System;
using System.Threading.Tasks;
using UnityEngine;
using InTheHand.Bluetooth; // For Bluetooth operations
using InTheHand.Net.Bluetooth; // For additional Bluetooth functions
using InTheHand.Net.Sockets; // For Bluetooth sockets, if needed
using Xamarin.Essentials; // For platform-specific permissions
using TMPro;

public class BluetoothPrinterConnector : MonoBehaviour
{
    private const string PrinterName = "GoTabless -R-";
    private const string PrinterMacAddress = "";
    [SerializeField] private TMP_Text Scan_Text;
    [SerializeField] private TMP_Text Device_Text;
    [SerializeField] private TMP_Text Print_Text;

    void Start()
    {
        RequestBluetoothPermissions();
    }

    private async void RequestBluetoothPermissions()
    {
        try
        {
           

            await ConnectToPrinter(); // Ensure the ConnectToPrinter method is awaited
        }
        catch (Exception ex)
        {
            Scan_Text.text = $"Error requesting permissions: {ex.Message}";
        }
    }

    private async Task ConnectToPrinter() // Use Task for async methods that are awaited
    {
        try
        {
            var bluetoothClient = new BluetoothClient();

            // Start discovering devices
            var devices = await Task.Run(() => bluetoothClient.DiscoverDevices()); // Run on a separate thread
            BluetoothDeviceInfo targetDevice = null;

            foreach (var device in devices)
            {
                Scan_Text.text = $"Found device: {device.DeviceName} ({device.DeviceAddress})";
                if (device.DeviceName == PrinterName || device.DeviceAddress.ToString() == PrinterMacAddress)
                {
                    targetDevice = device;
                    break;
                }
            }

            if (targetDevice == null)
            {
                Print_Text.text = "Printer not found.";
                return;
            }

            // Connect to the printer
            await Task.Run(() => bluetoothClient.Connect(targetDevice.DeviceAddress, BluetoothService.SerialPort));
            Print_Text.text = "Connected to printer.";

            // Here you would typically start sending print jobs
        }
        catch (Exception ex)
        {
            Device_Text.text = $"Error connecting to printer: {ex.Message}";
        }
    }
}

and received an error stating - this operation is not supported on this platform…

Currently I have the same feature as you to handle ESC/POS printers, maybe you can check this github

But i still cant connect that module into unity

so you tried this - GitHub - DantSu/ESCPOS-ThermalPrinter-Android: Useful library to help Android developpers to print with (Bluetooth, TCP, USB) ESC/POS thermal printer. and couldn’t get it to work? … I’m researching now seems as thou we will have to create a java script bridge then compile it to a jar file and place it in Assets/Plugins/Android directory , something along the lines of

package com.riograndindustries.bluetooth;

import android.app.Activity;
import com.dantsu.escposprinter.connection.bluetooth.BluetoothPrintersConnections;
import com.dantsu.escposprinter.connection.bluetooth.BluetoothConnection;

public class ThermalPrinterService {
    private BluetoothConnection printerConnection;

    public ThermalPrinterService(Activity activity) {
        this.printerConnection = new BluetoothPrintersConnections().selectFirstPaired();
    }

    public boolean connectToPrinter(String printerName, String macAddress) {
        if (this.printerConnection == null) {
            return false;
        }
        this.printerConnection.connect();
        return this.printerConnection.isConnected();
    }

    public void printText(String text) {
        if (this.printerConnection != null && this.printerConnection.isConnected()) {
            this.printerConnection.sendEscPosCommand(text);
        }
    }

    public void disconnectPrinter() {
        if (this.printerConnection != null) {
            this.printerConnection.disconnect();
        }
    }
}
 

then have you c# interact with this bridge script something like

using UnityEngine;

public class ThermalPrinter : MonoBehaviour
{
    private AndroidJavaObject printerService;

    void Start()
    {
        InitializePrinter();
    }

    private void InitializePrinter()
    {
        try
        {
            using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            {
                AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");

                // Initialize the printer service
                printerService = new AndroidJavaObject("com.riograndindustries.bluetooth.ThermalPrinterService", currentActivity);
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError("Error initializing printer: " + ex.Message);
        }
    }

    public void ConnectToPrinter()
    {
        try
        {
            // Connect to the Bluetooth printer by its name or address
            bool isConnected = printerService.Call<bool>("connectToPrinter", "PrinterName", "PrinterMacAddress");
            if (isConnected)
            {
                Debug.Log("Connected to printer.");
                // Example of printing text
                PrintText("Hello from Unity!");
            }
            else
            {
                Debug.LogError("Failed to connect to printer.");
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError("Error connecting to printer: " + ex.Message);
        }
    }

    public void PrintText(string text)
    {
        try
        {
            // Call the print text method
            printerService.Call("printText", text);
        }
        catch (System.Exception ex)
        {
            Debug.LogError("Error printing text: " + ex.Message);
        }
    }
}

using AndroidJavaObject, so I’m working with visual studios in unity and I also opened visual code for the java script… trying to setup and test now… on google play just to test I downloaded this app called simple thermal bluetooth printer and it connects instantly and will print with in seconds … so I’m trying to get that same functionality and combine with current system…

what all have you done and what printer ? Also be sure the package name is yours " “com.riograndindustries.bluetooth.ThermalPrinterService” "

Yeah so it’s more, I’m working within 3 to 5 IDE’s to get this all setup, these are just test runs,… so it’s basically like we have to get get a java wrapper to communicate with our C# scripts , getting things configured is the issue now

if you target the application for Android device, maybe you can just use this aar library from that github JitPack | Publish JVM and Android libraries

I’m already connect to thermal printer using USB connection and already success print. You just need to create .java script to connect that aar with c#

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
    private String dataText;
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            synchronized (this) {
                UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
                UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    if (usbManager != null && usbDevice != null) {
                        System.out.println("USB Printer Permission Granted");
                        try {
                            EscPosPrinter printer = new EscPosPrinter(new UsbConnection(usbManager, usbDevice), 203, 80, 64);
                            System.out.println(dataText);
                            printer.printFormattedText(dataText);
                            printer.printFormattedText(dataText);
                        } catch (Exception e) {
                            System.out.println("ERROR");
                        }
                    }
                }
            }
        }
    };

    public void printData(String text){
        dataText = text;
        UsbConnection usbConnection = UsbPrintersConnections.selectFirstConnected(this);
        UsbManager usbManager = (UsbManager) getSystemService(this.USB_SERVICE);

        if (usbConnection == null || usbManager == null) {
		    System.out.println("NO USB Connection");
            return;
        }

        PendingIntent permissionIntent = PendingIntent.getBroadcast(
            this,
            0,
            new Intent(INTENT_ACTION_GRANT_USB),
            android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0
        );

        System.out.println("Connect USB");
        IntentFilter filter = new IntentFilter(INTENT_ACTION_GRANT_USB);
        registerReceiver(this.usbReceiver, filter);
        usbManager.requestPermission(usbConnection.getDevice(), permissionIntent);
    }

I’m trying to create the aar now and I only have the option to use bluetooth and I also have an error with the send to printer method, I’m trying to create the file in Android studios not sure if I have everything correct, do you mine sharing your process and what you have done,? I believe I feel I’m close , just a few things I’m missing,…

plugins {
    id 'com.android.application'
}

android {
    compileSdk 33

    defaultConfig {
        applicationId "com.rigrandindustries.Bluetooth"
        minSdk 21
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}




dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'com.github.DantSu:ESCPOS-ThermalPrinter-Android:3.3.0'
}
task createJar(type: Copy) {
    from('build/intermediates/javac/release/classes') {
        include '**/*.class'
    }
    into('build/libs')
    includeEmptyDirs = false
    doLast {
        ant.jar(destfile: 'build/libs/thermalPrinterService.jar',
                basedir: 'build/intermediates/javac/release/classes')
    }
}

java

package com.riograndindustries.Bluetooth;

import android.app.Activity;
import com.dantsu.escposprinter.connection.bluetooth.BluetoothPrintersConnections;
import com.dantsu.escposprinter.connection.bluetooth.BluetoothConnection;
import com.dantsu.escposprinter.exceptions.EscPosConnectionException;


public class ThermalPrinterService {
    private BluetoothConnection printerConnection;

    public ThermalPrinterService(Activity activity) {
        this.printerConnection = new BluetoothPrintersConnections().selectFirstPaired();
    }

    public boolean connectToPrinter(String printerName, String macAddress) throws EscPosConnectionException {
        if (this.printerConnection == null) {
            return false;
        }
        this.printerConnection.connect();
        return this.printerConnection.isConnected();
    }

    public void printText(String text) {
        if (this.printerConnection != null && this.printerConnection.isConnected()) {
            //this
                this.printerConnection.printText(text);
            //or this
                this.printText(text);
       
        
        }
    }

    public void disconnectPrinter() {
        if (this.printerConnection != null) {
            this.printerConnection.disconnect();
        }
    }
}

I used Unity’s AndroidJavaObject to call native Bluetooth functions from a Java plugin. It let me send raw print commands directly to the printer without needing extra libraries.