package xyz.mtfos.btdemo; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.ParcelUuid; import android.util.Log; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * Created by jay on 2017/8/22. */ public class PrinterBle { final private String TAG = "Bluetooth LE Object"; private Context ctx; private BluetoothAdapter mAdapter; private BluetoothGatt mBtGatt; private BluetoothGattCharacteristic printerCharacteristic; // for android api >= 21 use private BluetoothLeScanner mLeScanner; private ScanSettings mLeSettings; private List mLeFilters; // BLE相關參數 private String macAddr, serviceUUID, characteristicUUID; // 藍芽狀態列表 final public static int CONNECTING = 0x01; final public static int CONNECTED = 0x02; final public static int DISCONNECTED = 0x00; final public static int SCANNING = 0x03; // printer 相關參數 final public static int ALIGN_LEFT = 0x00; final public static int ALIGN_CENTER = 0x01; final public static int ALIGN_RIGHT = 0x02; private int lastAlign = 0x00; // 藍芽狀態 public int mState = 0x00; private boolean isConnected = false; // 藍芽通訊flag private boolean sending = false; // 發送內容陣列 private ArrayList sendingQueue = new ArrayList<>(); // 待處理發送陣列 private ArrayList dataQueue = new ArrayList<>(); // Listener Object private stateChangeListener mStateListener; private notifyListener mNotifyListener; private Handler mHandler = new Handler(); public interface stateChangeListener { void onChange(int state); } public interface notifyListener { void onNotify(String msg); } public PrinterBle(Context ctx, String macAddr, String serviceUUID, String characteristicUUID) { this.ctx = ctx; this.macAddr = macAddr; this.serviceUUID = serviceUUID; this.characteristicUUID = characteristicUUID; } private BluetoothGattCallback mBtGattCB = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Log.d(TAG, "Connect State Change to " + status + " new State : " + newState); if (status == BluetoothGatt.GATT_SUCCESS) { if(mState != DISCONNECTED) { mState = CONNECTED; Log.d(TAG, "Connected"); if(!isConnected) { isConnected = true; mBtGatt.discoverServices(); } } mHandler.postDelayed(new Runnable() { @Override public void run() { mStateListener.onChange(mState); } }, 100); } else if (newState == BluetoothGatt.GATT_FAILURE) { mState = DISCONNECTED; Log.d(TAG, "Connect Failed"); mStateListener.onChange(mState); } } // 遠端設備中的服務可用時的回調 @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (status == BluetoothGatt.GATT_SUCCESS) { BluetoothGattService btService = mBtGatt.getService(UUID.fromString(serviceUUID)); if (btService != null) { printerCharacteristic = btService.getCharacteristic(UUID.fromString(characteristicUUID)); // set notify enable mBtGatt.setCharacteristicNotification(printerCharacteristic, true); BluetoothGattDescriptor descriptor = printerCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBtGatt.writeDescriptor(descriptor); } } } // write characteristic callback @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); switch (status) { case BluetoothGatt.GATT_SUCCESS: Log.d(TAG, "Write Data Success"); break; case BluetoothGatt.GATT_FAILURE: Log.d(TAG, "Write Data Filed"); break; case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: Log.d(TAG, "Write not permitted"); break; } sending = false; runQueue(); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); readCharacteristicValue(characteristic); } }; /** * 讀取 BLE characteristic 資料 * * @param characteristic */ private void readCharacteristicValue(BluetoothGattCharacteristic characteristic) { byte[] data = characteristic.getValue(); StringBuffer buffer = new StringBuffer("0x"); int i; for (byte b : data) { i = b & 0xff; buffer.append(Integer.toHexString(i)); } Log.d(TAG, "ReadData: " + buffer.toString()); Log.d(TAG, "ReadData(As String): " + new String(data)); mNotifyListener.onNotify(new String(data)); } /** * 初始化檢查 * * @return 檢查成功返回true */ public boolean init() { BluetoothManager btMng = (BluetoothManager) ctx.getSystemService(Context.BLUETOOTH_SERVICE); if (btMng == null) return false; mAdapter = btMng.getAdapter(); if (mAdapter == null) return false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mLeScanner = mAdapter.getBluetoothLeScanner(); if (mLeScanner == null) return false; mLeSettings = new ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build(); mLeFilters = new ArrayList<>(); // mLeFilters.add(new ScanFilter.Builder().setServiceUuid(new ParcelUuid(UUID.fromString(serviceUUID))).build()); } return true; } private BluetoothAdapter.LeScanCallback mLeScanCB = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] bytes) { String devAddr = bluetoothDevice.getAddress(); if (devAddr.equals(macAddr)) { Log.d(TAG, bluetoothDevice.getName() + " has available service UUID"); // 處理連線配段相關 stopScan(); connect(bluetoothDevice); } } }; private ScanCallback mLeScanCB_21 = new ScanCallback() { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onScanResult(int callbackType, ScanResult result) { // super.onScanResult(callbackType, result); BluetoothDevice device = result.getDevice(); Log.d(TAG, "Device : " + device.getName() + " Found\nDeviceMac : " + device.getAddress()); if (device.getAddress().equals(macAddr)) { // 處理連線配段相關 stopScan(); connect(device); } } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); Log.d(TAG, "BLE Scan Fail Code : " + errorCode); } }; public void startScan() { if (mAdapter == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mLeScanner == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Log.d(TAG, "Start Scanning"); mLeScanner.startScan(mLeFilters, mLeSettings, mLeScanCB_21); } else { mAdapter.startLeScan(mLeScanCB); } mState = SCANNING; if(this.mStateListener != null) { this.mStateListener.onChange(mState); } } public void stopScan() { if (mAdapter == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mLeScanner == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mLeScanner.stopScan(mLeScanCB_21); } else { mAdapter.stopLeScan(mLeScanCB); } if (mState == SCANNING) { mState = DISCONNECTED; if(this.mStateListener != null) { this.mStateListener.onChange(mState); } } } /** * 中斷與設備的連線 */ public void disconnect() { mState = DISCONNECTED; isConnected = false; if (mBtGatt != null) mBtGatt.disconnect(); } /** * 與指定設備建立連線 * * @param device */ public void connect(BluetoothDevice device) { if (device == null) return; mBtGatt = device.connectGatt(ctx, false, mBtGattCB); mState = CONNECTING; if(this.mStateListener != null){ this.mStateListener.onChange(mState); } } public void setOnStateChangeListener(stateChangeListener listener) { this.mStateListener = listener; } public void setOnNotifyListener(notifyListener listener) { this.mNotifyListener = listener; } private void sendData(byte[] data) { if (data == null || data.length < 1 || data.length > 20) return; boolean setVal = printerCharacteristic.setValue(data); boolean sendVal = mBtGatt.writeCharacteristic(printerCharacteristic); Log.d(TAG, String.format("setVal: %s / sendVal: %s", String.valueOf(setVal), String.valueOf(sendVal))); } public void sendQueue() { if (sendingQueue == null) sendingQueue = new ArrayList<>(); if (dataQueue == null) dataQueue = new ArrayList<>(); if (dataQueue.size() == 0) return; sendingQueue.clear(); StringBuilder strbuild = new StringBuilder(); for (String s : dataQueue) { strbuild.append(s); } String str = strbuild.toString(); byte[] dataByte = str.getBytes(); pushQueue(new byte[]{0x00}); int idx = 0; while (true) { int st = idx * 18; int ed = idx * 18 + 18; ed = ed > dataByte.length ? dataByte.length : ed; byte[] tmp = Arrays.copyOfRange(dataByte, st, ed); byte[] sdata = new byte[2 + tmp.length]; sdata[1] = (byte) (idx & 0xff); sdata[0] = (byte) ((idx >> 8) & 0xff); for (int i = 2; i < sdata.length; i++) { if (i - 2 >= tmp.length) break; sdata[i] = tmp[i - 2]; } pushQueue(sdata); idx++; if (ed >= dataByte.length) break; } pushQueue(new byte[]{(byte) 0xff}); dataQueue.clear(); } private void pushQueue(byte[] data) { if (data == null || data.length == 0) return; if (sendingQueue == null) sendingQueue = new ArrayList<>(); sendingQueue.add(data); runQueue(); } private void runQueue() { if (sendingQueue == null) sendingQueue = new ArrayList<>(); if (sendingQueue.size() < 1 || sending == true) return; sending = true; byte[] d = sendingQueue.get(0); sendingQueue.remove(0); sendData(d); } public void clearQueue() { dataQueue.clear(); } public PrinterBle textTab() { return addText("\t"); } public PrinterBle textNewLine() { return addText("\n"); } public PrinterBle addText(String txt) { if (txt == null || txt.isEmpty()) return this; dataQueue.add(txt); return this; } public PrinterBle addTextln(String txt) { if (txt == null || txt.isEmpty()) return this; return addText(txt + "\n"); } public PrinterBle setAlign(int align) { String strAlign = ""; switch (align) { case ALIGN_CENTER: strAlign = "ct"; break; case ALIGN_LEFT: strAlign = "lt"; break; case ALIGN_RIGHT: strAlign = "rt"; break; default: return this; } this.lastAlign = align; return addText("__&a" + strAlign + "__"); } public PrinterBle setSize(int w, int h) { if (w < 1 || h < 1 || w > 3 || h > 3) return this; return addText(String.format("__&s%d,%d__", w, h)); } public PrinterBle addHR(){ int align = this.lastAlign; setAlign(ALIGN_CENTER); addTextln(StringUtils.repeat('-', 20)); setAlign(align); return this; } public void resetSize() { setSize(1, 1); } }