-
Notifications
You must be signed in to change notification settings - Fork 17
/
LittleBlueToothForTest.framework.coverage.txt
6312 lines (6285 loc) · 347 KB
/
LittleBlueToothForTest.framework.coverage.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/CustomOperator/Listen.swift:
1| |//
2| |// Listen.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 26/08/2020.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |import os.log
11| |#if TEST
12| |import CoreBluetoothMock
13| |#else
14| |import CoreBluetooth
15| |#endif
16| |
17| |
18| |// MARK: - Listen
19| |
20| |extension Publisher where Self.Failure == LittleBluetoothError {
21| |
22| | /// Returns a publisher with the `LittleBlueToothCharacteristic` where the notify command has been activated.
23| | /// After starting the listen command you should subscribe to the `listenPublisher` to be notified.
24| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
25| | /// - parameter characteristic: Characteristc you want to be notified.
26| | /// - returns: A publisher with the `LittleBlueToothCharacteristic` where the notify command has been activated.
27| | /// - important: This publisher only activate the notification on a specific characteristic, it will not send notified values.
28| | /// After starting the listen command you should subscribe to the `listenPublisher` to be notified.
29| | public func enableListen(for littleBluetooth: LittleBlueTooth,
30| 2| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<LittleBlueToothCharacteristic, LittleBluetoothError> {
31| 2|
32| 2| func enableListen<Upstream: Publisher>(upstream: Upstream,
33| 2| for littleBluetooth: LittleBlueTooth,
34| 2| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<LittleBlueToothCharacteristic, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
35| 2| return upstream
36| 2| .flatMapLatest { _ in
37| 2| littleBluetooth.enableListen(from: characteristic)
38| 2| }
39| 2| }
40| 2|
41| 2| return enableListen(upstream: self,
42| 2| for: littleBluetooth,
43| 2| from: characteristic)
44| 2| }
45| |
46| | /// Returns a shared publisher for listening to a specific characteristic.
47| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
48| | /// - parameter characteristic: Characteristc you want to be notified.
49| | /// - returns: A shared publisher that will send out values of the type defined by the generic type.
50| | /// - important: The type of the value must be conform to `Readable`
51| | public func startListen<T: Readable>(for littleBluetooth: LittleBlueTooth,
52| 1| from charact: LittleBlueToothCharacteristic) -> AnyPublisher<T, LittleBluetoothError> {
53| 1|
54| 1| func startListen<T: Readable, Upstream: Publisher>(upstream: Upstream,
55| 1| for littleBluetooth: LittleBlueTooth,
56| 1| from charact: LittleBlueToothCharacteristic) -> AnyPublisher<T, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
57| 1| return upstream
58| 1| .flatMapLatest { _ in
59| 1| littleBluetooth.startListen(from: charact)
60| 1| }
61| 1| }
62| 1|
63| 1| return startListen(upstream: self,
64| 1| for: littleBluetooth,
65| 1| from: charact)
66| 1| }
67| |
68| | /// Disable listen from a specific characteristic
69| | /// - parameter characteristic: characteristic you want to stop listen
70| | /// - returns: A publisher with that informs you about the successful or failed task
71| | public func disableListen(for littleBluetooth: LittleBlueTooth,
72| 2| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<LittleBlueToothCharacteristic, LittleBluetoothError> {
73| 2| func disableListen<Upstream: Publisher>(upstream: Upstream,
74| 2| for littleBluetooth: LittleBlueTooth,
75| 2| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<LittleBlueToothCharacteristic, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
76| 2| return upstream
77| 2| .flatMapLatest { _ in
78| 2| littleBluetooth.disableListen(from: characteristic)
79| 2| }
80| 2| }
81| 2| return disableListen(upstream: self,
82| 2| for: littleBluetooth,
83| 2| from: characteristic)
84| 2| }
85| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/CustomOperator/Log.swift:
1| |//
2| |// Log.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 29/01/21.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |
11| |extension Publisher {
12| 410| func customPrint(_ prefix: String = "", to: TextOutputStream? = nil, isEnabled: Bool = true) -> AnyPublisher<Self.Output, Self.Failure> {
13| 410| if isEnabled {
14| 410| return print(prefix, to: to).eraseToAnyPublisher()
15| 410| }
16| 0| return AnyPublisher(self)
17| 410| }
18| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/CustomOperator/ReadAndWrite.swift:
1| |//
2| |// ReadAndWrite.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 26/08/2020.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |import os.log
11| |#if TEST
12| |import CoreBluetoothMock
13| |#else
14| |import CoreBluetooth
15| |#endif
16| |
17| |extension Publisher where Self.Failure == LittleBluetoothError {
18| | // MARK: - RSSI
19| | /// Returns a publisher with the `Int`value of the RSSI.
20| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
21| | /// - returns: A publisher with the `Int` value of the RSSI..
22| 1| public func readRSSI(for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Int, LittleBluetoothError> {
23| 1|
24| 1| func readRSSI<Upstream: Publisher>(upstream: Upstream,
25| 1| for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Int, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
26| 1| return upstream
27| 1| .flatMapLatest { _ in
28| 1| littleBluetooth.readRSSI()
29| 1| }
30| 1| }
31| 1| return readRSSI(upstream: self,
32| 1| for: littleBluetooth)
33| 1| }
34| |
35| | // MARK: - Read
36| |
37| | /// Read a value from a specific charteristic
38| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
39| | /// - parameter characteristic: characteristic where you want to read
40| | /// - returns: A publisher with the value you want to read.
41| | /// - important: The type of the value must be conform to `Readable`
42| | public func read<T: Readable>(for littleBluetooth: LittleBlueTooth,
43| 3| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<T, LittleBluetoothError> {
44| 3|
45| 3| func read<T: Readable, Upstream: Publisher>(upstream: Upstream,
46| 3| for littleBluetooth: LittleBlueTooth,
47| 3| from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher<T, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
48| 3| return upstream
49| 3| .flatMapLatest { _ in
50| 3| littleBluetooth.read(from: characteristic)
51| 3| }
52| 3| }
53| 3|
54| 3| return read(upstream: self,
55| 3| for: littleBluetooth,
56| 3| from: characteristic)
57| 3| }
58| |
59| | // MARK: - Write
60| |
61| | /// Write a value to a specific charteristic
62| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
63| | /// - parameter characteristic: characteristic where you want to write
64| | /// - parameter value: The value you want to write
65| | /// - parameter response: An optional `Bool` value that will look for error after write operation
66| | /// - returns: A publisher with that informs you about eventual error
67| | /// - important: The type of the value must be conform to `Writable`
68| | public func write<T: Writable>(for littleBluetooth: LittleBlueTooth,
69| | to characteristic: LittleBlueToothCharacteristic,
70| | value: T,
71| 1| response: Bool = true) -> AnyPublisher<Void, LittleBluetoothError> {
72| 1|
73| 1| func write<T: Writable, Upstream: Publisher>(upstream: Upstream,
74| 1| for littleBluetooth: LittleBlueTooth,
75| 1| to characteristic: LittleBlueToothCharacteristic,
76| 1| value: T,
77| 1| response: Bool = true) -> AnyPublisher<Void, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
78| 1| return upstream
79| 1| .flatMapLatest { _ in
80| 1| littleBluetooth.write(to: characteristic, value: value, response: response)
81| 1| }
82| 1| }
83| 1|
84| 1| return write(upstream: self,
85| 1| for: littleBluetooth,
86| 1| to: characteristic,
87| 1| value: value,
88| 1| response: response)
89| 1| }
90| |
91| | /// Write a value to a specific charteristic and wait for a response
92| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
93| | /// - parameter characteristic: characteristic where you want to write and listen
94| | /// - parameter value: The value you want to write must conform to `Writable`
95| | /// - returns: A publisher with that post and error or the response of the write requests.
96| | /// - important: Written value must conform to `Writable`, response must conform to `Readable`
97| | public func writeAndListen<W: Writable, R: Readable>(for littleBluetooth: LittleBlueTooth,
98| | from characteristic: LittleBlueToothCharacteristic,
99| 1| value: W) -> AnyPublisher<R, LittleBluetoothError> {
100| 1| func writeAndListen<W: Writable, R: Readable, Upstream: Publisher>(upstream: Upstream,
101| 1| for littleBluetooth: LittleBlueTooth,
102| 1| from characteristic: LittleBlueToothCharacteristic,
103| 1| value: W) -> AnyPublisher<R, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
104| 1| return upstream
105| 1| .flatMapLatest { _ in
106| 1| littleBluetooth.writeAndListen(from: characteristic,
107| 1| value: value)
108| 1| }
109| 1| }
110| 1| return writeAndListen(upstream: self,
111| 1| for: littleBluetooth,
112| 1| from: characteristic,
113| 1| value: value)
114| 1| }
115| |
116| |}
117| |
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/CustomOperator/ScanAndConnection.swift:
1| |//
2| |// Connection.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 26/08/2020.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |import os.log
11| |#if TEST
12| |import CoreBluetoothMock
13| |#else
14| |import CoreBluetooth
15| |#endif
16| |
17| |// MARK: - Discover
18| |extension Publisher where Self.Failure == LittleBluetoothError {
19| | /// Starts scanning for `PeripheralDiscovery`
20| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
21| | /// - parameter services: Services for peripheral you are looking for
22| | /// - parameter options: Scanning options same as CoreBluetooth central manager option.
23| | /// - returns: A publisher with stream of disovered peripherals.
24| 11| public func startDiscovery(for littleBluetooth: LittleBlueTooth, withServices services: [CBUUID]?, options: [String : Any]? = nil) -> AnyPublisher<PeripheralDiscovery, LittleBluetoothError> {
25| 11| func startDiscovery<Upstream: Publisher>(upstream: Upstream,
26| 11| for littleBluetooth: LittleBlueTooth,
27| 11| withServices services: [CBUUID]?,
28| 11| options: [String : Any]? = nil) -> AnyPublisher<PeripheralDiscovery, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
29| 11| return upstream
30| 11| .flatMapLatest { _ in
31| 11| littleBluetooth.startDiscovery(withServices: services, options: options)
32| 11| }
33| 11| }
34| 11| return startDiscovery(upstream: self,
35| 11| for: littleBluetooth,
36| 11| withServices: services,
37| 11| options: options)
38| 11| }
39| |
40| | /// Stops peripheral discovery
41| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
42| | /// - returns: A publisher when discovery has been stopped
43| 1| public func stopDiscovery(for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Void, LittleBluetoothError> {
44| 1| func stopDiscovery<Upstream: Publisher>(upstream: Upstream,
45| 1| for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Void, LittleBluetoothError>where Upstream.Failure == LittleBluetoothError {
46| 1| return upstream
47| 2| .flatMapLatest { _ in
48| 2| littleBluetooth.stopDiscovery()
49| 2| }
50| 1| }
51| 1| return stopDiscovery(upstream: self,
52| 1| for: littleBluetooth)
53| 1| }
54| |}
55| |
56| |// MARK: - Connect
57| |extension Publisher where Self.Output == PeripheralDiscovery, Self.Failure == LittleBluetoothError {
58| |
59| | /// Starts connection for `PeripheralDiscovery`
60| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
61| | /// - parameter options: Connecting options same as CoreBluetooth central manager option.
62| | /// - returns: A publisher with the just connected `Peripheral`.
63| | public func connect(for littleBluetooth: LittleBlueTooth,
64| 9| options: [String : Any]? = nil) -> AnyPublisher<Peripheral, LittleBluetoothError> {
65| 9|
66| 9| func connect<Upstream: Publisher>(upstream: Upstream,
67| 9| for littleBluetooth: LittleBlueTooth,
68| 9| options: [String : Any]? = nil) -> AnyPublisher<Peripheral, LittleBluetoothError> where Upstream.Output == PeripheralDiscovery, Upstream.Failure == LittleBluetoothError {
69| 9| return upstream
70| 9| .flatMapLatest { (periph) in
71| 9| littleBluetooth.connect(to: periph, options: options)
72| 9| }.eraseToAnyPublisher()
73| 9| }
74| 9|
75| 9| return connect(upstream: self,
76| 9| for: littleBluetooth,
77| 9| options: options)
78| 9| }
79| |}
80| |
81| |extension Publisher where Self.Output == PeripheralIdentifier, Self.Failure == LittleBluetoothError {
82| |
83| | /// Starts connection for `PeripheralIdentifier`
84| | /// - parameter littleBluetooth: the `LittleBlueTooth` instance
85| | /// - parameter options: Connecting options same as CoreBluetooth central manager option.
86| | /// - returns: A publisher with the just connected `Peripheral`.
87| | public func connect(for littleBluetooth: LittleBlueTooth,
88| 1| options: [String : Any]? = nil) -> AnyPublisher<Peripheral, LittleBluetoothError> {
89| 1|
90| 1| func connect<Upstream: Publisher>(upstream: Upstream,
91| 1| for littleBluetooth: LittleBlueTooth,
92| 1| options: [String : Any]? = nil) -> AnyPublisher<Peripheral, LittleBluetoothError> where Upstream.Output == PeripheralIdentifier, Upstream.Failure == LittleBluetoothError {
93| 1| return upstream
94| 1| .flatMapLatest { (periph) in
95| 1| littleBluetooth.connect(to: periph, options: options)
96| 1| }.eraseToAnyPublisher()
97| 1| }
98| 1|
99| 1| return connect(upstream: self,
100| 1| for: littleBluetooth,
101| 1| options: options)
102| 1| }
103| |}
104| |
105| |// MARK: - Disconnect
106| |extension Publisher where Self.Failure == LittleBluetoothError {
107| |
108| | /// Disconnect the connected `Peripheral`
109| | /// - returns: A publisher with the just disconnected `Peripheral` or a `LittleBluetoothError`
110| | @discardableResult
111| 1| public func disconnect(for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Peripheral, LittleBluetoothError> {
112| 1| func disconnect<Upstream: Publisher>(upstream: Upstream,
113| 1| for littleBluetooth: LittleBlueTooth) -> AnyPublisher<Peripheral, LittleBluetoothError> where Upstream.Failure == LittleBluetoothError {
114| 1| return upstream
115| 1| .flatMapLatest { _ in
116| 1| littleBluetooth.disconnect()
117| 1| }
118| 1| }
119| 1| return disconnect(upstream: self,
120| 1| for: littleBluetooth)
121| 1| }
122| |
123| | /// Specialized timeout function to return a `LittleBluetoothError` error type. By default it returns `.operationTimeout`, but you can specify a different error such as `.connectionTimeout`, `.scanTimeout`
124| | /// Terminates publishing if the upstream publisher exceeds the specified time interval without producing an element.
125| | /// - Parameters:
126| | /// - interval: The maximum time interval the publisher can go without emitting an element, expressed in the time system of the scheduler.
127| | /// - scheduler: The scheduler to deliver events on.
128| | /// - options: Scheduler options that customize the delivery of elements.
129| | /// - error: An error to be returned if the publisher times out, by default `LittleBluetoothError.connectionTimeout`
130| | /// - Returns: A publisher that terminates if the specified interval elapses with no events received from the upstream publisher.
131| 1| public func timeout<S>(_ interval: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil, error: LittleBluetoothError = .operationTimeout) -> AnyPublisher<Self.Output, LittleBluetoothError> where S: Scheduler {
132| 1| func timeout<Upstream: Publisher, S>(upsstream: Upstream,_ interval: S.SchedulerTimeType.Stride, scheduler: S, options: S.SchedulerOptions? = nil, error: LittleBluetoothError = .operationTimeout) -> AnyPublisher<Upstream.Output, LittleBluetoothError> where S: Scheduler, Upstream.Failure == LittleBluetoothError {
133| 1| return upsstream
134| 1| .timeout(interval, scheduler: scheduler, options: options, customError: {error})
135| 1| .eraseToAnyPublisher()
136| 1| }
137| 1|
138| 1| return timeout(upsstream: self, interval, scheduler: scheduler, options: options, error: error)
139| 1| }
140| |
141| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Extension/Helper.swift:
1| |//
2| |// Helper.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 09/08/2020.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |import os.log
11| |#if TEST
12| |import CoreBluetoothMock
13| |#else
14| |import CoreBluetooth
15| |#endif
16| |
17| |extension AnyCancellable {
18| | func store(in dictionary: inout [UUID : AnyCancellable],
19| 235| for key: UUID) {
20| 235| dictionary[key] = self
21| 235| }
22| |}
23| |extension Publisher {
24| |
25| 174| func flatMapLatest<T: Publisher>(_ transform: @escaping (Self.Output) -> T) -> AnyPublisher<T.Output, T.Failure> where T.Failure == Self.Failure {
26| 174| return map(transform).switchToLatest().eraseToAnyPublisher()
27| 174| }
28| |}
29| |
30| |extension TimeInterval {
31| 1| public var dispatchInterval: DispatchTimeInterval {
32| 1| let microseconds = Int64(self * TimeInterval(USEC_PER_SEC)) // perhaps use nanoseconds, though would more often be > Int.max
33| 1| return microseconds < Int.max ? DispatchTimeInterval.microseconds(Int(microseconds)) : DispatchTimeInterval.seconds(Int(self))
34| 1| }
35| |}
36| |
37| |extension OSLog {
38| | public static var Subsystem = "it.vanillagorilla.LittleBlueTooth"
39| | public static var General = "General"
40| | public static var CentralManager = "CentralManager"
41| | public static var Peripheral = "Peripheral"
42| | public static var Restore = "Restore"
43| |
44| | public static let LittleBT_Log_General = OSLog(subsystem: Subsystem, category: General)
45| | public static let LittleBT_Log_CentralManager = OSLog(subsystem: Subsystem, category: CentralManager)
46| | public static let LittleBT_Log_Peripheral = OSLog(subsystem: Subsystem, category: Peripheral)
47| | public static let LittleBT_Log_Restore = OSLog(subsystem: Subsystem, category: Restore)
48| |
49| |}
50| |#if TEST
51| |extension CBMPeripheral {
52| 109| public var description: String {
53| 109| return "Test peripheral"
54| 109| }
55| |}
56| |#endif
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/AdvertisingData.swift:
1| |//
2| |// AdvertisingData.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 10/06/2020.
6| |// Copyright © 2020 Andrea Finollo. All rights reserved.
7| |//
8| |
9| |import Foundation
10| |#if TEST
11| |import CoreBluetoothMock
12| |#else
13| |import CoreBluetooth
14| |#endif
15| |
16| |public struct AdvertisingInfo {
17| | public let advertisementData: [String: Any]
18| |
19| | /// Creates advertisement data based on CoreBluetooth's dictionary
20| | /// - parameter advertisementData: Core Bluetooth's advertisement data
21| 42| public init(advertisementData: [String: Any]) {
22| 42| self.advertisementData = advertisementData
23| 42| }
24| |
25| | /// A string containing the local name of a peripheral.
26| 19| public var localName: String? {
27| 19| return advertisementData[CBAdvertisementDataLocalNameKey] as? String
28| 19| }
29| |
30| | /// A Data object containing the manufacturer data of a peripheral.
31| 19| public var manufacturerData: Data? {
32| 19| return advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
33| 19| }
34| |
35| | /// A dictionary containing service-specific advertisement data.
36| | /// The keys are CBUUID objects, representing CBService UUIDs. The values are Data objects,
37| | /// representing service-specific data.
38| 19| public var serviceData: [CBUUID: Data]? {
39| 19| return advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
40| 19| }
41| |
42| | /// An array of service UUIDs.
43| 19| public var serviceUUIDs: [CBUUID]? {
44| 19| return advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
45| 19| }
46| |
47| | /// An array of one or more CBUUID objects, representing CBService UUIDs that were found in the “overflow”
48| | /// area of the advertisement data.
49| 19| public var overflowServiceUUIDs: [CBUUID]? {
50| 19| return advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID]
51| 19| }
52| |
53| | /// A number (an instance of NSNumber) containing the transmit power of a peripheral.
54| | /// This key and value are available if the broadcaster (peripheral)
55| | /// provides its Tx power level in its advertising packet.
56| | /// Using the RSSI value and the Tx power level, it is possible to calculate path loss.
57| 19| public var txPowerLevel: NSNumber? {
58| 19| return advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber
59| 19| }
60| |
61| | /// A Boolean value that indicates whether the advertising event type is connectable.
62| | /// The value for this key is an NSNumber object. You can use this value to determine whether
63| | /// a peripheral is connectable at a particular moment.
64| 19| public var isConnectable: Bool? {
65| 19| return advertisementData[CBAdvertisementDataIsConnectable] as? Bool
66| 19| }
67| |
68| | /// An array of one or more CBUUID objects, representing CBService UUIDs.
69| 19| public var solicitedServiceUUIDs: [CBUUID]? {
70| 19| return advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID]
71| 19| }
72| |}
73| |
74| |extension AdvertisingInfo: CustomDebugStringConvertible {
75| |
76| 19| public var debugDescription: String {
77| 19| return """
78| 19| Name: \(localName ?? "Not available")
79| 19| Manufacturer: \(manufacturerData?.description ?? "Not available")
80| 19| Service Data: \(serviceData ?? [:])
81| 19| ServiceUUID: \(serviceUUIDs ?? [])
82| 19| OverflowService: \(overflowServiceUUIDs ?? [])
83| 19| TX: \(txPowerLevel?.stringValue ?? "Not available")
84| 19| Connectable: \(isConnectable?.description ?? "Not available")
85| 19| SolicitedService: \(solicitedServiceUUIDs ?? [])
86| 19| """
87| 19| }
88| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/CentralRestorer.swift:
1| |//
2| |// CentralRestorer.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 15/07/2020.
6| |//
7| |
8| |import Foundation
9| |import Combine
10| |#if TEST
11| |import CoreBluetoothMock
12| |#else
13| |import CoreBluetooth
14| |#endif
15| |/**
16| | This object contains parsed information passed from the `centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any])` method of `CBCentralManagerDelegate` dictionary
17| | */
18| |public struct CentralRestorer {
19| | public unowned let centralManager: CBCentralManager
20| | public let restoredInfo: [String : Any]
21| |
22| |
23| | /// Array of `PeripheralIdentifier` objects which have been restored.
24| | /// These are peripherals that were connected to the central manager (or had a connection pending)
25| | /// at the time the app was terminated by the system.
26| 3| public var peripherals: [PeripheralIdentifier] {
27| 3| if let peripherals = restoredInfo[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
28| 3| return centralManager.retrievePeripherals(withIdentifiers: peripherals.map{$0.identifier}).map {PeripheralIdentifier(peripheral: $0)}
29| 3| }
30| 0| return []
31| 3| }
32| |
33| | /// Dictionary that contains all of the peripheral scan options that were being used
34| | /// by the central manager at the time the app was terminated by the system.
35| 2| public var scanOptions: [String: AnyObject] {
36| 2| if let info = restoredInfo[CBCentralManagerRestoredStateScanOptionsKey] as? [String: AnyObject] {
37| 2| return info
38| 2| }
39| 0| return [:]
40| 2| }
41| |
42| | /// Array of `CBUUID` objects of services which have been restored.
43| | /// These are all the services the central manager was scanning for at the time the app
44| | /// was terminated by the system.
45| 2| public var services: [CBUUID] {
46| 2| if let servicesUUID = restoredInfo[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID] {
47| 2| return servicesUUID
48| 2| }
49| 0| return []
50| 2| }
51| |}
52| |
53| |extension CentralRestorer: CustomDebugStringConvertible {
54| 1| public var debugDescription: String {
55| 1| return """
56| 1| Peripherals: \(peripherals)
57| 1| Scan options: \(scanOptions)
58| 1| Services: \(services)
59| 1| """
60| 1| }
61| |}
62| |
63| |/**
64| |This object contains the restored action during state restoration
65| |*/
66| |public enum Restored: CustomDebugStringConvertible {
67| | /// Peripherals scan has been restored
68| | case scan(discoveryPublisher: AnyPublisher<PeripheralDiscovery, LittleBluetoothError>)
69| | /// Peripheral has been restored
70| | case peripheral(Peripheral)
71| | /// Nothing has been restored
72| | case nothing
73| |
74| 1| public var debugDescription: String {
75| 1| switch self {
76| 1| case .scan(_):
77| 0| return "Restored Scan"
78| 1| case .peripheral(let periph):
79| 0| return "Restored \(periph.debugDescription)"
80| 1| case .nothing:
81| 1| return "Nothing to be restored"
82| 1| }
83| 1| }
84| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/LittleBlueToothCharacteristic.swift:
1| |//
2| |// Task.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 10/06/2020.
6| |// Copyright © 2020 Andrea Finollo. All rights reserved.
7| |//
8| |
9| |import Foundation
10| |#if TEST
11| |import CoreBluetoothMock
12| |#else
13| |import CoreBluetooth
14| |#endif
15| |
16| |public typealias LittleBlueToothServiceIndentifier = String
17| |public typealias LittleBlueToothCharacteristicIndentifier = String
18| |
19| |
20| |/// A representation of a bluetooth characteristic
21| |public struct LittleBlueToothCharacteristic: Identifiable {
22| | public let id: CBUUID
23| | public let service: CBUUID
24| | public let properties: Properties
25| |
26| 82| public var rawValue: Data? {
27| 82| cbCharacteristic?.value
28| 82| }
29| |
30| | private var cbCharacteristic: CBCharacteristic?
31| |
32| 30| public init(characteristic: LittleBlueToothCharacteristicIndentifier, for service: LittleBlueToothServiceIndentifier, properties: LittleBlueToothCharacteristic.Properties) {
33| 30| self.id = CBUUID(string: characteristic)
34| 30| self.service = CBUUID(string: service)
35| 30| self.properties = properties
36| 30| }
37| |
38| 175| public init(with characteristic: CBCharacteristic) {
39| 175| self.id = characteristic.uuid
40| 175| self.service = characteristic.service.uuid
41| 175| self.cbCharacteristic = characteristic
42| 175| self.properties = Properties(properties: characteristic.properties)
43| 175| }
44| |
45| 82| public func value<T: Readable>() throws -> T {
46| 82| guard let data = rawValue else {
47| 0| throw LittleBluetoothError.emptyData
48| 82| }
49| 82| return try T.init(from: data)
50| 82| }
51| |}
52| |
53| |extension LittleBlueToothCharacteristic: Equatable, Hashable {
54| 2| public static func == (lhs: Self, rhs: Self) -> Bool {
55| 2| if lhs.id == rhs.id &&
56| 2| lhs.service == rhs.service {
57| 1| return true
58| 1| }
59| 1| return false
60| 2| }
61| |
62| 2| public func hash(into hasher: inout Hasher) {
63| 2| hasher.combine(id)
64| 2| hasher.combine(service)
65| 2| }
66| |
67| |}
68| |
69| |public extension LittleBlueToothCharacteristic {
70| | /// Permitted operations on the characteristic they already exist in CBCharacteristic need to remap when initialized from CBCharacteristic
71| | struct Properties: OptionSet {
72| | public let rawValue: UInt8
73| |
74| | public static var broadcast = Properties(rawValue: 1 << 0)
75| | public static var read = Properties(rawValue: 1 << 1)
76| | public static var writeWithoutResponse = Properties(rawValue: 1 << 2)
77| | public static var write = Properties(rawValue: 1 << 3)
78| | public static var notify = Properties(rawValue: 1 << 4)
79| | public static var indicate = Properties(rawValue: 1 << 5)
80| | public static var authenticatedSignedWrites = Properties(rawValue: 1 << 6)
81| | public static var extendedProperties = Properties(rawValue: 1 << 7)
82| | public static var notifyEncryptionRequired = Properties(rawValue: 1 << 8)
83| | public static var indicateEncryptionRequired = Properties(rawValue: 1 << 9)
84| |
85| 2.33k| public init(rawValue: UInt8) {
86| 2.33k| self.rawValue = rawValue
87| 2.33k| }
88| |
89| 175| public init(properties: CBCharacteristicProperties) {
90| 175| self = Self.mapToProperties(values: properties)
91| 175| }
92| |
93| 175| static func mapToProperties(values: CBCharacteristicProperties) -> Properties {
94| 175| var properties: Properties = []
95| 474| values.elements().forEach { (prop) in
96| 474| switch prop {
97| 474| case .broadcast:
98| 0| properties.update(with: .broadcast)
99| 474| case .read:
100| 175| properties.update(with: .read)
101| 474| case .writeWithoutResponse:
102| 0| properties.update(with: .writeWithoutResponse)
103| 474| case .write:
104| 124| properties.update(with: .write)
105| 474| case .notify:
106| 175| properties.update(with: .notify)
107| 474| case .indicate:
108| 0| properties.update(with: .indicate)
109| 474| case .authenticatedSignedWrites:
110| 0| properties.update(with: .authenticatedSignedWrites)
111| 474| case .extendedProperties:
112| 0| properties.update(with: .extendedProperties)
113| 474| case .notifyEncryptionRequired:
114| 0| properties.update(with: .notifyEncryptionRequired)
115| 474| case .indicateEncryptionRequired:
116| 0| properties.update(with: .indicateEncryptionRequired)
117| 474| default:
118| 0| print("NO mapping")
119| 474| }
120| 474| }
121| 175| return properties
122| 175| }
123| | }
124| |}
125| |
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/LittleBluetoothConfiguration.swift:
1| |//
2| |// LittleBluetoothConfiguration.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 26/07/2020.
6| |//
7| |
8| |import Foundation
9| |
10| |/// Pass a `Peripheral` and an evetual `LittleBluetoothError` and expect a boolean as an answer
11| |public typealias AutoconnectionHandler = (PeripheralIdentifier, LittleBluetoothError?) -> Bool
12| |
13| |/// Configuration object that must be passed during the `LittleBlueTooth` initialization
14| |public struct LittleBluetoothConfiguration {
15| | /// `CBCentralManager` options dictionary for instance the restore identifier, thay are the same
16| | /// requested for `CBCentralManager`
17| | public var centralManagerOptions: [String : Any]?
18| | /// `CBCentralManager` queue
19| | public var centralManagerQueue: DispatchQueue?
20| | /// This handler must be used to handle connection process after a disconnession.
21| | /// You can inspect the error and decide if an automatic connection is necessary.
22| | /// If you return `true` the connection process will start, once the peripheral has been found a connection will be established.
23| | /// If you return `false` the system will not try to establish a connection
24| | /// Connection process will remain active also in background if the app has the right
25| | /// permission, to cancel just call `disconnect`.
26| | /// When a connection will be established an `.autoConnected(PeripheralIdentifier)` event will be streamed to
27| | /// the `connectionEventPublisher`
28| | public var autoconnectionHandler: AutoconnectionHandler?
29| | /// Handler used to manage state restoration. `Restored` object will contain the restored information
30| | /// could be a peripheral, a scan or nothing
31| | public var restoreHandler: ((Restored) -> Void)?
32| | /// Enable logging, log is made using os_log and it exposes some information even in release configuration
33| | public var isLogEnabled = false
34| |
35| 45| public init() {}
36| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/Loggable.swift:
1| |//
2| |// Loggable.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 07/08/2020.
6| |//
7| |
8| |import Foundation
9| |import os.log
10| |
11| |protocol Loggable {
12| | var isLogEnabled: Bool {get set}
13| | func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: CVarArg...)
14| |}
15| |
16| |
17| |extension Loggable {
18| 1.22k| func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: CVarArg...) {
19| 1.22k| #if !TEST
20| 1.22k| guard isLogEnabled else {
21| 1.22k| return
22| 1.22k| }
23| 1.22k| os_log(type, log: log, message, arg)
24| 1.22k| #endif
25| 1.22k| }
26| |}
/Users/Andrea/Documents/GitHub/LittleBlueTooth/Sources/LittleBlueTooth/Classes/Model/Peripheral.swift:
1| |//
2| |// Peripheral.swift
3| |// LittleBlueTooth
4| |//
5| |// Created by Andrea Finollo on 10/06/2020.
6| |// Copyright © 2020 Andrea Finollo. All rights reserved.
7| |//
8| |
9| |import Foundation
10| |import Combine
11| |
12| |#if TEST
13| |import CoreBluetoothMock
14| |#else
15| |import CoreBluetooth
16| |#endif
17| |
18| |public enum PeripheralChanges {
19| | case name(String?)
20| | case invalidatedServices([CBService])
21| |}
22| |
23| |public enum PeripheralState {
24| | case disconnected
25| | case connecting
26| | case connected
27| | case disconnecting
28| | case unknown
29| |
30| 1.50k| init(state: CBPeripheralState) {
31| 1.50k| switch state {
32| 1.50k| case .disconnected:
33| 1.35k| self = .disconnected
34| 1.50k| case .connected:
35| 146| self = .connected
36| 1.50k| case .disconnecting:
37| 1| self = .disconnecting
38| 1.50k| case .connecting:
39| 0| self = .connecting
40| 1.50k| default:
41| 0| self = .unknown
42| 1.50k| }
43| 1.50k| }
44| |}
45| |
46| |public class Peripheral: Identifiable {
47| 18| public var id: UUID {
48| 18| cbPeripheral.identifier
49| 18| }
50| |
51| 18| public var name: String? {
52| 18| cbPeripheral.name
53| 18| }
54| |
55| 69| public var state: PeripheralState {
56| 69| PeripheralState(state: cbPeripheral.state)
57| 69| }
58| |
59| | public let cbPeripheral: CBPeripheral
60| | public var rssi: Int?
61| |
62| |
63| | var isLogEnabled: Bool {
64| 68| get {
65| 68| return _isLogEnabled
66| 68| }
67| 71| set {
68| 71| _isLogEnabled = newValue
69| 71| peripheralProxy.isLogEnabled = newValue
70| 71| }
71| | }
72| |
73| | lazy var changesPublisher: AnyPublisher<PeripheralChanges, Never> =
74| | peripheralProxy.peripheralChangesPublisher
75| | .share()
76| | .eraseToAnyPublisher()
77| |
78| | lazy var listenPublisher: AnyPublisher<CBCharacteristic, LittleBluetoothError> =
79| | peripheralProxy.peripheralUpdatedValueForNotifyCharacteristicPublisher
80| 122| .tryMap { (value) -> CBCharacteristic in
81| 122| switch value {
82| 122| case let (_, error?):
83| 0| throw error
84| 122| case let (charact, _):
85| 122| return charact
86| 122| }
87| 122| }
88| 0| .mapError {$0 as! LittleBluetoothError}
89| | .share()
90| | .eraseToAnyPublisher()
91| |
92| | let peripheralStatePublisher: AnyPublisher<PeripheralState, Never>
93| |
94| 36| private let peripheralProxy = CBPeripheralDelegateProxy()
95| | private var _isLogEnabled: Bool = false
96| |
97| 36| init(_ peripheral: CBPeripheral) {
98| 36| self.cbPeripheral = peripheral
99| 36| self.cbPeripheral.delegate = self.peripheralProxy
100| 36| #if !TEST
101| 36| self.peripheralStatePublisher = self.cbPeripheral.publisher(for: \.state)
102| 36| .map{ (state) -> PeripheralState in
103| 36| PeripheralState(state: state)
104| 36| }
105| 36| .share()
106| 36| .eraseToAnyPublisher()
107| 36| // Using a timer to poll peripheral state for test to simulate KVO
108| 36| #else
109| 36| self.peripheralStatePublisher = Timer.publish(every: 0.2, on: .main, in: .common)
110| 36| .autoconnect()
111| 1.43k| .map {_ in
112| 1.43k| PeripheralState(state: peripheral.state)
113| 1.43k| }
114| 36| .eraseToAnyPublisher()
115| 36| #endif
116| 36| }
117| |
118| 34| fileprivate func getService(serviceUUID: CBUUID) -> AnyPublisher<[CBService]?, LittleBluetoothError> {
119| 34| if let services = self.cbPeripheral.services, services.contains(where: { (service) -> Bool in
120| 13| return service.uuid == serviceUUID
121| 13| }) {
122| 13| return Result<[CBService]?, LittleBluetoothError>.Publisher(.success(services)).eraseToAnyPublisher()
123| 21| } else {
124| 21| let services = self.peripheralProxy.peripheralDiscoveredServicesPublisher
125| 21| .tryMap { (value) -> [CBService]? in
126| 20| switch value {
127| 20| case let (_, error?):
128| 0| throw error
129| 20| case let (services?, _) where services.map{$0.uuid}.contains(serviceUUID):
130| 19| return services
131| 20| case (_, .none):
132| 1| throw LittleBluetoothError.serviceNotFound(nil)
133| 20| }
134| 20| }
135| 21| .mapError {$0 as! LittleBluetoothError}
136| 21| defer {
137| 21| self.cbPeripheral.discoverServices([serviceUUID])
138| 21| }
139| 21| return services.eraseToAnyPublisher()
140| 21| }
141| 0| }
142| |
143| 32| fileprivate func getCharateristic(characteristicUUID: CBUUID, from service: CBService) -> AnyPublisher<CBService, LittleBluetoothError> {
144| 32| if let characteristics = service.characteristics, characteristics.contains(where: { (charact) -> Bool in
145| 15| return charact.uuid == characteristicUUID
146| 15| }) {
147| 10| return Result<CBService, LittleBluetoothError>.Publisher(.success(service)).eraseToAnyPublisher()
148| 22| } else {
149| 22| let charact = self.peripheralProxy.peripheralDiscoveredCharacteristicsForServicePublisher
150| 22| .tryMap { (value) -> CBService in
151| 22| switch value {
152| 22| case let (_, error?):
153| 0| throw error
154| 22| case let (service, _):
155| 22| return service
156| 22| }
157| 22| }
158| 22| .mapError {$0 as! LittleBluetoothError}
159| 22| defer {
160| 22| self.cbPeripheral.discoverCharacteristics([characteristicUUID], for: service)
161| 22| }
162| 22| return charact.eraseToAnyPublisher()
163| 22| }
164| 0| }
165| |
166| 34| fileprivate func discoverCharacteristic(_ charateristicUUID: CBUUID, fromService serviceUUID: CBUUID) -> AnyPublisher<CBCharacteristic, LittleBluetoothError> {
167| 34| let discovery = self.getService(serviceUUID: serviceUUID)
168| 34| .customPrint("[LBT] Discover service", isEnabled: isLogEnabled)
169| 34| .flatMap { services -> AnyPublisher<CBService, LittleBluetoothError> in
170| 32| let service = services!.filter{ $0.uuid == serviceUUID}.first!
171| 32| return self.getCharateristic(characteristicUUID: charateristicUUID, from: service)
172| 32| }
173| 34| .customPrint("[LBT] Discover characteristic", isEnabled: isLogEnabled)
174| 34| .tryMap { (service) -> CBCharacteristic in
175| 37| guard let charact = service.characteristics?.filter({ $0.uuid == charateristicUUID}).first else {
176| 2| throw LittleBluetoothError.characteristicNotFound(nil)
177| 30| }
178| 30| return charact
179| 32| }
180| 34| .mapError{$0 as! LittleBluetoothError}
181| 34| .eraseToAnyPublisher()
182| 34| return discovery
183| 34| }
184| |
185| 2| func readRSSI() -> AnyPublisher<Int, LittleBluetoothError> {
186| 2| let readRSSI =
187| 2| peripheralProxy.peripheralRSSIPublisher
188| 2| .tryMap { (value) -> Int in
189| 2| switch value {
190| 2| case let (_, error?):
191| 0| throw error
192| 2| case let (rssi, _):
193| 2| return rssi
194| 2| }
195| 2| }
196| 2| .mapError {$0 as! LittleBluetoothError}
197| 2| .eraseToAnyPublisher()
198| 2| defer {
199| 2| cbPeripheral.readRSSI()
200| 2| }
201| 2| return readRSSI
202| 2| }
203| |