├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ ├── iOS-BLE-Library-Mock.xcscheme
│ ├── iOS-BLE-Library.xcscheme
│ └── iOS-BLE-LibraryTests.xcscheme
├── Gemfile
├── Gemfile.lock
├── IOS-BLE-Library-Mock.podspec
├── IOS-BLE-Library.podspec
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── iOS-BLE-Library-Mock
│ ├── Alias.swift
│ ├── CentralManager
│ │ ├── CentralManager.swift
│ │ ├── Model
│ │ │ └── ScanResult.swift
│ │ └── ReactiveCentralManagerDelegate.swift
│ ├── Documentation.docc
│ │ ├── CentralManager
│ │ │ ├── CentralManager.md
│ │ │ ├── CentralManager
│ │ │ │ └── connect.md
│ │ │ └── ReactiveCentralManagerDelegate.md
│ │ ├── Documentation.md
│ │ └── Peripheral
│ │ │ └── Peripheral.md
│ ├── Peripheral
│ │ ├── Peripheral+Writer.swift
│ │ ├── Peripheral.swift
│ │ └── ReactivePeripheralDelegate.swift
│ └── Utilities
│ │ ├── AdvertisementData.swift
│ │ ├── AsyncCharacteristicData.swift
│ │ ├── CBManagerState.swift
│ │ ├── Extensions
│ │ ├── CBManagerState+Ext.swift
│ │ ├── Data+Ext.swift.swift
│ │ └── Publishers+Async.swift
│ │ ├── Logger.swift
│ │ ├── Publishers
│ │ ├── ContinuationSubscriber.swift
│ │ ├── Publishers+Bluetooth.swift
│ │ ├── Publishers+Connectable.swift
│ │ ├── Publishers+FailablePrefix.swift.swift
│ │ ├── Publishers+GuestList.swift
│ │ └── Publishers+Peripheral.swift
│ │ ├── Queue.swift
│ │ ├── RSSI.swift
│ │ └── UnimplementedError.swift
└── iOS-BLE-Library
│ ├── CentralManager
│ ├── CentralManager.swift
│ ├── Model
│ │ └── ScanResult.swift
│ └── ReactiveCentralManagerDelegate.swift
│ ├── Documentation.docc
│ ├── CentralManager
│ │ ├── CentralManager.md
│ │ ├── CentralManager
│ │ │ └── connect.md
│ │ └── ReactiveCentralManagerDelegate.md
│ ├── Documentation.md
│ └── Peripheral
│ │ └── Peripheral.md
│ ├── Peripheral
│ ├── Peripheral+Writer.swift
│ ├── Peripheral.swift
│ └── ReactivePeripheralDelegate.swift
│ └── Utilities
│ ├── AdvertisementData.swift
│ ├── AsyncCharacteristicData.swift
│ ├── CBManagerState.swift
│ ├── Extensions
│ ├── CBManagerState+Ext.swift
│ ├── Data+Ext.swift.swift
│ └── Publishers+Async.swift
│ ├── Logger.swift
│ ├── Publishers
│ ├── ContinuationSubscriber.swift
│ ├── Publishers+Bluetooth.swift
│ ├── Publishers+Connectable.swift
│ ├── Publishers+FailablePrefix.swift.swift
│ └── Publishers+GuestList.swift
│ ├── Queue.swift
│ ├── RSSI.swift
│ └── UnimplementedError.swift
├── Tests
└── iOS-BLE-LibraryTests
│ ├── CentralManagerStateTest.swift
│ ├── CentralManagerTests.swift
│ ├── PeripheralMultitaskingTests.swift
│ └── PeripheralReadWriteDescriptorTests.swift
├── code_gen
├── additional_files
│ └── Alias.swift
├── code_gen.py
├── copy_files.py
└── replace_code.py
├── docs
├── css
│ ├── chunk-c0335d80.10a2f091.css
│ ├── documentation-topic.1d1eec04.css
│ ├── documentation-topic~topic.b6287bcf.css
│ ├── documentation-topic~topic~tutorials-overview.d6f5411c.css
│ ├── index.038e887c.css
│ ├── topic.d8c126f3.css
│ └── tutorials-overview.c249c765.css
├── data
│ └── documentation
│ │ ├── ios_ble_library.json
│ │ └── ios_ble_library
│ │ ├── advertisementdata.json
│ │ ├── advertisementdata
│ │ ├── !=(_:_:).json
│ │ ├── ==(_:_:).json
│ │ ├── advertisedid().json
│ │ ├── connectablemock.json
│ │ ├── equatable-implementations.json
│ │ ├── hash(into:).json
│ │ ├── init().json
│ │ ├── init(_:).json
│ │ ├── isconnectable.json
│ │ ├── localname.json
│ │ ├── manufacturerdata.json
│ │ ├── overflowserviceuuids.json
│ │ ├── rawdata.json
│ │ ├── servicedata.json
│ │ ├── serviceuuids.json
│ │ ├── solicitedserviceuuids.json
│ │ ├── txpowerlevel.json
│ │ └── unconnectablemock.json
│ │ ├── asynccharacteristicdata.json
│ │ ├── asynccharacteristicdata
│ │ ├── allsatisfy(_:).json
│ │ ├── asyncsequence-implementations.json
│ │ ├── compactmap(_:)-3l977.json
│ │ ├── compactmap(_:)-8kqp1.json
│ │ ├── contains(_:).json
│ │ ├── contains(where:).json
│ │ ├── drop(while:).json
│ │ ├── dropfirst(_:).json
│ │ ├── element.json
│ │ ├── filter(_:).json
│ │ ├── first(where:).json
│ │ ├── flatmap(_:)-50ncz.json
│ │ ├── flatmap(_:)-8auld.json
│ │ ├── makeasynciterator().json
│ │ ├── map(_:)-70277.json
│ │ ├── map(_:)-8x8iq.json
│ │ ├── max(by:).json
│ │ ├── min(by:).json
│ │ ├── next().json
│ │ ├── prefix(_:).json
│ │ ├── prefix(while:).json
│ │ ├── reduce(_:_:).json
│ │ └── reduce(into:_:).json
│ │ ├── asyncpublisher.json
│ │ ├── asyncpublisher
│ │ ├── allsatisfy(_:).json
│ │ ├── asynciterator.json
│ │ ├── asyncsequence-implementations.json
│ │ ├── characters.json
│ │ ├── compactmap(_:)-2ziwe.json
│ │ ├── compactmap(_:)-746g3.json
│ │ ├── contains(_:).json
│ │ ├── contains(where:).json
│ │ ├── drop(while:).json
│ │ ├── dropfirst(_:).json
│ │ ├── element.json
│ │ ├── filter(_:).json
│ │ ├── first(where:).json
│ │ ├── flatmap(_:)-648jy.json
│ │ ├── flatmap(_:)-9aeej.json
│ │ ├── init(_:).json
│ │ ├── iterator.json
│ │ ├── iterator
│ │ │ ├── element.json
│ │ │ └── next().json
│ │ ├── lines.json
│ │ ├── makeasynciterator().json
│ │ ├── map(_:)-155e0.json
│ │ ├── map(_:)-1cjy6.json
│ │ ├── max().json
│ │ ├── max(by:).json
│ │ ├── min().json
│ │ ├── min(by:).json
│ │ ├── prefix(_:).json
│ │ ├── prefix(while:).json
│ │ ├── reduce(_:_:).json
│ │ ├── reduce(into:_:).json
│ │ └── unicodescalars.json
│ │ ├── asyncstreamvalue.json
│ │ ├── asyncthrowingpublisher.json
│ │ ├── asyncthrowingpublisher
│ │ ├── allsatisfy(_:).json
│ │ ├── asynciterator.json
│ │ ├── asyncsequence-implementations.json
│ │ ├── characters.json
│ │ ├── compactmap(_:)-8it2j.json
│ │ ├── compactmap(_:)-zh70.json
│ │ ├── contains(_:).json
│ │ ├── contains(where:).json
│ │ ├── drop(while:).json
│ │ ├── dropfirst(_:).json
│ │ ├── element.json
│ │ ├── filter(_:).json
│ │ ├── first(where:).json
│ │ ├── flatmap(_:)-36cmu.json
│ │ ├── flatmap(_:)-39oak.json
│ │ ├── init(_:).json
│ │ ├── iterator.json
│ │ ├── iterator
│ │ │ ├── element.json
│ │ │ └── next().json
│ │ ├── lines.json
│ │ ├── makeasynciterator().json
│ │ ├── map(_:)-2gzj6.json
│ │ ├── map(_:)-3wux3.json
│ │ ├── max().json
│ │ ├── max(by:).json
│ │ ├── min().json
│ │ ├── min(by:).json
│ │ ├── prefix(_:).json
│ │ ├── prefix(while:).json
│ │ ├── reduce(_:_:).json
│ │ ├── reduce(into:_:).json
│ │ └── unicodescalars.json
│ │ ├── centralmanager.json
│ │ ├── centralmanager
│ │ ├── cancelperipheralconnection(_:).json
│ │ ├── centralmanager.json
│ │ ├── centralmanagerdelegate.json
│ │ ├── connect(_:options:).json
│ │ ├── connectedperipheralchannel.json
│ │ ├── disconnectedperipheralschannel.json
│ │ ├── err.json
│ │ ├── err
│ │ │ ├── badstate(_:).json
│ │ │ ├── localizeddescription.json
│ │ │ ├── unknownerror.json
│ │ │ └── wrongmanager.json
│ │ ├── init(centralmanager:).json
│ │ ├── init(centralmanagerdelegate:queue:options:).json
│ │ ├── isscanningchannel.json
│ │ ├── retrieveconnectedperipherals(withservices:).json
│ │ ├── retrieveperipherals(withidentifiers:).json
│ │ ├── scanforperipherals(withservices:).json
│ │ ├── scanresultschannel.json
│ │ ├── statechannel.json
│ │ └── stopscan().json
│ │ ├── combine.json
│ │ ├── combine
│ │ ├── publisher.json
│ │ └── publisher
│ │ │ └── value.json
│ │ ├── corebluetooth.json
│ │ ├── corebluetooth
│ │ ├── cbmanagerstate.json
│ │ └── cbmanagerstate
│ │ │ ├── customdebugstringconvertible-implementations.json
│ │ │ ├── customstringconvertible-implementations.json
│ │ │ ├── debugdescription.json
│ │ │ └── description.json
│ │ ├── foundation.json
│ │ ├── foundation
│ │ ├── data.json
│ │ └── data
│ │ │ ├── hexencodedstring(options:separator:).json
│ │ │ ├── hexencodingoptions.json
│ │ │ └── hexencodingoptions
│ │ │ ├── !=(_:_:).json
│ │ │ ├── contains(_:).json
│ │ │ ├── equatable-implementations.json
│ │ │ ├── formintersection(_:).json
│ │ │ ├── formsymmetricdifference(_:).json
│ │ │ ├── formunion(_:).json
│ │ │ ├── init().json
│ │ │ ├── init(_:).json
│ │ │ ├── init(arrayliteral:).json
│ │ │ ├── init(rawvalue:).json
│ │ │ ├── insert(_:).json
│ │ │ ├── intersection(_:).json
│ │ │ ├── isdisjoint(with:).json
│ │ │ ├── isempty.json
│ │ │ ├── isstrictsubset(of:).json
│ │ │ ├── isstrictsuperset(of:).json
│ │ │ ├── issubset(of:).json
│ │ │ ├── issuperset(of:).json
│ │ │ ├── optionset-implementations.json
│ │ │ ├── rawvalue.json
│ │ │ ├── remove(_:).json
│ │ │ ├── reverseendianness.json
│ │ │ ├── setalgebra-implementations.json
│ │ │ ├── subtract(_:).json
│ │ │ ├── subtracting(_:).json
│ │ │ ├── symmetricdifference(_:).json
│ │ │ ├── union(_:).json
│ │ │ ├── update(with:).json
│ │ │ └── uppercase.json
│ │ ├── peripheral.json
│ │ ├── peripheral
│ │ ├── discovercharacteristics(_:for:).json
│ │ ├── discoverdescriptors(for:).json
│ │ ├── discoverincludedservices(_:for:).json
│ │ ├── discoverservices(serviceuuids:).json
│ │ ├── err.json
│ │ ├── err
│ │ │ ├── !=(_:_:).json
│ │ │ ├── baddelegate.json
│ │ │ ├── equatable-implementations.json
│ │ │ ├── error-implementations.json
│ │ │ └── localizeddescription.json
│ │ ├── init(peripheral:delegate:).json
│ │ ├── listenvalues(for:).json
│ │ ├── name.json
│ │ ├── peripheral.json
│ │ ├── peripheraldelegate.json
│ │ ├── peripheralstatechannel.json
│ │ ├── readrssi().json
│ │ ├── readvalue(for:)-880d.json
│ │ ├── readvalue(for:)-8wwmb.json
│ │ ├── services.json
│ │ ├── setnotifyvalue(_:for:).json
│ │ ├── writevalue(_:for:).json
│ │ ├── writevaluewithoutresponse(_:for:).json
│ │ └── writevaluewithresponse(_:for:).json
│ │ ├── reactivecentralmanagerdelegate.json
│ │ ├── reactivecentralmanagerdelegate
│ │ ├── centralmanager(_:didconnect:).json
│ │ ├── centralmanager(_:diddisconnectperipheral:error:).json
│ │ ├── centralmanager(_:diddiscover:advertisementdata:rssi:).json
│ │ ├── centralmanager(_:didfailtoconnect:error:).json
│ │ └── centralmanagerdidupdatestate(_:).json
│ │ ├── reactiveperipheraldelegate.json
│ │ ├── reactiveperipheraldelegate
│ │ ├── peripheral(_:diddiscovercharacteristicsfor:error:).json
│ │ ├── peripheral(_:diddiscoverdescriptorsfor:error:).json
│ │ ├── peripheral(_:diddiscoverservices:).json
│ │ ├── peripheral(_:didmodifyservices:).json
│ │ ├── peripheral(_:didreadrssi:error:).json
│ │ ├── peripheral(_:didupdatenotificationstatefor:error:).json
│ │ ├── peripheral(_:didupdatevaluefor:error:)-2btq1.json
│ │ ├── peripheral(_:didupdatevaluefor:error:)-7o3t2.json
│ │ ├── peripheral(_:didwritevaluefor:error:)-29s4r.json
│ │ ├── peripheral(_:didwritevaluefor:error:)-2ei85.json
│ │ ├── peripheraldidupdatename(_:).json
│ │ └── peripheralisready(tosendwritewithoutresponse:).json
│ │ ├── rssi.json
│ │ ├── rssi
│ │ ├── !=(_:_:).json
│ │ ├── bad.json
│ │ ├── equatable-implementations.json
│ │ ├── good.json
│ │ ├── init(integerliteral:).json
│ │ ├── integerliteraltype.json
│ │ ├── ok.json
│ │ ├── outofrange.json
│ │ ├── practicalworst.json
│ │ ├── signal-swift.enum.json
│ │ ├── signal-swift.enum
│ │ │ ├── !=(_:_:).json
│ │ │ ├── bad.json
│ │ │ ├── equatable-implementations.json
│ │ │ ├── good.json
│ │ │ ├── ok.json
│ │ │ ├── outofrange.json
│ │ │ └── practicalworst.json
│ │ ├── signal-swift.property.json
│ │ └── value.json
│ │ ├── scanresult.json
│ │ └── scanresult
│ │ ├── advertisementdata.json
│ │ ├── name.json
│ │ ├── peripheral.json
│ │ └── rssi.json
├── developer-og-twitter.jpg
├── developer-og.jpg
├── documentation
│ └── ios_ble_library
│ │ ├── advertisementdata
│ │ ├── !=(_:_:)
│ │ │ └── index.html
│ │ ├── ==(_:_:)
│ │ │ └── index.html
│ │ ├── advertisedid()
│ │ │ └── index.html
│ │ ├── connectablemock
│ │ │ └── index.html
│ │ ├── equatable-implementations
│ │ │ └── index.html
│ │ ├── hash(into:)
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init()
│ │ │ └── index.html
│ │ ├── init(_:)
│ │ │ └── index.html
│ │ ├── isconnectable
│ │ │ └── index.html
│ │ ├── localname
│ │ │ └── index.html
│ │ ├── manufacturerdata
│ │ │ └── index.html
│ │ ├── overflowserviceuuids
│ │ │ └── index.html
│ │ ├── rawdata
│ │ │ └── index.html
│ │ ├── servicedata
│ │ │ └── index.html
│ │ ├── serviceuuids
│ │ │ └── index.html
│ │ ├── solicitedserviceuuids
│ │ │ └── index.html
│ │ ├── txpowerlevel
│ │ │ └── index.html
│ │ └── unconnectablemock
│ │ │ └── index.html
│ │ ├── asynccharacteristicdata
│ │ ├── allsatisfy(_:)
│ │ │ └── index.html
│ │ ├── asyncsequence-implementations
│ │ │ └── index.html
│ │ ├── compactmap(_:)-3l977
│ │ │ └── index.html
│ │ ├── compactmap(_:)-8kqp1
│ │ │ └── index.html
│ │ ├── contains(_:)
│ │ │ └── index.html
│ │ ├── contains(where:)
│ │ │ └── index.html
│ │ ├── drop(while:)
│ │ │ └── index.html
│ │ ├── dropfirst(_:)
│ │ │ └── index.html
│ │ ├── element
│ │ │ └── index.html
│ │ ├── filter(_:)
│ │ │ └── index.html
│ │ ├── first(where:)
│ │ │ └── index.html
│ │ ├── flatmap(_:)-50ncz
│ │ │ └── index.html
│ │ ├── flatmap(_:)-8auld
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── makeasynciterator()
│ │ │ └── index.html
│ │ ├── map(_:)-70277
│ │ │ └── index.html
│ │ ├── map(_:)-8x8iq
│ │ │ └── index.html
│ │ ├── max(by:)
│ │ │ └── index.html
│ │ ├── min(by:)
│ │ │ └── index.html
│ │ ├── next()
│ │ │ └── index.html
│ │ ├── prefix(_:)
│ │ │ └── index.html
│ │ ├── prefix(while:)
│ │ │ └── index.html
│ │ ├── reduce(_:_:)
│ │ │ └── index.html
│ │ └── reduce(into:_:)
│ │ │ └── index.html
│ │ ├── asyncpublisher
│ │ ├── allsatisfy(_:)
│ │ │ └── index.html
│ │ ├── asynciterator
│ │ │ └── index.html
│ │ ├── asyncsequence-implementations
│ │ │ └── index.html
│ │ ├── characters
│ │ │ └── index.html
│ │ ├── compactmap(_:)-2ziwe
│ │ │ └── index.html
│ │ ├── compactmap(_:)-746g3
│ │ │ └── index.html
│ │ ├── contains(_:)
│ │ │ └── index.html
│ │ ├── contains(where:)
│ │ │ └── index.html
│ │ ├── drop(while:)
│ │ │ └── index.html
│ │ ├── dropfirst(_:)
│ │ │ └── index.html
│ │ ├── element
│ │ │ └── index.html
│ │ ├── filter(_:)
│ │ │ └── index.html
│ │ ├── first(where:)
│ │ │ └── index.html
│ │ ├── flatmap(_:)-648jy
│ │ │ └── index.html
│ │ ├── flatmap(_:)-9aeej
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init(_:)
│ │ │ └── index.html
│ │ ├── iterator
│ │ │ ├── element
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ └── next()
│ │ │ │ └── index.html
│ │ ├── lines
│ │ │ └── index.html
│ │ ├── makeasynciterator()
│ │ │ └── index.html
│ │ ├── map(_:)-155e0
│ │ │ └── index.html
│ │ ├── map(_:)-1cjy6
│ │ │ └── index.html
│ │ ├── max()
│ │ │ └── index.html
│ │ ├── max(by:)
│ │ │ └── index.html
│ │ ├── min()
│ │ │ └── index.html
│ │ ├── min(by:)
│ │ │ └── index.html
│ │ ├── prefix(_:)
│ │ │ └── index.html
│ │ ├── prefix(while:)
│ │ │ └── index.html
│ │ ├── reduce(_:_:)
│ │ │ └── index.html
│ │ ├── reduce(into:_:)
│ │ │ └── index.html
│ │ └── unicodescalars
│ │ │ └── index.html
│ │ ├── asyncstreamvalue
│ │ └── index.html
│ │ ├── asyncthrowingpublisher
│ │ ├── allsatisfy(_:)
│ │ │ └── index.html
│ │ ├── asynciterator
│ │ │ └── index.html
│ │ ├── asyncsequence-implementations
│ │ │ └── index.html
│ │ ├── characters
│ │ │ └── index.html
│ │ ├── compactmap(_:)-8it2j
│ │ │ └── index.html
│ │ ├── compactmap(_:)-zh70
│ │ │ └── index.html
│ │ ├── contains(_:)
│ │ │ └── index.html
│ │ ├── contains(where:)
│ │ │ └── index.html
│ │ ├── drop(while:)
│ │ │ └── index.html
│ │ ├── dropfirst(_:)
│ │ │ └── index.html
│ │ ├── element
│ │ │ └── index.html
│ │ ├── filter(_:)
│ │ │ └── index.html
│ │ ├── first(where:)
│ │ │ └── index.html
│ │ ├── flatmap(_:)-36cmu
│ │ │ └── index.html
│ │ ├── flatmap(_:)-39oak
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init(_:)
│ │ │ └── index.html
│ │ ├── iterator
│ │ │ ├── element
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ └── next()
│ │ │ │ └── index.html
│ │ ├── lines
│ │ │ └── index.html
│ │ ├── makeasynciterator()
│ │ │ └── index.html
│ │ ├── map(_:)-2gzj6
│ │ │ └── index.html
│ │ ├── map(_:)-3wux3
│ │ │ └── index.html
│ │ ├── max()
│ │ │ └── index.html
│ │ ├── max(by:)
│ │ │ └── index.html
│ │ ├── min()
│ │ │ └── index.html
│ │ ├── min(by:)
│ │ │ └── index.html
│ │ ├── prefix(_:)
│ │ │ └── index.html
│ │ ├── prefix(while:)
│ │ │ └── index.html
│ │ ├── reduce(_:_:)
│ │ │ └── index.html
│ │ ├── reduce(into:_:)
│ │ │ └── index.html
│ │ └── unicodescalars
│ │ │ └── index.html
│ │ ├── centralmanager
│ │ ├── cancelperipheralconnection(_:)
│ │ │ └── index.html
│ │ ├── centralmanager
│ │ │ └── index.html
│ │ ├── centralmanagerdelegate
│ │ │ └── index.html
│ │ ├── connect(_:options:)
│ │ │ └── index.html
│ │ ├── connectedperipheralchannel
│ │ │ └── index.html
│ │ ├── disconnectedperipheralschannel
│ │ │ └── index.html
│ │ ├── err
│ │ │ ├── badstate(_:)
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ ├── localizeddescription
│ │ │ │ └── index.html
│ │ │ ├── unknownerror
│ │ │ │ └── index.html
│ │ │ └── wrongmanager
│ │ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init(centralmanager:)
│ │ │ └── index.html
│ │ ├── init(centralmanagerdelegate:queue:options:)
│ │ │ └── index.html
│ │ ├── isscanningchannel
│ │ │ └── index.html
│ │ ├── retrieveconnectedperipherals(withservices:)
│ │ │ └── index.html
│ │ ├── retrieveperipherals(withidentifiers:)
│ │ │ └── index.html
│ │ ├── scanforperipherals(withservices:)
│ │ │ └── index.html
│ │ ├── scanresultschannel
│ │ │ └── index.html
│ │ ├── statechannel
│ │ │ └── index.html
│ │ └── stopscan()
│ │ │ └── index.html
│ │ ├── combine
│ │ ├── index.html
│ │ └── publisher
│ │ │ ├── index.html
│ │ │ └── value
│ │ │ └── index.html
│ │ ├── corebluetooth
│ │ ├── cbmanagerstate
│ │ │ ├── customdebugstringconvertible-implementations
│ │ │ │ └── index.html
│ │ │ ├── customstringconvertible-implementations
│ │ │ │ └── index.html
│ │ │ ├── debugdescription
│ │ │ │ └── index.html
│ │ │ ├── description
│ │ │ │ └── index.html
│ │ │ └── index.html
│ │ └── index.html
│ │ ├── foundation
│ │ ├── data
│ │ │ ├── hexencodedstring(options:separator:)
│ │ │ │ └── index.html
│ │ │ ├── hexencodingoptions
│ │ │ │ ├── !=(_:_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── contains(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── equatable-implementations
│ │ │ │ │ └── index.html
│ │ │ │ ├── formintersection(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── formsymmetricdifference(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── formunion(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── index.html
│ │ │ │ ├── init()
│ │ │ │ │ └── index.html
│ │ │ │ ├── init(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── init(arrayliteral:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── init(rawvalue:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── insert(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── intersection(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── isdisjoint(with:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── isempty
│ │ │ │ │ └── index.html
│ │ │ │ ├── isstrictsubset(of:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── isstrictsuperset(of:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── issubset(of:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── issuperset(of:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── optionset-implementations
│ │ │ │ │ └── index.html
│ │ │ │ ├── rawvalue
│ │ │ │ │ └── index.html
│ │ │ │ ├── remove(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── reverseendianness
│ │ │ │ │ └── index.html
│ │ │ │ ├── setalgebra-implementations
│ │ │ │ │ └── index.html
│ │ │ │ ├── subtract(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── subtracting(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── symmetricdifference(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── union(_:)
│ │ │ │ │ └── index.html
│ │ │ │ ├── update(with:)
│ │ │ │ │ └── index.html
│ │ │ │ └── uppercase
│ │ │ │ │ └── index.html
│ │ │ └── index.html
│ │ └── index.html
│ │ ├── index.html
│ │ ├── peripheral
│ │ ├── discovercharacteristics(_:for:)
│ │ │ └── index.html
│ │ ├── discoverdescriptors(for:)
│ │ │ └── index.html
│ │ ├── discoverincludedservices(_:for:)
│ │ │ └── index.html
│ │ ├── discoverservices(serviceuuids:)
│ │ │ └── index.html
│ │ ├── err
│ │ │ ├── !=(_:_:)
│ │ │ │ └── index.html
│ │ │ ├── baddelegate
│ │ │ │ └── index.html
│ │ │ ├── equatable-implementations
│ │ │ │ └── index.html
│ │ │ ├── error-implementations
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ └── localizeddescription
│ │ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init(peripheral:delegate:)
│ │ │ └── index.html
│ │ ├── listenvalues(for:)
│ │ │ └── index.html
│ │ ├── name
│ │ │ └── index.html
│ │ ├── peripheral
│ │ │ └── index.html
│ │ ├── peripheraldelegate
│ │ │ └── index.html
│ │ ├── peripheralstatechannel
│ │ │ └── index.html
│ │ ├── readrssi()
│ │ │ └── index.html
│ │ ├── readvalue(for:)-880d
│ │ │ └── index.html
│ │ ├── readvalue(for:)-8wwmb
│ │ │ └── index.html
│ │ ├── services
│ │ │ └── index.html
│ │ ├── setnotifyvalue(_:for:)
│ │ │ └── index.html
│ │ ├── writevalue(_:for:)
│ │ │ └── index.html
│ │ ├── writevaluewithoutresponse(_:for:)
│ │ │ └── index.html
│ │ └── writevaluewithresponse(_:for:)
│ │ │ └── index.html
│ │ ├── reactivecentralmanagerdelegate
│ │ ├── centralmanager(_:didconnect:)
│ │ │ └── index.html
│ │ ├── centralmanager(_:diddisconnectperipheral:error:)
│ │ │ └── index.html
│ │ ├── centralmanager(_:diddiscover:advertisementdata:rssi:)
│ │ │ └── index.html
│ │ ├── centralmanager(_:didfailtoconnect:error:)
│ │ │ └── index.html
│ │ ├── centralmanagerdidupdatestate(_:)
│ │ │ └── index.html
│ │ └── index.html
│ │ ├── reactiveperipheraldelegate
│ │ ├── index.html
│ │ ├── peripheral(_:diddiscovercharacteristicsfor:error:)
│ │ │ └── index.html
│ │ ├── peripheral(_:diddiscoverdescriptorsfor:error:)
│ │ │ └── index.html
│ │ ├── peripheral(_:diddiscoverservices:)
│ │ │ └── index.html
│ │ ├── peripheral(_:didmodifyservices:)
│ │ │ └── index.html
│ │ ├── peripheral(_:didreadrssi:error:)
│ │ │ └── index.html
│ │ ├── peripheral(_:didupdatenotificationstatefor:error:)
│ │ │ └── index.html
│ │ ├── peripheral(_:didupdatevaluefor:error:)-2btq1
│ │ │ └── index.html
│ │ ├── peripheral(_:didupdatevaluefor:error:)-7o3t2
│ │ │ └── index.html
│ │ ├── peripheral(_:didwritevaluefor:error:)-29s4r
│ │ │ └── index.html
│ │ ├── peripheral(_:didwritevaluefor:error:)-2ei85
│ │ │ └── index.html
│ │ ├── peripheraldidupdatename(_:)
│ │ │ └── index.html
│ │ └── peripheralisready(tosendwritewithoutresponse:)
│ │ │ └── index.html
│ │ ├── rssi
│ │ ├── !=(_:_:)
│ │ │ └── index.html
│ │ ├── bad
│ │ │ └── index.html
│ │ ├── equatable-implementations
│ │ │ └── index.html
│ │ ├── good
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── init(integerliteral:)
│ │ │ └── index.html
│ │ ├── integerliteraltype
│ │ │ └── index.html
│ │ ├── ok
│ │ │ └── index.html
│ │ ├── outofrange
│ │ │ └── index.html
│ │ ├── practicalworst
│ │ │ └── index.html
│ │ ├── signal-swift.enum
│ │ │ ├── !=(_:_:)
│ │ │ │ └── index.html
│ │ │ ├── bad
│ │ │ │ └── index.html
│ │ │ ├── equatable-implementations
│ │ │ │ └── index.html
│ │ │ ├── good
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ ├── ok
│ │ │ │ └── index.html
│ │ │ ├── outofrange
│ │ │ │ └── index.html
│ │ │ └── practicalworst
│ │ │ │ └── index.html
│ │ ├── signal-swift.property
│ │ │ └── index.html
│ │ └── value
│ │ │ └── index.html
│ │ └── scanresult
│ │ ├── advertisementdata
│ │ └── index.html
│ │ ├── index.html
│ │ ├── name
│ │ └── index.html
│ │ ├── peripheral
│ │ └── index.html
│ │ └── rssi
│ │ └── index.html
├── favicon.ico
├── favicon.svg
├── img
│ ├── added-icon.d6f7e47d.svg
│ ├── deprecated-icon.015b4f17.svg
│ ├── modified-icon.f496e73d.svg
│ └── no-image@2x.df2a0a50.png
├── index.html
├── index
│ └── index.json
├── js
│ ├── chunk-2d0d3105.cd72cc8e.js
│ ├── chunk-c0335d80.76a68cc5.js
│ ├── chunk-vendors.ba2dd0cb.js
│ ├── documentation-topic.57e91f8a.js
│ ├── documentation-topic~topic.1679ec90.js
│ ├── documentation-topic~topic~tutorials-overview.90c61522.js
│ ├── highlight-js-bash.1b52852f.js
│ ├── highlight-js-c.d1db3f17.js
│ ├── highlight-js-cpp.eaddddbe.js
│ ├── highlight-js-css.75eab1fe.js
│ ├── highlight-js-custom-markdown.7cffc4b3.js
│ ├── highlight-js-custom-swift.5cda5c20.js
│ ├── highlight-js-diff.62d66733.js
│ ├── highlight-js-http.163e45b6.js
│ ├── highlight-js-java.8326d9d8.js
│ ├── highlight-js-javascript.acb8a8eb.js
│ ├── highlight-js-json.471128d2.js
│ ├── highlight-js-llvm.6100b125.js
│ ├── highlight-js-markdown.90077643.js
│ ├── highlight-js-objectivec.bcdf5156.js
│ ├── highlight-js-perl.757d7b6f.js
│ ├── highlight-js-php.cc8d6c27.js
│ ├── highlight-js-python.c214ed92.js
│ ├── highlight-js-ruby.f889d392.js
│ ├── highlight-js-scss.62ee18da.js
│ ├── highlight-js-shell.dd7f411f.js
│ ├── highlight-js-swift.84f3e88c.js
│ ├── highlight-js-xml.9c3688c7.js
│ ├── index.e8a5d294.js
│ ├── topic.8cd0c0c4.js
│ └── tutorials-overview.2a32cd6f.js
└── metadata.json
├── format.swift-format
└── res
└── Screenshot-1.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 | .idea/
11 | .vscode/
12 |
13 | __pycache__
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
16 |
17 |
18 |
24 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
51 |
52 |
58 |
59 |
65 |
66 |
67 |
68 |
70 |
71 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'activesupport', '~> 7.0', '>= 7.0.8'
4 | gem 'cocoapods'
5 |
--------------------------------------------------------------------------------
/IOS-BLE-Library-Mock.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'IOS-BLE-Library-Mock'
3 | s.swift_version = '5.0'
4 | s.version = ENV['LIB_VERSION']
5 | s.summary = 'Extension for standard CoreBluetooth framework that is based on Combine and brings Reactive Approach. This version of the library uses CoreBluetoothMock for testing and running on simulator. But it can be usen on real devices and in production as well.'
6 | s.homepage = 'https://github.com/NordicSemiconductor/IOS-BLE-Library'
7 | s.license = { :type => 'BSD-3-Clause', :file => 'LICENSE' }
8 | s.author = { 'Nordic Semiconductor ASA' => 'mag@nordicsemi.no' }
9 | s.source = { :git => 'https://github.com/NordicSemiconductor/IOS-BLE-Library.git', :tag => s.version.to_s }
10 | s.platforms = { :ios => '13.0', :osx => '12' }
11 | s.source_files = 'Sources/iOS-BLE-Library-Mock/**/*'
12 |
13 | s.dependency 'CoreBluetoothMock', '~> 0.17.0'
14 | end
--------------------------------------------------------------------------------
/IOS-BLE-Library.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'IOS-BLE-Library'
3 | s.swift_version = '5.0'
4 | s.version = ENV['LIB_VERSION']
5 | s.summary = 'Extension for standard CoreBluetooth framework that is based on Combine and brings Reactive Approach.'
6 | s.homepage = 'https://github.com/NordicSemiconductor/IOS-BLE-Library'
7 | s.license = { :type => 'BSD-3-Clause', :file => 'LICENSE' }
8 | s.author = { 'Nordic Semiconductor ASA' => 'mag@nordicsemi.no' }
9 | s.source = { :git => 'https://github.com/NordicSemiconductor/IOS-BLE-Library.git', :tag => s.version.to_s }
10 | s.platforms = { :ios => '13.0', :osx => '12' }
11 | s.source_files = 'Sources/iOS-BLE-Library/**/*'
12 | end
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2023, Nordic Semiconductor
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "corebluetoothmock-collection",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/NickKibish/CoreBluetoothMock-Collection.git",
7 | "state" : {
8 | "revision" : "91fc016e9345f37d0b4539f9fc6c270d6d5d6f75",
9 | "version" : "1.0.0"
10 | }
11 | },
12 | {
13 | "identity" : "ios-bluetooth-numbers-database",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/NickKibish/iOS-Bluetooth-Numbers-Database.git",
16 | "state" : {
17 | "revision" : "40ae8d7050d16fc2603c314dd560a7992c35f56f",
18 | "version" : "1.0.0"
19 | }
20 | },
21 | {
22 | "identity" : "ios-corebluetooth-mock",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git",
25 | "state" : {
26 | "revision" : "194863075520d244f710fddd66f4f111c21661ef",
27 | "version" : "0.17.0"
28 | }
29 | },
30 | {
31 | "identity" : "swift-docc-plugin",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/apple/swift-docc-plugin",
34 | "state" : {
35 | "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
36 | "version" : "1.3.0"
37 | }
38 | },
39 | {
40 | "identity" : "swift-docc-symbolkit",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/apple/swift-docc-symbolkit",
43 | "state" : {
44 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
45 | "version" : "1.0.0"
46 | }
47 | }
48 | ],
49 | "version" : 2
50 | }
51 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.6
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "iOS-BLE-Library",
6 | platforms: [
7 | .iOS(.v13),
8 | .macOS(.v10_15),
9 | .watchOS(.v6)
10 | ],
11 | products: [
12 | .library(name: "iOS-BLE-Library", targets: ["iOS-BLE-Library"]),
13 | .library(name: "iOS-BLE-Library-Mock", targets: ["iOS-BLE-Library-Mock"]),
14 | ],
15 | dependencies: [
16 | .package(url: "https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock.git", from: "0.17.0"),
17 | .package(url: "https://github.com/NickKibish/CoreBluetoothMock-Collection.git", from: "1.0.0"),
18 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
19 | ],
20 | targets: [
21 | .target(name: "iOS-BLE-Library"),
22 | .target(name: "iOS-BLE-Library-Mock", dependencies: ["iOS-BLE-Library", .product(name: "CoreBluetoothMock", package: "IOS-CoreBluetooth-Mock")]),
23 | .testTarget(name: "iOS-BLE-LibraryTests", dependencies: ["iOS-BLE-Library-Mock", .product(name: "CoreBluetoothMock-Collection", package: "CoreBluetoothMock-Collection")]),
24 | ]
25 | )
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # iOS-BLE-Library
4 |
5 | This library is a wrapper around the CoreBluetooth framework which provides a modern async API based on Combine Framework.
6 |
7 | # Library Versions
8 |
9 | This package contains two versions of the library:
10 | * `iOS-BLE-Library` - the library that uses the native CoreBluetooth API.
11 | * `iOS-BLE-Library-Mock` - the library that uses the [CoreBluetoothMock](https://github.com/NordicSemiconductor/IOS-CoreBluetooth-Mock) API.
12 |
13 | # Installation
14 | ## Swift Package Manager
15 | The library can be installed using Swift Package Manager.
16 |
17 | You can choose between two versions of the library:
18 | 
19 |
20 | Or you can add it as a dependency to your library:
21 | ```swift
22 |
23 | let package = Package(
24 | /// . . .
25 | dependencies: [
26 | // Set the link to the library and choose the version
27 | .package(url: "https://github.com/NordicSemiconductor/IOS-BLE-Library.git", from: "0.3.1"),
28 | ],
29 | targets: [
30 | .target(
31 | name: "MyLib",
32 | dependencies: [
33 | // You can use "native" CoreBluetooth API
34 | .product(name: "iOS-BLE-Library", package: "iOS-BLE-Library")
35 | ]
36 | ),
37 | .testTarget(
38 | name: "MyLibTests",
39 | dependencies: [
40 | "MyLib",
41 | // Or you can use the CoreBluetoothMock API
42 | .product(name: "iOS-BLE-Library-Mock", package: "iOS-BLE-Library")
43 | ]
44 | ),
45 | ]
46 | )
47 | ```
48 |
49 | ## CocoaPods
50 | The library can be installed using CocoaPods.
51 |
52 | Add the following line to your Podfile:
53 | ```ruby
54 | pod 'IOS-BLE-Library', '~> 0.3.2'
55 | ```
56 |
57 | or
58 | ```ruby
59 | pod 'IOS-BLE-Library-Mock', '~> 0.3.2'
60 | ```
61 |
62 | # Documentation & Examples
63 | Please check the [Documentation Page](https://nordicsemiconductor.github.io/IOS-BLE-Library/documentation/ios_ble_library/) to start using the library.
64 |
65 | Also you can check [iOS-nRF-Toolbox](https://github.com/NordicSemiconductor/IOS-nRF-Toolbox/tree/develop) to find more examples.
66 |
67 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/CentralManager/Model/ScanResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 19/04/2023.
6 | //
7 |
8 | import CoreBluetoothMock
9 | import Foundation
10 |
11 | public struct ScanResult {
12 | public let peripheral: CBPeripheral
13 | public let rssi: RSSI
14 | public let advertisementData: AdvertisementData
15 |
16 | init(peripheral: CBPeripheral, rssi: NSNumber, advertisementData: [String: Any]) {
17 | self.peripheral = peripheral
18 | self.rssi = RSSI(integerLiteral: rssi.intValue)
19 | self.advertisementData = AdvertisementData(advertisementData)
20 | }
21 |
22 | public var name: String? {
23 | peripheral.name ?? advertisementData.localName
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/CentralManager/ReactiveCentralManagerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 18/04/2023.
6 | //
7 |
8 | import Combine
9 | import CoreBluetoothMock
10 | import Foundation
11 |
12 | // MARK: - ReactiveCentralManagerDelegate
13 |
14 | open class ReactiveCentralManagerDelegate: NSObject, CBCentralManagerDelegate {
15 | enum BluetoothError: Error {
16 | case failedToConnect
17 | }
18 |
19 | let stateSubject = CurrentValueSubject(.unknown)
20 | let scanResultSubject = PassthroughSubject()
21 | let connectedPeripheralSubject = PassthroughSubject<(CBPeripheral, Error?), Never>()
22 | let disconnectedPeripheralsSubject = PassthroughSubject<(CBPeripheral, Error?), Never>()
23 | let connectionEventSubject = PassthroughSubject<(CBPeripheral, CBConnectionEvent), Never>()
24 |
25 | // MARK: Monitoring Connections with Peripherals
26 | open func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
27 | connectedPeripheralSubject.send((peripheral, nil))
28 | }
29 |
30 | open func centralManager(
31 | _ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral,
32 | error: Error?
33 | ) {
34 | disconnectedPeripheralsSubject.send((peripheral, error))
35 | }
36 |
37 | open func centralManager(
38 | _ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral,
39 | error: Error?
40 | ) {
41 | connectedPeripheralSubject.send((peripheral, error))
42 | }
43 |
44 | #if !os(macOS)
45 | open func centralManager(
46 | _ central: CBCentralManager,
47 | connectionEventDidOccur event: CBConnectionEvent,
48 | for peripheral: CBPeripheral
49 | ) {
50 | connectionEventSubject.send((peripheral, event))
51 | }
52 | #endif
53 |
54 | // MARK: Discovering and Retrieving Peripherals
55 |
56 | open func centralManager(
57 | _ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
58 | advertisementData: [String: Any], rssi RSSI: NSNumber
59 | ) {
60 | let scanResult = ScanResult(
61 | peripheral: peripheral,
62 | rssi: RSSI,
63 | advertisementData: advertisementData
64 | )
65 | scanResultSubject.send(scanResult)
66 | }
67 |
68 | // MARK: Monitoring the Central Manager’s State
69 |
70 | open func centralManagerDidUpdateState(_ central: CBCentralManager) {
71 | stateSubject.send(central.state)
72 | }
73 |
74 | // MARK: Monitoring the Central Manager’s Authorization
75 | #if !os(macOS)
76 | public func centralManager(
77 | _ central: CBCentralManager,
78 | didUpdateANCSAuthorizationFor peripheral: CBPeripheral
79 | ) {
80 | unimplementedError()
81 | }
82 | #endif
83 |
84 | // MARK: Instance Methods
85 | // BETA
86 | // func centralManager(CBCentralManager, didDisconnectPeripheral: CBPeripheral, timestamp: CFAbsoluteTime, isReconnecting: Bool, error: Error?)
87 | }
88 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md:
--------------------------------------------------------------------------------
1 | # ``iOS_BLE_Library/CentralManager``
2 |
3 | ### Create a Central Manager
4 |
5 | ``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside.
6 |
7 | The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:options:)``, or an existing instance can be passed using ``init(centralManager:)``.
8 |
9 | If you pass a central manager inside ``init(centralManager:)``, it should already have a delegate set. The delegate should be an instance of ``ReactiveCentralManagerDelegate``; otherwise, an error will be thrown.
10 |
11 | ### Channels
12 |
13 | Channels are used to pass through data from the `CBCentralManagerDelegate` methods.
14 | You can consider them as a reactive version of the `CBCentralManagerDelegate` methods.
15 |
16 | In most cases, you will not need to use them directly, as `centralManager`'s methods return proper publishers. However, if you need to handle the data differently (e.g., log all the events), you can subscribe to the channels directly.
17 |
18 | All channels have `Never` as their failure type because they never fail. Some channels, like `CentralManager/connectedPeripheralChannel` or `CentralManager/disconnectedPeripheralsChannel`, send tuples with the peripheral and the error, allowing you to handle errors if needed. Despite this, the failure type remains `Never`, so it will not complete even if an error occurs during the connection or disconnection of the peripheral.
19 |
20 | ```swift
21 | centralManager.connectedPeripheralChannel
22 | .sink { peripheral, error in
23 | if let error = error {
24 | print("Error: \(error)")
25 | } else {
26 | print("New peripheral connected: \(peripheral)"
27 | }
28 | }
29 | .store(in: &cancellables)
30 | ```
31 |
32 | ## Topics
33 |
34 | ### Initializers
35 |
36 | - ``init(centralManagerDelegate:queue:)``
37 | - ``init(centralManager:)``
38 |
39 | ### Instance Properties
40 |
41 | - ``centralManager``
42 | - ``centralManagerDelegate``
43 |
44 | ### Scan
45 |
46 | - ``scanForPeripherals(withServices:)``
47 | - ``stopScan()``
48 | - ``retrievePeripherals(withIdentifiers:)``
49 |
50 | ### Connection
51 |
52 | - ``connect(_:options:)``
53 | - ``cancelPeripheralConnection(_:)``
54 | - ``retrieveConnectedPeripherals(withServices:)``
55 |
56 | ### Channels
57 |
58 | - ``stateChannel``
59 | - ``isScanningChannel``
60 | - ``scanResultsChannel``
61 | - ``connectedPeripheralChannel``
62 | - ``disconnectedPeripheralsChannel``
63 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md:
--------------------------------------------------------------------------------
1 | # ``iOS_BLE_Library/CentralManager/connect(_:options:)``
2 |
3 | ## See Also
4 |
5 | - ``CentralManager/connectedPeripheralChannel``
6 | - ``CentralManager/disconnectedPeripheralsChannel``
7 |
8 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/ReactiveCentralManagerDelegate.md:
--------------------------------------------------------------------------------
1 | # ``iOS_BLE_Library/ReactiveCentralManagerDelegate``
2 |
3 | Implementation of the `CBCentralManagerDelegate`.
4 |
5 | `ReactiveCentralManagerDelegate` is a class that implements the `CBCentralManagerDelegate` and is an essential part of the ``CentralManager`` class.
6 |
7 | It brings a reactive programming approach, utilizing Combine publishers to seamlessly handle Bluetooth events and data.
8 | This class allows to monitor and respond to events such as peripheral connection, disconnection, and scanning for peripherals.
9 |
10 | It has all needed publishers that are used for handling Bluetooth events and data.
11 |
12 | ## Override
13 |
14 | It's possible to override the default implementation of the `ReactiveCentralManagerDelegate` by creating a new class that inherits from `ReactiveCentralManagerDelegate` and overriding the needed methods.
15 |
16 | However, it's important to call the `super` implementation of the method, otherwise it will break the `CentralManager` functionality.
17 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Documentation.docc/Documentation.md:
--------------------------------------------------------------------------------
1 | # ``iOS_BLE_Library``
2 |
3 | This library is a wrapper around the CoreBluetooth framework which provides a modern async API based on Combine Framework.
4 |
5 | The library has been designed to have a simple API similar to the one provided by the CoreBluetooth framework.
6 | So if you are familiar with the CoreBluetooth framework, you will be able to use this library without any problem.
7 |
8 | ## Topics
9 |
10 | ### Central Manager
11 | - ``CentralManager``
12 | - ``ReactiveCentralManagerDelegate``
13 |
14 | ### Peripheral
15 | - ``Peripheral``
16 | - ``ReactivePeripheralDelegate``
17 |
18 | ### Essentials
19 | - ``iOS_BLE_Library/Combine/Publishers/BluetoothPublisher``
20 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Documentation.docc/Peripheral/Peripheral.md:
--------------------------------------------------------------------------------
1 | ``iOS_BLE_Library/Peripheral``
2 |
3 | ### Create a Peripheral
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/AsyncCharacteristicData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncCharacteristicData.swift
3 | // iOS-BLE-Library
4 | //
5 | // Created by Dinesh Harjani on 23/8/22.
6 | //
7 |
8 | import CoreBluetoothMock
9 | import Foundation
10 |
11 | public typealias AsyncStreamValue = (characteristic: CBCharacteristic, data: Data?)
12 |
13 | public struct AsyncCharacteristicData: AsyncSequence, AsyncIteratorProtocol {
14 | public typealias Element = Data?
15 |
16 | let serviceUUID: String
17 | let characteristicUUID: String
18 | let stream: AsyncThrowingStream
19 |
20 | public func makeAsyncIterator() -> AsyncCharacteristicData {
21 | self
22 | }
23 |
24 | mutating public func next() async throws -> Element? {
25 | for try await newValue in stream {
26 | guard newValue.characteristic.uuid.uuidString == characteristicUUID,
27 | let service = newValue.characteristic.service,
28 | service.uuid.uuidString == serviceUUID
29 | else { continue }
30 | return newValue.data
31 | }
32 | return nil
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/CBManagerState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CBManagerState.swift
3 | //
4 | //
5 | // Created by Dinesh Harjani on 23/8/22.
6 | //
7 |
8 | import CoreBluetoothMock
9 | import Foundation
10 |
11 | // MARK: - CBManagerState
12 | #if hasFeature(RetroactiveAttribute)
13 | @available(iOS 10.0, *)
14 | @available(macOS 10.13, *)
15 | extension CBManagerState: @retroactive CustomDebugStringConvertible, @retroactive
16 | CustomStringConvertible
17 | {
18 |
19 | public var debugDescription: String {
20 | return description
21 | }
22 |
23 | public var description: String {
24 | switch self {
25 | case .poweredOff:
26 | return "poweredOff"
27 | case .poweredOn:
28 | return "poweredOn"
29 | case .resetting:
30 | return "resetting"
31 | case .unauthorized:
32 | return "unauthorized"
33 | case .unsupported:
34 | return "unsupported"
35 | default:
36 | return "unknown"
37 | }
38 | }
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/Extensions/CBManagerState+Ext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 19/04/2023.
6 | //
7 |
8 | import CoreBluetoothMock
9 | import Foundation
10 |
11 | extension CBManagerState {
12 |
13 | var ready: Bool? {
14 | switch self {
15 | case .poweredOn:
16 | return true
17 | case .unknown, .resetting:
18 | return nil
19 | case .poweredOff, .unauthorized, .unsupported:
20 | return false
21 | default:
22 | return false
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/Extensions/Data+Ext.swift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data.swift
3 | //
4 | //
5 | // Created by Dinesh Harjani on 18/8/22.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Data Extension
11 |
12 | extension Data {
13 |
14 | // MARK: HexEncodingOptions
15 |
16 | struct HexEncodingOptions: OptionSet {
17 |
18 | public static let upperCase = HexEncodingOptions(rawValue: 1)
19 | public static let reverseEndianness = HexEncodingOptions(rawValue: 2)
20 |
21 | public let rawValue: Int
22 |
23 | public init(rawValue: Int) {
24 | self.rawValue = rawValue << 0
25 | }
26 | }
27 |
28 | // MARK: hexEncodedString
29 |
30 | func hexEncodedString(options: HexEncodingOptions = [], separator: String = "")
31 | -> String
32 | {
33 | let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
34 |
35 | var bytes = self
36 | if options.contains(.reverseEndianness) {
37 | bytes.reverse()
38 | }
39 | return
40 | bytes
41 | .map { String(format: format, $0) }
42 | .joined(separator: separator)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 20/01/2023.
6 | //
7 |
8 | import Foundation
9 | import os
10 |
11 | @available(iOS 14.0, macOS 11, watchOS 7.0, *)
12 | private struct Loggers {
13 | static var loggers: [UUID: Logger] = [:]
14 | }
15 |
16 | struct L {
17 | @inline(__always)
18 | static let enabled: Bool = false
19 |
20 | let subsystem: String
21 | let category: String
22 |
23 | private let shouldLog: Bool
24 |
25 | private let id = UUID()
26 |
27 | init(
28 | subsystem: String = "com.nordicsemi.ios_ble_library", category: String,
29 | enabled: Bool = Self.enabled
30 | ) {
31 | self.subsystem = subsystem
32 | self.category = category
33 | self.shouldLog = enabled
34 |
35 | if #available(iOS 14, macOS 11, watchOS 7, *) {
36 | Loggers.loggers[self.id] = Logger(subsystem: subsystem, category: category)
37 | }
38 | }
39 |
40 | func i(_ msg: String) {
41 | #if DEBUG
42 | if !shouldLog { return }
43 |
44 | if #available(iOS 14, macOS 11, watchOS 7, *) {
45 | Loggers.loggers[id]?.info("\(msg)")
46 | } else {
47 | os_log("%@", type: .info, msg)
48 | }
49 |
50 | #endif
51 | }
52 |
53 | func d(_ msg: String) {
54 | #if DEBUG
55 | if !shouldLog { return }
56 | if #available(iOS 14, macOS 11, watchOS 7, *) {
57 | Loggers.loggers[id]?.debug("\(msg)")
58 | } else {
59 | os_log("%@", type: .debug, msg)
60 | }
61 | #endif
62 | }
63 |
64 | func e(_ msg: String) {
65 | #if DEBUG
66 | if !shouldLog { return }
67 | if #available(iOS 14, macOS 11, watchOS 7, *) {
68 | Loggers.loggers[id]?.error("\(msg)")
69 | } else {
70 | os_log("%@", type: .error, msg)
71 | }
72 | #endif
73 | }
74 |
75 | func f(_ msg: String) {
76 | #if DEBUG
77 | if !shouldLog { return }
78 | if #available(iOS 14, macOS 11, watchOS 7, *) {
79 | Loggers.loggers[id]?.fault("\(msg)")
80 | } else {
81 | os_log("%@", type: .fault, msg)
82 | }
83 | #endif
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/ContinuationSubscriber.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 05/05/2023.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 |
11 | class ContinuationSubscriber: Subscriber {
12 |
13 | typealias Input = Upstream.Output
14 | typealias Failure = Upstream.Failure
15 |
16 | private let continuation: CheckedContinuation
17 | private var state: State = .waitingForSubscription
18 | private var lock = NSLock()
19 | private var subscription: Subscription?
20 |
21 | enum State {
22 | case waitingForSubscription
23 | case receivedSubscription
24 | case terminated
25 | }
26 |
27 | init(continuation: CheckedContinuation) {
28 | self.continuation = continuation
29 | }
30 |
31 | func receive(subscription: Subscription) {
32 | lock.lock()
33 | guard case .waitingForSubscription = state else {
34 | lock.unlock()
35 | return
36 | }
37 |
38 | self.state = .receivedSubscription
39 | self.subscription = subscription
40 | lock.unlock()
41 |
42 | subscription.request(.max(1))
43 | }
44 |
45 | func receive(_ input: Upstream.Output) -> Subscribers.Demand {
46 | lock.lock()
47 | guard case .receivedSubscription = state else {
48 | lock.unlock()
49 | return .none
50 | }
51 | self.state = .terminated
52 | continuation.resume(returning: input)
53 |
54 | self.subscription?.cancel()
55 | lock.unlock()
56 |
57 | return .none
58 | }
59 |
60 | func receive(completion: Subscribers.Completion) {
61 | lock.lock()
62 | guard case .receivedSubscription = state else {
63 | lock.unlock()
64 | return
65 | }
66 |
67 | self.state = .terminated
68 |
69 | switch completion {
70 | case .finished:
71 | break
72 | case .failure(let failure):
73 | continuation.resume(throwing: failure)
74 | }
75 | lock.unlock()
76 | }
77 | }
78 |
79 | extension ContinuationSubscriber {
80 |
81 | static func withCheckedContinuation(_ upstream: Upstream) async throws -> Input
82 | where Upstream.Output == Input, Upstream.Failure == Failure {
83 |
84 | try await withCheckedThrowingContinuation { c in
85 | upstream.subscribe(ContinuationSubscriber(continuation: c))
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nick Kibysh on 05/05/2023.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 |
11 | extension Publisher {
12 | func bluetooth(_ fire: @escaping () -> Void)
13 | -> Publishers.BluetoothPublisher