12/08/2018, 15:20

[Xamarin iOS][Bluetooth] Sử dụng Bluetooth Low Energy Trong Xamarin iOS

1. Các khái niệm cơ bản. Trong một kết nối BLE giữa hai thiết bị, một thiết bị sẽ đóng vai trò là Central (tạm hiểu là khách), một thiết bị sẽ đóng vai trò là peripheral (một thiết bị chủ). Thiết bị khách sẽ gửi các request đến một thiết bị chủ, ở phía bên kia, thiết bị chủ sẽ căn cứ vào nội dung ...

1. Các khái niệm cơ bản. Trong một kết nối BLE giữa hai thiết bị, một thiết bị sẽ đóng vai trò là Central (tạm hiểu là khách), một thiết bị sẽ đóng vai trò là peripheral (một thiết bị chủ). Thiết bị khách sẽ gửi các request đến một thiết bị chủ, ở phía bên kia, thiết bị chủ sẽ căn cứ vào nội dung của request nhận được mà gửi trả lại cho thiết bị khách nội dung thích hợp, hoặc thực thi một hành động nào đó. Vậy nếu cần viết một ứng dụng chạy trên iOS, dùng để điều khiển một thiết bị khác (ví dụ: nồi cơm điện) thông qua BLE, thì điện thoại lúc này đóng vai trò là một central, còn nồi cơm điện sẽ đóng vai trò là một peripheral. Trong xamarin ios,mối quan hệ điện thoại (central)- nồi cơm điện (peripheral), được thể hiện qua các đối tượng sau: CBCentralManager và Peripheral.

  • CBCentralManager đố tượng này có các hàm quan trọng như
    • public void ScanForPeripherals (CBUUID[] peripheralUuids, NSDictionary options): hàm này dùng để tìm kiềm(scan) các peripheral ỏ xung quanh.Hàm này có hai đối số truyền vào hỗ trợ cho việc filter khi tìm kiếm. Mỗi mộit peripheral muốn các central biết đến sự tồn tại của mình, thì cần phải advertise (tạm hiểu liên tục "bắn" ra các gói thông tin (package), tuỳ vào thiết kế, thông tin mà các gói đó mang sẽ khác nhau. Có một vài trường dữ liệu đã được chuyện hoá theo BLE protocol, trong đó có một trường là UUID. Vậy giả sử, nồi cơm điện, bắn ra một package có trường UUID là"xxxx-yyyy-zzz-tttt", với đối số đầu tiên của ScanForPeripherals(),ta truyền vào một CBUUID có gí trị là "xxxx-yyyy-zzz-tttt", khi đó kết quả của việc scan sẽ chỉ là những peripheral mà package khi advertise của nó có giá trị của trường UUID là "xxxx-yyyy-zzz-tttt". Việc này có ý nghĩa lớn, khi xung quanh môi trường có nhiều loại thiết bị BLE: tivi, lò ví sóng, điện thoại... nhưng ứng dụng của ta lại chỉ cho một loại thiết bị cụ thể. Đối số thứ 2 là một NSDictionary,cho phép ta on-off một vài đặc điểm, theo kiểu key-value được truyền qua NSDictionary trên,cái mà chúng ta cần để ý nhất đó là giá trị của CBCentralManager.ScanOptionAllowDuplicatesKey. Trước tiên, giải thích một chút về cơ chết advertise của peripheral, cứ tuần tự sau một khoảng thời gian nhất định peripheral sẽ bắn ra một package chứa thông tin của nó. Sau khi hàm ScanForPeripherals, API sẽ trả về cho chúng ta một peripheral quả một hàm delegate gọi là DiscoveredPeripheral(), mỗi khi nó nhận được một package của peripheral đó. Tuỳ thuộc vào giá trị của ScanOptionAllowDuplicatesKey là true (giá trị 1) thì hàm DiscoveredPeripheral() sẽ được gọi bất cứ khi nào API nhận được package, còn nếu là false (giá trị 0), thì DiscoveredPeripheral() chỉ được gọi tại lần đầu tiên package được phát hiện. Mình thường dùng đoạn code sau, để scan các thiết bị xung quanh.

            NSMutableDictionary op = new NSMutableDictionary();
            op.SetValueForKey(NSNumber.FromInt16(1), CBCentralManager.ScanOptionAllowDuplicatesKey);
            CentralManager.ScanForPeripherals(pers, op); 
      
  • CentralManager.RetrievePeripheralsWithIdentifiers (params NSUuid[] identifiers): hàm này sẽ trả về một CBPeripheral[], ứng với mảng id truyền vào. Mỗi một CBPeripheral có một Identifier duy nhất. Những peripheral nào đủ điều kiện được trả về qua RetrievePeripheralsWithIdentifiers(), đó là những peripheral đã được connect đến, hoặc những peripheral đã được trả về qua hàm ScanForPeripherals().
  • ConnectPeripheral (CBPeripheral peripheral, PeripheralConnectionOptions options = null): hàm này đúng như tên gọi, sau khi đã scan được một peripheral, giờ muốn tạo liên kết đến nó, hãy gọi hàm này, đối số truyền vào là peripheral muốn tạo liên kết. -Peripheral là một thể hiện của thiết bị ngoại vi, mỗi peripheral có một Identify riêng, tuy nhiên trên mỗi thiết bị điện thoại khác nhau thì Identify của cùng một peripheral lại khác nhau. Ví dụ, một cái nồi cơm điện, khi scan bằng điện thoại A, sẽ nhận được một peripheral có Identify là "xxxx-xxxx", tuy nhiên khi scan bằng điện thoại B là sẽ nhận được một peripheral có Indentify là "yyyy-yyyy". Những hàm ta cần quan tâm của đối tượng nà là:
    • public void DiscoverServices (): sau khi đã kết nối được với thiết bị, hàm này giúp lấy ra các services mà peripheral cung câp
    • public void DiscoverCharacteristics (CBService forService); khi đã discover được service, việc tiếp theo là discover các characteristic của service đó, characateristic là đơn vị nhỏ nhất, dữ liệu sẽ được lưu trong đó.
    • Kết quả của cả hai hàm trên đều được trả về qua đối đượng CBPeripheralDelegate.

2. Delegate

Có hai Delegate chúng ta cần quan tâm đó là CBCentralManagerDelegate- đối tượng chứa các hàm phàn hồi từ hệ thống: khi các peripheral được tìm thấy, hoặc khi đã kết nối được đến peripheral đó; CBPeripheralDelegate- đối tượng chứa các phần hồi liên quan quan đến hiện trạng của peripheral đó: khi các services, characteristic của peripheral được discover, hoặc khi một characteristic thay đổi gía trị...

  • CBCentralManagerDelegate:
    • public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, Foundation.NSDictionary advertisementData, Foundation.NSNumber RSSI): đây chính là nơi hệ thống sẽ thông báo cho chúng ta biết khi bắt được mọt advertise package, cũng là kết quả của hàm ScanForPeripherals() được đề cập ở trện.
    • public override void ConnectedPeripheral(CBCentralManager central, CBPeripheral peripheral): khi ta gọi hàm ConnectPeripheral() nếu việc kết nối thành công thì hệ thống sẽ báo cho ta qua hàm này.
    • public override void FailedToConnectPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error): Khi không thể kết nối tới peripheral.
    • public override void DisconnectedPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError error): khi kết nối giữa peripheral bị ngắt, ngắt kết nối ở đây cũng có thể là do ta chủ động ngắt, hoặc cũng thể do sự cố (thiết bị mất nguồn điện chẳng hạn).
    • public override void UpdatedState(CBCentralManager central): hiện trạng của phần cứng Bluetooth của điện thoại, các giá tị có thể có của CBCentralManagerState gồm PoweredOff,PoweredOn,Resetting,Unauthorized,Unsupported,Unknown.
  • CBPeripheralDelegate:
    • public virtual void DiscoveredService(CBPeripheral peripheral, NSError error): sau khi gọi hàm DiscoverServices (), kết quả sẽ được trả về qua hàm này.
    • public virtual void DiscoveredCharacteristic(CBPeripheral peripheral, CBService service, NSError error): tương tự sau khi gọi DiscoverCharacteristics(), kết quả sẽ được trả về qua hàm này.
    • Cách đọc giá trị của một characteristic sử dụng hàm sau:
    public void ReadCharacteristics(string deviceID, string serviceID, string characterID,CBPeripheralDelegate callback)
        {
            var gatt = CentralManager.RetrievePeripheralsWithIdentifiers(new NSUuid(deviceID));
            if (gatt != null && gatt.Length > 0)
            {
                gatt[0].Delegate = callback;
                var service = GetCBService(gatt[0], serviceID);
                if (service != null)
                {
                    var rxCharacteristic = GetCBCharacteristic(service, characterID);
                    if (rxCharacteristic != null)
                    {
                        gatt[0].ReadValue(rxCharacteristic);
                    }
                }

            }
        }

