diff --git a/.idea/misc.xml b/.idea/misc.xml index 75dac50..3963879 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,7 +24,7 @@ - + diff --git a/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt b/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt index 682815f..89d28d7 100644 --- a/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt +++ b/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt @@ -19,7 +19,7 @@ class MainActivity : Activity() { val btn: Button by bind(R.id.bt) val connectBtn: Button by bind(R.id.connect) val disconnectBtn: Button by bind(R.id.disconnect) - var ble: BLECls? = null + var ble: PrinterBle? = null val uiHandler: Handler = Handler() var th: Thread? = null var thrun: Boolean = false @@ -33,7 +33,11 @@ class MainActivity : Activity() { connectBtn.isEnabled = true disconnectBtn.isEnabled = false - ble = BLECls(this@MainActivity) + ble = PrinterBle(this@MainActivity, "43:43:A1:12:1F:AC", "dd535b71-8f05-4e30-beb0-6fa7ea3dfc3e", "00000002-8f05-4e30-beb0-6fa7ea3dfc3e") + + ble?.setOnStateChangeListener { + System.out.println("Now State is >>>> " + it) + } var permissionCheck: Int = 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -46,7 +50,7 @@ class MainActivity : Activity() { btn.setOnClickListener { val txt: String = intxt.text.toString() - ble?.sendLongData(txt.toByteArray()) + } connectBtn.setOnClickListener { @@ -67,41 +71,17 @@ class MainActivity : Activity() { } fun scanConnect() { - if (ble?.init()!!) { - isInit = true + if(ble?.init()!!){ ble?.startScan() } - - th = Thread(Runnable { - thrun = true - while (thrun) { - System.out.println("state ===== " + ble?.mState) - if (ble?.mState == ble?.CONNECTED) { - uiHandler.post { - btn.isEnabled = true - disconnectBtn.isEnabled = true - connectBtn.isEnabled = false - } - thrun = false - break - } - Thread.sleep(200) - } - }) - th?.start() } fun disconnect() { - thrun = false - if (ble?.isScanning!!) { + if(ble?.mState!! == PrinterBle.SCANNING) { ble?.stopScan() } - if (ble?.mState == ble?.CONNECTED) { + if(ble?.mState!! == PrinterBle.CONNECTED) { ble?.disconnect() } - disconnectBtn.isEnabled = false - connectBtn.isEnabled = true - btn.isEnabled = false - isInit = false } } \ No newline at end of file diff --git a/app/src/main/java/xyz/mtfos/btdemo/PrinterBle.java b/app/src/main/java/xyz/mtfos/btdemo/PrinterBle.java new file mode 100644 index 0000000..1944086 --- /dev/null +++ b/app/src/main/java/xyz/mtfos/btdemo/PrinterBle.java @@ -0,0 +1,387 @@ +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.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.ParcelUuid; +import android.util.Log; + +import org.apache.commons.lang3.ArrayUtils; + +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; + + // 藍芽狀態 + public int mState = 0x00; + + // 藍芽通訊flag + private boolean sending = false; + + // 發送內容陣列 + private ArrayList sendingQueue = new ArrayList<>(); + + // 待處理發送陣列 + private ArrayList dataQueue = new ArrayList<>(); + + // Listener Object + private stateChangeListener mStateListener; + private notifyListener mNotifyListener; + + 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"); + } + mStateListener.onChange(mState); + if(mState == CONNECTED) { + mBtGatt.discoverServices(); + } + } 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); + } + } + } + + // 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; + } + + @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 = CONNECTING; + } + + 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 == CONNECTING) mState = DISCONNECTED; + } + + /** + * 中斷與設備的連線 + */ + public void disconnect() { + mState = DISCONNECTED; + if (mBtGatt != null) mBtGatt.disconnect(); + } + + /** + * 與指定設備建立連線 + * + * @param device + */ + public void connect(BluetoothDevice device) { + if (device == null) return; + mBtGatt = device.connectGatt(ctx, false, mBtGattCB); + mState = CONNECTING; + } + + 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))); + } + + private 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 void textTab() { + addText("\t"); + } + + public void textNewLine() { + addText("\n"); + } + + public void addText(String txt) { + if (txt == null || txt.isEmpty()) return; + dataQueue.add(txt); + } + + public void addTextln(String txt) { + if (txt == null || txt.isEmpty()) return; + addText(txt + "\n"); + } + + public void setAlign(int align) { + String strAlign = ""; + switch (align) { + case ALIGN_CENTER: + strAlign = "ct"; + break; + case ALIGN_LEFT: + strAlign = "lt"; + case ALIGN_RIGHT: + strAlign = "rt"; + default: + return; + } + addText("__a" + strAlign + "__"); + } + + public void setSize(int w, int h) { + if (w < 1 || h < 1 || w > 3 || h > 3) return; + addText(String.format("__s%d,%d__", w, h)); + } + + public void resetSize() { + setSize(1, 1); + } +}