using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
namespace CoreBluetooth
{
public enum CBPeripheralState
{
Disconnected = 0,
Connecting,
Connected,
Disconnecting
}
public interface ICBPeripheralDelegate
{
void DidDiscoverServices(CBPeripheral peripheral, CBError error) { }
void DidDiscoverCharacteristics(CBPeripheral peripheral, CBService service, CBError error) { }
void DidUpdateValueForCharacteristic(CBPeripheral peripheral, CBCharacteristic characteristic, CBError error) { }
void DidWriteValueForCharacteristic(CBPeripheral peripheral, CBCharacteristic characteristic, CBError error) { }
void IsReadyToSendWriteWithoutResponse(CBPeripheral peripheral) { }
void DidUpdateNotificationStateForCharacteristic(CBPeripheral peripheral, CBCharacteristic characteristic, CBError error) { }
void DidReadRSSI(CBPeripheral peripheral, int rssi, CBError error) { }
void DidUpdateName(CBPeripheral peripheral) { }
void DidModifyServices(CBPeripheral peripheral, CBService[] services) { }
}
internal interface INativePeripheralDelegate
{
void DidDiscoverServices(string[] serviceUUIDs, CBError error) { }
void DidDiscoverCharacteristics(string serviceUUID, string[] characteristicUUIDs, CBError error) { }
void DidUpdateValueForCharacteristic(string serviceUUID, string characteristicUUID, byte[] data, CBError error) { }
void DidWriteValueForCharacteristic(string serviceUUID, string characteristicUUID, CBError error) { }
void IsReadyToSendWriteWithoutResponse() { }
void DidUpdateNotificationStateForCharacteristic(string serviceUUID, string characteristicUUID, bool enabled, CBError error) { }
void DidReadRSSI(int rssi, CBError error) { }
void DidUpdateName() { }
void DidModifyServices(string[] invalidatedServiceUUIDs) { }
}
///
/// A remote peripheral device.
/// https://developer.apple.com/documentation/corebluetooth/cbperipheral
///
public class CBPeripheral : INativePeripheralDelegate, IDisposable
{
bool _disposed = false;
internal SafeNativePeripheralHandle Handle { get; }
NativePeripheralProxy _nativePeripheral = null;
string _identifier = null;
public string Identifier
{
get
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
if (_identifier == null)
{
_identifier = _nativePeripheral.Identifier;
}
return _identifier;
}
}
public string Name
{
get
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
return _nativePeripheral.Name;
}
}
public ICBPeripheralDelegate Delegate { get; set; }
///
/// ICBPeripheralDelegate callbacks will be called in this context.
///
public SynchronizationContext CallbackContext { get; set; }
List _services = new List();
public ReadOnlyCollection Services { get; }
internal CBPeripheral(SafeNativePeripheralHandle nativePeripheral, SynchronizationContext callbackContext)
{
Handle = nativePeripheral;
_nativePeripheral = new NativePeripheralProxy(Handle, this);
Services = _services.AsReadOnly();
CallbackContext = callbackContext;
}
///
/// Discovers the specified services of the peripheral.
/// If the servicesUUIDs parameter is nil, this method returns all of the peripheral’s available services. This is much slower than providing an array of service UUIDs to search for.
///
public void DiscoverServices(string[] serviceUUIDs = null)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.DiscoverServices(serviceUUIDs);
}
///
/// Discovers the specified characteristics of a service.
///
public void DiscoverCharacteristics(string[] characteristicUUIDs, CBService service)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.DiscoverCharacteristics(characteristicUUIDs, service);
}
///
/// Discover all characteristics in a service (slow).
///
public void DiscoverCharacteristics(CBService service)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
DiscoverCharacteristics(null, service);
}
///
/// Retrieves the value of a specified characteristic.
/// When you call this method to read the value of a characteristic, the peripheral calls the peripheral(_:didUpdateValueFor:error:) method of its delegate object.
/// If the peripheral successfully reads the value of the characteristic, you can access it through the characteristic’s value property.
///
public void ReadValue(CBCharacteristic characteristic)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.ReadValue(characteristic);
}
public void WriteValue(byte[] data, CBCharacteristic characteristic, CBCharacteristicWriteType type)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.WriteValue(data, characteristic, type);
}
///
/// The maximum amount of data, in bytes, you can send to a characteristic in a single write type.
///
public int GetMaximumWriteValueLength(CBCharacteristicWriteType type)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
return _nativePeripheral.GetMaximumWriteValueLength(type);
}
///
/// Sets notifications or indications for the value of a specified characteristic.
///
public void SetNotifyValue(bool enabled, CBCharacteristic characteristic)
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.SetNotifyValue(enabled, characteristic);
}
///
/// The connection state of the peripheral.
///
public CBPeripheralState State
{
get
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
return _nativePeripheral.State;
}
}
public bool CanSendWriteWithoutResponse
{
get
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
return _nativePeripheral.CanSendWriteWithoutResponse;
}
}
///
/// Retrieves the current RSSI value for the peripheral while connected to the central manager.
///
public void ReadRSSI()
{
ExceptionUtils.ThrowObjectDisposedExceptionIf(_disposed, this);
_nativePeripheral.ReadRSSI();
}
internal CBCharacteristic FindCharacteristic(string serviceUUID, string characteristicUUID)
{
if (string.IsNullOrEmpty(serviceUUID))
{
return Services.SelectMany(s => s.Characteristics).FirstOrDefault(c => c.UUID == characteristicUUID);
}
var service = Services.FirstOrDefault(s => s.UUID == serviceUUID);
if (service == null) return null;
return service.FindCharacteristic(characteristicUUID);
}
CBCharacteristic FindOrInitializeCharacteristic(CBService service, string characteristicUUID)
{
var characteristic = service.FindCharacteristic(characteristicUUID);
if (characteristic == null)
{
var nativeCharacteristicProxy = new NativeCharacteristicProxy(service.UUID, characteristicUUID, service.Peripheral.Handle);
characteristic = new CBCharacteristic(characteristicUUID, nativeCharacteristicProxy);
}
return characteristic;
}
void INativePeripheralDelegate.DidDiscoverServices(string[] serviceUUIDs, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var services = serviceUUIDs.Select(uuid =>
{
return _services.FirstOrDefault(s => s.UUID == uuid) ?? new CBService(uuid, this);
}).ToArray();
_services.Clear();
_services.AddRange(services);
Delegate?.DidDiscoverServices(this, error);
}, null);
}
void INativePeripheralDelegate.DidDiscoverCharacteristics(string serviceUUID, string[] characteristicUUIDs, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var service = _services.FirstOrDefault(s => s.UUID == serviceUUID);
if (service == null) return;
var characteristics = characteristicUUIDs.Select(uuid => FindOrInitializeCharacteristic(service, uuid)).ToArray();
service.UpdateCharacteristics(characteristics);
Delegate?.DidDiscoverCharacteristics(this, service, error);
}, null);
}
void INativePeripheralDelegate.DidUpdateValueForCharacteristic(string serviceUUID, string characteristicUUID, byte[] data, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var characteristic = FindCharacteristic(serviceUUID, characteristicUUID);
if (characteristic == null) return;
characteristic.UpdateValue(data);
Delegate?.DidUpdateValueForCharacteristic(this, characteristic, error);
}, null);
}
void INativePeripheralDelegate.DidWriteValueForCharacteristic(string serviceUUID, string characteristicUUID, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var characteristic = FindCharacteristic(serviceUUID, characteristicUUID);
if (characteristic == null) return;
Delegate?.DidWriteValueForCharacteristic(this, characteristic, error);
}, null);
}
void INativePeripheralDelegate.IsReadyToSendWriteWithoutResponse()
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
Delegate?.IsReadyToSendWriteWithoutResponse(this);
}, null);
}
void INativePeripheralDelegate.DidUpdateNotificationStateForCharacteristic(string serviceUUID, string characteristicUUID, bool isNotifying, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
var characteristic = FindCharacteristic(serviceUUID, characteristicUUID);
if (characteristic == null) return;
characteristic.UpdateIsNotifying(isNotifying);
Delegate?.DidUpdateNotificationStateForCharacteristic(this, characteristic, error);
}, null);
}
void INativePeripheralDelegate.DidReadRSSI(int rssi, CBError error)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
Delegate?.DidReadRSSI(this, rssi, error);
}, null);
}
void INativePeripheralDelegate.DidUpdateName()
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
Delegate?.DidUpdateName(this);
}, null);
}
void INativePeripheralDelegate.DidModifyServices(string[] invalidatedServiceUUIDs)
{
CallbackContext.Post(_ =>
{
if (_disposed) return;
List invalidatedServices = new List();
foreach (var uuid in invalidatedServiceUUIDs)
{
var service = _services.FirstOrDefault(s => s.UUID == uuid);
if (service != null)
{
invalidatedServices.Add(service);
_services.Remove(service);
}
}
Delegate?.DidModifyServices(this, invalidatedServices.ToArray());
}, null);
}
public override string ToString()
{
return $"CBPeripheral: identifier = {Identifier}, name = {Name}, state = {State}";
}
public void Dispose()
{
if (_disposed) return;
_nativePeripheral?.Dispose();
Handle?.Dispose();
_disposed = true;
}
}
}