Kết quả sẽ được trả về qua delegate

      public override void UpdatedCharacterteristicValue(CBPeripheral peripheral, CBCharacteristic characteristic, NSError error)
        {
             if (characteristic.Value == null || characteristic.Value.Length == 0)
                return null;
            var data = characteristic.Value.ToArray();
        }
  • Cách ghi (Write) giá trị vào một characteristic sử dụng đoạn code sau:
     public void WriteCharacteristics(string deviceID, string serviceID, string characterID, byte[] data, CBPeripheralDelegate callback callback)
        {
            var gatt = CentralManager.RetrievePeripheralsWithIdentifiers(new NSUuid(deviceID));
            var callback = _gattCallbacks[deviceID];
            if (gatt != null && gatt.Length > 0)
            {
                gatt[0].Delegate = callback;
                var service = GetCBService(gatt[0], serviceID);
                if (service != null)
                {
                    var rxCharacteristic = GetCBCharacteristic(service, characterID);
                    if (rxCharacteristic != null)
                    {
                        NSData writeData = NSData.FromArray(data);
                        gatt[0].WriteValue(writeData, rxCharacteristic, CBCharacteristicWriteType.WithResponse);
                    }
                }
            }
        }

Kết quả của việc ghi giá trị sẽ được trả về qua hàm

   public override void WroteCharacteristicValue(CBPeripheral peripheral, CBCharacteristic characteristic, NSError error)
        {
               if(error==null)
                           System.Diagnostics.Debug.WriteLine(" WroteCharacteristicValue Success ");
               else
                            System.Diagnostics.Debug.WriteLine(" WroteCharacteristicValue Faile ");
        }
  • Cách lắng nghe sự thay đổi giá trị của một characteristic, sử dụng đoạng code sau:
   void ListenCharacteristicChanged(CBPeripheral peripheral, CBCharacteristic characteristic)
        {
            if ((characteristic.Properties & CBCharacteristicProperties.Notify) != 0)
            {
                peripheral.SetNotifyValue(true, characteristic);
            }
        }

Khi có sự thay đổi đổi giá trị thì hàm callback sẽ là UpdatedCharacterteristicValue,cũng giống như trường hợp đọc giá trị

  public override void UpdatedCharacterteristicValue(CBPeripheral peripheral, CBCharacteristic characteristic, NSError error)
        {
             if (characteristic.Value == null || characteristic.Value.Length == 0)
                return null;
            var data = characteristic.Value.ToArray();
        }
0