Writing to Printer with Core Bluetooth

I have a very cheap Bluetooth-connected printer. And I want to print out a word or two via Core Bluetooth. It's an iOS app with the SwiftUI framework. The following is what I have for an ObservableObject class.

import Foundation
import CoreBluetooth

class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    @Published var connectedDevices: [CBPeripheral] = []
    @Published var powerOn = false
    @Published var peripheralConnected = false
    private var centralManager: CBCentralManager!
    private var peripheralName = "LX-D02"
    private var connectedPeripheral: CBPeripheral?
    private var writeCharacteristic: CBCharacteristic?
    
    private let serviceUUID = CBUUID(string:"5833FF01-9B8B-5191-6142-22A4536EF123")
    private let characteristicUUID = CBUUID(string: "FFE1")
    
    override init() {
        super.init()
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    func startScanning() {
        if centralManager.state == .poweredOn {
            centralManager.scanForPeripherals(withServices: nil, options: nil)
        }
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            powerOn = true
            print("Bluetooth is powered on")
        } else {
            print("Bluetooth is not available")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !connectedDevices.contains(peripheral) {
            if let localName = advertisementData["kCBAdvDataLocalName"] as? String {
                if localName == peripheralName {
                    connectedDevices.append(peripheral)
                    centralManager.connect(peripheral, options: nil)
                    centralManager.stopScan()
                    peripheralConnected = true
                    print("Connected: \(peripheral.identifier.uuidString)")
                }
            }
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        peripheral.delegate = self
        let services = [serviceUUID]
        peripheral.discoverServices(services)
        //discoverServices(peripheral: peripheral)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: (any Error)?) {
        guard let error = error else {
            print("Failed connection unobserved")
            return
        }
        
        print("Error: \(error.localizedDescription)")
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let error = error {
            print("Failing to discover servies: \(error.localizedDescription)")
            return
        }
        
        discoverCharacteristics(peripheral: peripheral)
    }
    /* Return all available services */
    private func discoverServices(peripheral: CBPeripheral) {
        peripheral.discoverServices(nil)
    }
    
    private func discoverCharacteristics(peripheral: CBPeripheral) {
        guard let services = peripheral.services else {
            return
        }
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else {
            return
        }
        
        for characteristic in characteristics {
            let characteristicUUID = characteristic.uuid
            print("Discovered characteristic: \(characteristicUUID)")
            
            peripheral.setNotifyValue(true, for: characteristic)
            
            if characteristic.properties.contains(.writeWithoutResponse) {
                writeCharacteristic = characteristic
                print("You can write!!!") // Never read...
            }
            
            if characteristic.properties.contains(.write) {
                print("You can write?")
                writeCharacteristic = characteristic // Being read...
            }
    }
    
    func writeToPrinter() {
        guard let peripheral = connectedPeripheral else {
            print("Ughhh...")
            return
        }
        
        if let characteristic = writeCharacteristic {
            if let data = "Hello".data(using: .utf8, allowLossyConversion: true) {
                peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
                peripheral.writeValue(data, for: characteristic, type: .withResponse) // -> Message sent successfully
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Writing error: \(error.localizedDescription)")
            return
        }
        
        print("Message sent successfully")
    }
}

My app has no trouble connecting to the bluetooth-connected printer. Initially, I called

discoverServices(peripheral:)

to get all services And I get a service identifier (5833FF01-9B8B-5191-6142-22A4536EF123) for my printer. peripheral(_:didDiscoverCharacteristicsFor:error:) doesn't return a thing for .writeWithoutResponse but does return a characteristic for .write. Eventually, if I call writeToPrinter(),

peripheral.writeValue(data, for: characteristic, type: .withoutResponse)

returns

WARNING: Characteristic <CBCharacteristic: 0x3019040c0, UUID = 5833FF02-9B8B-5191-6142-22A4536EF123, properties = 0x8, value = (null), notifying = NO> does not specify the "Write Without Response" property - ignoring response-less write

If I call

peripheral.writeValue(data, for: characteristic, type: .withResponse)

, there is no error. But I get no output from the printer. What am I doing wrong? Thanks.

First of all, the .withoutResponse bit is obvious. The printer does seem to expect writes with responses. If .withoutResponse is required for your app for some reason, you may want to check with the support resources for the printer to see if there is a way to open up a characteristic like that.

As for the printer not printing anything, that is also a question for the printer support resources. If you are getting to the point of discovering the device and the services and characteristics of it without any errors, then your code is likely correct. Does the printer documentation claim that if you just send raw data to that characteristic, it is supposed to print what is sent?

There could be so many issues here, from incorrect encoding to some sort of a protocol that you need to implement to start printing. The answers will likely be on the printer side.


Argun Tekant /  DTS Engineer / Core Technologies

Writing to Printer with Core Bluetooth
 
 
Q