FlutterBluePlugin.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. // Copyright 2017, Paul DeMarco.
  2. // All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. #import "FlutterBluePlugin.h"
  5. #import "Flutterblue.pbobjc.h"
  6. @interface CBUUID (CBUUIDAdditionsFlutterBlue)
  7. - (NSString *)fullUUIDString;
  8. @end
  9. @implementation CBUUID (CBUUIDAdditionsFlutterBlue)
  10. - (NSString *)fullUUIDString {
  11. if(self.UUIDString.length == 4) {
  12. return [[NSString stringWithFormat:@"0000%@-0000-1000-8000-00805F9B34FB", self.UUIDString] lowercaseString];
  13. }
  14. return [self.UUIDString lowercaseString];
  15. }
  16. @end
  17. typedef NS_ENUM(NSUInteger, LogLevel) {
  18. emergency = 0,
  19. alert = 1,
  20. critical = 2,
  21. error = 3,
  22. warning = 4,
  23. notice = 5,
  24. info = 6,
  25. debug = 7
  26. };
  27. @interface FlutterBluePlugin ()
  28. @property(nonatomic, retain) NSObject<FlutterPluginRegistrar> *registrar;
  29. @property(nonatomic, retain) FlutterMethodChannel *channel;
  30. @property(nonatomic, retain) FlutterBlueStreamHandler *stateStreamHandler;
  31. @property(nonatomic, retain) CBCentralManager *centralManager;
  32. @property(nonatomic) NSMutableDictionary *scannedPeripherals;
  33. @property(nonatomic) NSMutableArray *servicesThatNeedDiscovered;
  34. @property(nonatomic) NSMutableArray *characteristicsThatNeedDiscovered;
  35. @property(nonatomic) LogLevel logLevel;
  36. @end
  37. @implementation FlutterBluePlugin
  38. + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  39. FlutterMethodChannel* channel = [FlutterMethodChannel
  40. methodChannelWithName:NAMESPACE @"/methods"
  41. binaryMessenger:[registrar messenger]];
  42. FlutterEventChannel* stateChannel = [FlutterEventChannel eventChannelWithName:NAMESPACE @"/state" binaryMessenger:[registrar messenger]];
  43. FlutterBluePlugin* instance = [[FlutterBluePlugin alloc] init];
  44. instance.channel = channel;
  45. instance.centralManager = [[CBCentralManager alloc] initWithDelegate:instance queue:nil];
  46. instance.scannedPeripherals = [NSMutableDictionary new];
  47. instance.servicesThatNeedDiscovered = [NSMutableArray new];
  48. instance.characteristicsThatNeedDiscovered = [NSMutableArray new];
  49. instance.logLevel = emergency;
  50. // STATE
  51. FlutterBlueStreamHandler* stateStreamHandler = [[FlutterBlueStreamHandler alloc] init];
  52. [stateChannel setStreamHandler:stateStreamHandler];
  53. instance.stateStreamHandler = stateStreamHandler;
  54. [registrar addMethodCallDelegate:instance channel:channel];
  55. }
  56. - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  57. if ([@"setLogLevel" isEqualToString:call.method]) {
  58. NSNumber *logLevelIndex = [call arguments];
  59. _logLevel = (LogLevel)[logLevelIndex integerValue];
  60. result(nil);
  61. } else if ([@"state" isEqualToString:call.method]) {
  62. FlutterStandardTypedData *data = [self toFlutterData:[self toBluetoothStateProto:self->_centralManager.state]];
  63. result(data);
  64. } else if([@"isAvailable" isEqualToString:call.method]) {
  65. if(self.centralManager.state != CBManagerStateUnsupported && self.centralManager.state != CBManagerStateUnknown) {
  66. result(@(YES));
  67. } else {
  68. result(@(NO));
  69. }
  70. } else if([@"isOn" isEqualToString:call.method]) {
  71. if(self.centralManager.state == CBManagerStatePoweredOn) {
  72. result(@(YES));
  73. } else {
  74. result(@(NO));
  75. }
  76. } else if([@"startScan" isEqualToString:call.method]) {
  77. // Clear any existing scan results
  78. [self.scannedPeripherals removeAllObjects];
  79. // TODO: Request Permission?
  80. FlutterStandardTypedData *data = [call arguments];
  81. ProtosScanSettings *request = [[ProtosScanSettings alloc] initWithData:[data data] error:nil];
  82. // UUID Service filter
  83. NSArray *uuids = [NSArray array];
  84. for (int i = 0; i < [request serviceUuidsArray_Count]; i++) {
  85. NSString *u = [request.serviceUuidsArray objectAtIndex:i];
  86. uuids = [uuids arrayByAddingObject:[CBUUID UUIDWithString:u]];
  87. }
  88. NSMutableDictionary<NSString *, id> *scanOpts = [NSMutableDictionary new];
  89. if (request.allowDuplicates) {
  90. [scanOpts setObject:[NSNumber numberWithBool:YES] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
  91. }
  92. [self->_centralManager scanForPeripheralsWithServices:uuids options:scanOpts];
  93. result(nil);
  94. } else if([@"stopScan" isEqualToString:call.method]) {
  95. [self->_centralManager stopScan];
  96. result(nil);
  97. } else if([@"getConnectedDevices" isEqualToString:call.method]) {
  98. // Cannot pass blank UUID list for security reasons. Assume all devices have the Generic Access service 0x1800
  99. NSArray *periphs = [self->_centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:@"1800"]]];
  100. NSLog(@"getConnectedDevices periphs size: %lu", [periphs count]);
  101. result([self toFlutterData:[self toConnectedDeviceResponseProto:periphs]]);
  102. } else if([@"connect" isEqualToString:call.method]) {
  103. FlutterStandardTypedData *data = [call arguments];
  104. ProtosConnectRequest *request = [[ProtosConnectRequest alloc] initWithData:[data data] error:nil];
  105. NSString *remoteId = [request remoteId];
  106. @try {
  107. CBPeripheral *peripheral = [_scannedPeripherals objectForKey:remoteId];
  108. if(peripheral == nil) {
  109. @throw [FlutterError errorWithCode:@"connect"
  110. message:@"Peripheral not found"
  111. details:nil];
  112. }
  113. // TODO: Implement Connect options (#36)
  114. [_centralManager connectPeripheral:peripheral options:nil];
  115. result(nil);
  116. } @catch(FlutterError *e) {
  117. result(e);
  118. }
  119. } else if([@"disconnect" isEqualToString:call.method]) {
  120. NSString *remoteId = [call arguments];
  121. @try {
  122. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  123. [_centralManager cancelPeripheralConnection:peripheral];
  124. result(nil);
  125. } @catch(FlutterError *e) {
  126. result(e);
  127. }
  128. } else if([@"deviceState" isEqualToString:call.method]) {
  129. NSString *remoteId = [call arguments];
  130. @try {
  131. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  132. result([self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]);
  133. } @catch(FlutterError *e) {
  134. result(e);
  135. }
  136. } else if([@"discoverServices" isEqualToString:call.method]) {
  137. NSString *remoteId = [call arguments];
  138. @try {
  139. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  140. // Clear helper arrays
  141. [_servicesThatNeedDiscovered removeAllObjects];
  142. [_characteristicsThatNeedDiscovered removeAllObjects ];
  143. [peripheral discoverServices:nil];
  144. result(nil);
  145. } @catch(FlutterError *e) {
  146. result(e);
  147. }
  148. } else if([@"services" isEqualToString:call.method]) {
  149. NSString *remoteId = [call arguments];
  150. @try {
  151. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  152. result([self toFlutterData:[self toServicesResultProto:peripheral]]);
  153. } @catch(FlutterError *e) {
  154. result(e);
  155. }
  156. } else if([@"readCharacteristic" isEqualToString:call.method]) {
  157. FlutterStandardTypedData *data = [call arguments];
  158. ProtosReadCharacteristicRequest *request = [[ProtosReadCharacteristicRequest alloc] initWithData:[data data] error:nil];
  159. NSString *remoteId = [request remoteId];
  160. @try {
  161. // Find peripheral
  162. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  163. // Find characteristic
  164. CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
  165. // Trigger a read
  166. [peripheral readValueForCharacteristic:characteristic];
  167. result(nil);
  168. } @catch(FlutterError *e) {
  169. result(e);
  170. }
  171. } else if([@"readDescriptor" isEqualToString:call.method]) {
  172. FlutterStandardTypedData *data = [call arguments];
  173. ProtosReadDescriptorRequest *request = [[ProtosReadDescriptorRequest alloc] initWithData:[data data] error:nil];
  174. NSString *remoteId = [request remoteId];
  175. @try {
  176. // Find peripheral
  177. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  178. // Find characteristic
  179. CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
  180. // Find descriptor
  181. CBDescriptor *descriptor = [self locateDescriptor:[request descriptorUuid] characteristic:characteristic];
  182. [peripheral readValueForDescriptor:descriptor];
  183. result(nil);
  184. } @catch(FlutterError *e) {
  185. result(e);
  186. }
  187. } else if([@"writeCharacteristic" isEqualToString:call.method]) {
  188. FlutterStandardTypedData *data = [call arguments];
  189. ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] initWithData:[data data] error:nil];
  190. NSString *remoteId = [request remoteId];
  191. @try {
  192. // Find peripheral
  193. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  194. // Find characteristic
  195. CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
  196. // Get correct write type
  197. CBCharacteristicWriteType type = ([request writeType] == ProtosWriteCharacteristicRequest_WriteType_WithoutResponse) ? CBCharacteristicWriteWithoutResponse : CBCharacteristicWriteWithResponse;
  198. // Write to characteristic
  199. [peripheral writeValue:[request value] forCharacteristic:characteristic type:type];
  200. result(nil);
  201. } @catch(FlutterError *e) {
  202. result(e);
  203. }
  204. } else if([@"writeDescriptor" isEqualToString:call.method]) {
  205. FlutterStandardTypedData *data = [call arguments];
  206. ProtosWriteDescriptorRequest *request = [[ProtosWriteDescriptorRequest alloc] initWithData:[data data] error:nil];
  207. NSString *remoteId = [request remoteId];
  208. @try {
  209. // Find peripheral
  210. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  211. // Find characteristic
  212. CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
  213. // Find descriptor
  214. CBDescriptor *descriptor = [self locateDescriptor:[request descriptorUuid] characteristic:characteristic];
  215. // Write descriptor
  216. [peripheral writeValue:[request value] forDescriptor:descriptor];
  217. result(nil);
  218. } @catch(FlutterError *e) {
  219. result(e);
  220. }
  221. } else if([@"setNotification" isEqualToString:call.method]) {
  222. FlutterStandardTypedData *data = [call arguments];
  223. ProtosSetNotificationRequest *request = [[ProtosSetNotificationRequest alloc] initWithData:[data data] error:nil];
  224. NSString *remoteId = [request remoteId];
  225. @try {
  226. // Find peripheral
  227. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  228. // Find characteristic
  229. CBCharacteristic *characteristic = [self locateCharacteristic:[request characteristicUuid] peripheral:peripheral serviceId:[request serviceUuid] secondaryServiceId:[request secondaryServiceUuid]];
  230. // Set notification value
  231. [peripheral setNotifyValue:[request enable] forCharacteristic:characteristic];
  232. result(nil);
  233. } @catch(FlutterError *e) {
  234. result(e);
  235. }
  236. } else if([@"mtu" isEqualToString:call.method]) {
  237. NSString *remoteId = [call arguments];
  238. @try {
  239. CBPeripheral *peripheral = [self findPeripheral:remoteId];
  240. uint32_t mtu = [self getMtu:peripheral];
  241. result([self toFlutterData:[self toMtuSizeResponseProto:peripheral mtu:mtu]]);
  242. } @catch(FlutterError *e) {
  243. result(e);
  244. }
  245. } else if([@"requestMtu" isEqualToString:call.method]) {
  246. result([FlutterError errorWithCode:@"requestMtu" message:@"iOS does not allow mtu requests to the peripheral" details:NULL]);
  247. } else {
  248. result(FlutterMethodNotImplemented);
  249. }
  250. }
  251. - (CBPeripheral*)findPeripheral:(NSString*)remoteId {
  252. NSArray<CBPeripheral*> *peripherals = [_centralManager retrievePeripheralsWithIdentifiers:@[[[NSUUID alloc] initWithUUIDString:remoteId]]];
  253. CBPeripheral *peripheral;
  254. for(CBPeripheral *p in peripherals) {
  255. if([[p.identifier UUIDString] isEqualToString:remoteId]) {
  256. peripheral = p;
  257. break;
  258. }
  259. }
  260. if(peripheral == nil) {
  261. @throw [FlutterError errorWithCode:@"findPeripheral"
  262. message:@"Peripheral not found"
  263. details:nil];
  264. }
  265. return peripheral;
  266. }
  267. - (CBCharacteristic*)locateCharacteristic:(NSString*)characteristicId peripheral:(CBPeripheral*)peripheral serviceId:(NSString*)serviceId secondaryServiceId:(NSString*)secondaryServiceId {
  268. CBService *primaryService = [self getServiceFromArray:serviceId array:[peripheral services]];
  269. if(primaryService == nil || [primaryService isPrimary] == false) {
  270. @throw [FlutterError errorWithCode:@"locateCharacteristic"
  271. message:@"service could not be located on the device"
  272. details:nil];
  273. }
  274. CBService *secondaryService;
  275. if(secondaryServiceId.length) {
  276. secondaryService = [self getServiceFromArray:secondaryServiceId array:[primaryService includedServices]];
  277. @throw [FlutterError errorWithCode:@"locateCharacteristic"
  278. message:@"secondary service could not be located on the device"
  279. details:secondaryServiceId];
  280. }
  281. CBService *service = (secondaryService != nil) ? secondaryService : primaryService;
  282. CBCharacteristic *characteristic = [self getCharacteristicFromArray:characteristicId array:[service characteristics]];
  283. if(characteristic == nil) {
  284. @throw [FlutterError errorWithCode:@"locateCharacteristic"
  285. message:@"characteristic could not be located on the device"
  286. details:nil];
  287. }
  288. return characteristic;
  289. }
  290. - (CBDescriptor*)locateDescriptor:(NSString*)descriptorId characteristic:(CBCharacteristic*)characteristic {
  291. CBDescriptor *descriptor = [self getDescriptorFromArray:descriptorId array:[characteristic descriptors]];
  292. if(descriptor == nil) {
  293. @throw [FlutterError errorWithCode:@"locateDescriptor"
  294. message:@"descriptor could not be located on the device"
  295. details:nil];
  296. }
  297. return descriptor;
  298. }
  299. // Reverse search to find primary service
  300. - (CBService*)findPrimaryService:(CBService*)secondaryService peripheral:(CBPeripheral*)peripheral {
  301. for(CBService *s in [peripheral services]) {
  302. for(CBService *ss in [s includedServices]) {
  303. if([[ss.UUID UUIDString] isEqualToString:[secondaryService.UUID UUIDString]]) {
  304. return s;
  305. }
  306. }
  307. }
  308. return nil;
  309. }
  310. - (CBDescriptor*)findCCCDescriptor:(CBCharacteristic*)characteristic {
  311. for(CBDescriptor *d in characteristic.descriptors) {
  312. if([d.UUID.UUIDString isEqualToString:@"2902"]) {
  313. return d;
  314. }
  315. }
  316. return nil;
  317. }
  318. - (CBService*)getServiceFromArray:(NSString*)uuidString array:(NSArray<CBService*>*)array {
  319. for(CBService *s in array) {
  320. if([[s UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
  321. return s;
  322. }
  323. }
  324. return nil;
  325. }
  326. - (CBCharacteristic*)getCharacteristicFromArray:(NSString*)uuidString array:(NSArray<CBCharacteristic*>*)array {
  327. for(CBCharacteristic *c in array) {
  328. if([[c UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
  329. return c;
  330. }
  331. }
  332. return nil;
  333. }
  334. - (CBDescriptor*)getDescriptorFromArray:(NSString*)uuidString array:(NSArray<CBDescriptor*>*)array {
  335. for(CBDescriptor *d in array) {
  336. if([[d UUID] isEqual:[CBUUID UUIDWithString:uuidString]]) {
  337. return d;
  338. }
  339. }
  340. return nil;
  341. }
  342. //
  343. // CBCentralManagerDelegate methods
  344. //
  345. - (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
  346. if(_stateStreamHandler.sink != nil) {
  347. FlutterStandardTypedData *data = [self toFlutterData:[self toBluetoothStateProto:self->_centralManager.state]];
  348. self.stateStreamHandler.sink(data);
  349. }
  350. }
  351. - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
  352. [self.scannedPeripherals setObject:peripheral
  353. forKey:[[peripheral identifier] UUIDString]];
  354. ProtosScanResult *result = [self toScanResultProto:peripheral advertisementData:advertisementData RSSI:RSSI];
  355. [_channel invokeMethod:@"ScanResult" arguments:[self toFlutterData:result]];
  356. }
  357. - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
  358. NSLog(@"didConnectPeripheral");
  359. // Register self as delegate for peripheral
  360. peripheral.delegate = self;
  361. // Send initial mtu size
  362. uint32_t mtu = [self getMtu:peripheral];
  363. [_channel invokeMethod:@"MtuSize" arguments:[self toFlutterData:[self toMtuSizeResponseProto:peripheral mtu:mtu]]];
  364. // Send connection state
  365. [_channel invokeMethod:@"DeviceState" arguments:[self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]];
  366. }
  367. - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
  368. NSLog(@"didDisconnectPeripheral");
  369. // Unregister self as delegate for peripheral, not working #42
  370. peripheral.delegate = nil;
  371. // Send connection state
  372. [_channel invokeMethod:@"DeviceState" arguments:[self toFlutterData:[self toDeviceStateProto:peripheral state:peripheral.state]]];
  373. }
  374. - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
  375. // TODO:?
  376. }
  377. //
  378. // CBPeripheralDelegate methods
  379. //
  380. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
  381. NSLog(@"didDiscoverServices");
  382. // Send negotiated mtu size
  383. uint32_t mtu = [self getMtu:peripheral];
  384. [_channel invokeMethod:@"MtuSize" arguments:[self toFlutterData:[self toMtuSizeResponseProto:peripheral mtu:mtu]]];
  385. // Loop through and discover characteristics and secondary services
  386. [_servicesThatNeedDiscovered addObjectsFromArray:peripheral.services];
  387. for(CBService *s in [peripheral services]) {
  388. NSLog(@"Found service: %@", [s.UUID UUIDString]);
  389. [peripheral discoverCharacteristics:nil forService:s];
  390. // [peripheral discoverIncludedServices:nil forService:s]; // Secondary services in the future (#8)
  391. }
  392. }
  393. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
  394. NSLog(@"didDiscoverCharacteristicsForService");
  395. // Loop through and discover descriptors for characteristics
  396. [_servicesThatNeedDiscovered removeObject:service];
  397. [_characteristicsThatNeedDiscovered addObjectsFromArray:service.characteristics];
  398. for(CBCharacteristic *c in [service characteristics]) {
  399. [peripheral discoverDescriptorsForCharacteristic:c];
  400. }
  401. }
  402. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  403. NSLog(@"didDiscoverDescriptorsForCharacteristic");
  404. [_characteristicsThatNeedDiscovered removeObject:characteristic];
  405. if(_servicesThatNeedDiscovered.count > 0 || _characteristicsThatNeedDiscovered.count > 0) {
  406. // Still discovering
  407. return;
  408. }
  409. // Send updated tree
  410. ProtosDiscoverServicesResult *result = [self toServicesResultProto:peripheral];
  411. [_channel invokeMethod:@"DiscoverServicesResult" arguments:[self toFlutterData:result]];
  412. }
  413. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error {
  414. NSLog(@"didDiscoverIncludedServicesForService");
  415. // Loop through and discover characteristics for secondary services
  416. for(CBService *ss in [service includedServices]) {
  417. [peripheral discoverCharacteristics:nil forService:ss];
  418. }
  419. }
  420. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  421. NSLog(@"didUpdateValueForCharacteristic %@", [peripheral.identifier UUIDString]);
  422. ProtosReadCharacteristicResponse *result = [[ProtosReadCharacteristicResponse alloc] init];
  423. [result setRemoteId:[peripheral.identifier UUIDString]];
  424. [result setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  425. [_channel invokeMethod:@"ReadCharacteristicResponse" arguments:[self toFlutterData:result]];
  426. // on iOS, this method also handles notification values
  427. ProtosOnCharacteristicChanged *onChangedResult = [[ProtosOnCharacteristicChanged alloc] init];
  428. [onChangedResult setRemoteId:[peripheral.identifier UUIDString]];
  429. [onChangedResult setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  430. [_channel invokeMethod:@"OnCharacteristicChanged" arguments:[self toFlutterData:onChangedResult]];
  431. }
  432. - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  433. NSLog(@"didWriteValueForCharacteristic");
  434. ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] init];
  435. [request setRemoteId:[peripheral.identifier UUIDString]];
  436. [request setCharacteristicUuid:[characteristic.UUID fullUUIDString]];
  437. [request setServiceUuid:[characteristic.service.UUID fullUUIDString]];
  438. ProtosWriteCharacteristicResponse *result = [[ProtosWriteCharacteristicResponse alloc] init];
  439. [result setRequest:request];
  440. [result setSuccess:(error == nil)];
  441. [_channel invokeMethod:@"WriteCharacteristicResponse" arguments:[self toFlutterData:result]];
  442. }
  443. - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  444. NSLog(@"didUpdateNotificationStateForCharacteristic");
  445. // Read CCC descriptor of characteristic
  446. CBDescriptor *cccd = [self findCCCDescriptor:characteristic];
  447. if(cccd == nil || error != nil) {
  448. // Send error
  449. ProtosSetNotificationResponse *response = [[ProtosSetNotificationResponse alloc] init];
  450. [response setRemoteId:[peripheral.identifier UUIDString]];
  451. [response setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  452. [response setSuccess:false];
  453. [_channel invokeMethod:@"SetNotificationResponse" arguments:[self toFlutterData:response]];
  454. return;
  455. }
  456. // Request a read
  457. [peripheral readValueForDescriptor:cccd];
  458. }
  459. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
  460. ProtosReadDescriptorRequest *q = [[ProtosReadDescriptorRequest alloc] init];
  461. [q setRemoteId:[peripheral.identifier UUIDString]];
  462. [q setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
  463. [q setDescriptorUuid:[descriptor.UUID fullUUIDString]];
  464. if([descriptor.characteristic.service isPrimary]) {
  465. [q setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
  466. } else {
  467. [q setSecondaryServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
  468. CBService *primaryService = [self findPrimaryService:[descriptor.characteristic service] peripheral:[descriptor.characteristic.service peripheral]];
  469. [q setServiceUuid:[primaryService.UUID fullUUIDString]];
  470. }
  471. ProtosReadDescriptorResponse *result = [[ProtosReadDescriptorResponse alloc] init];
  472. [result setRequest:q];
  473. int value = [descriptor.value intValue];
  474. [result setValue:[NSData dataWithBytes:&value length:sizeof(value)]];
  475. [_channel invokeMethod:@"ReadDescriptorResponse" arguments:[self toFlutterData:result]];
  476. // If descriptor is CCCD, send a SetNotificationResponse in case anything is awaiting
  477. if([descriptor.UUID.UUIDString isEqualToString:@"2902"]){
  478. ProtosSetNotificationResponse *response = [[ProtosSetNotificationResponse alloc] init];
  479. [response setRemoteId:[peripheral.identifier UUIDString]];
  480. [response setCharacteristic:[self toCharacteristicProto:peripheral characteristic:descriptor.characteristic]];
  481. [response setSuccess:true];
  482. [_channel invokeMethod:@"SetNotificationResponse" arguments:[self toFlutterData:response]];
  483. }
  484. }
  485. - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
  486. ProtosWriteDescriptorRequest *request = [[ProtosWriteDescriptorRequest alloc] init];
  487. [request setRemoteId:[peripheral.identifier UUIDString]];
  488. [request setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
  489. [request setDescriptorUuid:[descriptor.UUID fullUUIDString]];
  490. if([descriptor.characteristic.service isPrimary]) {
  491. [request setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
  492. } else {
  493. [request setSecondaryServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
  494. CBService *primaryService = [self findPrimaryService:[descriptor.characteristic service] peripheral:[descriptor.characteristic.service peripheral]];
  495. [request setServiceUuid:[primaryService.UUID fullUUIDString]];
  496. }
  497. ProtosWriteDescriptorResponse *result = [[ProtosWriteDescriptorResponse alloc] init];
  498. [result setRequest:request];
  499. [result setSuccess:(error == nil)];
  500. [_channel invokeMethod:@"WriteDescriptorResponse" arguments:[self toFlutterData:result]];
  501. }
  502. //
  503. // Proto Helper methods
  504. //
  505. - (FlutterStandardTypedData*)toFlutterData:(GPBMessage*)proto {
  506. FlutterStandardTypedData *data = [FlutterStandardTypedData typedDataWithBytes:[[proto data] copy]];
  507. return data;
  508. }
  509. - (ProtosBluetoothState*)toBluetoothStateProto:(CBManagerState)state {
  510. ProtosBluetoothState *result = [[ProtosBluetoothState alloc] init];
  511. switch(state) {
  512. case CBManagerStateResetting:
  513. [result setState:ProtosBluetoothState_State_TurningOn];
  514. break;
  515. case CBManagerStateUnsupported:
  516. [result setState:ProtosBluetoothState_State_Unavailable];
  517. break;
  518. case CBManagerStateUnauthorized:
  519. [result setState:ProtosBluetoothState_State_Unauthorized];
  520. break;
  521. case CBManagerStatePoweredOff:
  522. [result setState:ProtosBluetoothState_State_Off];
  523. break;
  524. case CBManagerStatePoweredOn:
  525. [result setState:ProtosBluetoothState_State_On];
  526. break;
  527. default:
  528. [result setState:ProtosBluetoothState_State_Unknown];
  529. break;
  530. }
  531. return result;
  532. }
  533. - (ProtosScanResult*)toScanResultProto:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
  534. ProtosScanResult *result = [[ProtosScanResult alloc] init];
  535. [result setDevice:[self toDeviceProto:peripheral]];
  536. [result setRssi:[RSSI intValue]];
  537. ProtosAdvertisementData *ads = [[ProtosAdvertisementData alloc] init];
  538. [ads setConnectable:[advertisementData[CBAdvertisementDataIsConnectable] boolValue]];
  539. [ads setLocalName:advertisementData[CBAdvertisementDataLocalNameKey]];
  540. // Tx Power Level
  541. NSNumber *txPower = advertisementData[CBAdvertisementDataTxPowerLevelKey];
  542. if(txPower != nil) {
  543. ProtosInt32Value *txPowerWrapper = [[ProtosInt32Value alloc] init];
  544. [txPowerWrapper setValue:[txPower intValue]];
  545. [ads setTxPowerLevel:txPowerWrapper];
  546. }
  547. // Manufacturer Specific Data
  548. NSData *manufData = advertisementData[CBAdvertisementDataManufacturerDataKey];
  549. if(manufData.length > 2) {
  550. unsigned short manufacturerId;
  551. [manufData getBytes:&manufacturerId length:2];
  552. [[ads manufacturerData] setObject:[manufData subdataWithRange:NSMakeRange(2, manufData.length - 2)] forKey:manufacturerId];
  553. }
  554. // Service Data
  555. NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
  556. for (CBUUID *uuid in serviceData) {
  557. [[ads serviceData] setObject:serviceData[uuid] forKey:uuid.UUIDString];
  558. }
  559. // Service Uuids
  560. NSArray *serviceUuids = advertisementData[CBAdvertisementDataServiceUUIDsKey];
  561. for (CBUUID *uuid in serviceUuids) {
  562. [[ads serviceUuidsArray] addObject:uuid.UUIDString];
  563. }
  564. [result setAdvertisementData:ads];
  565. return result;
  566. }
  567. - (ProtosBluetoothDevice*)toDeviceProto:(CBPeripheral *)peripheral {
  568. ProtosBluetoothDevice *result = [[ProtosBluetoothDevice alloc] init];
  569. [result setName:[peripheral name]];
  570. [result setRemoteId:[[peripheral identifier] UUIDString]];
  571. [result setType:ProtosBluetoothDevice_Type_Le]; // TODO: Does iOS differentiate?
  572. return result;
  573. }
  574. - (ProtosDeviceStateResponse*)toDeviceStateProto:(CBPeripheral *)peripheral state:(CBPeripheralState)state {
  575. ProtosDeviceStateResponse *result = [[ProtosDeviceStateResponse alloc] init];
  576. switch(state) {
  577. case CBPeripheralStateDisconnected:
  578. [result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Disconnected];
  579. break;
  580. case CBPeripheralStateConnecting:
  581. [result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Connecting];
  582. break;
  583. case CBPeripheralStateConnected:
  584. [result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Connected];
  585. break;
  586. case CBPeripheralStateDisconnecting:
  587. [result setState:ProtosDeviceStateResponse_BluetoothDeviceState_Disconnecting];
  588. break;
  589. }
  590. [result setRemoteId:[[peripheral identifier] UUIDString]];
  591. return result;
  592. }
  593. - (ProtosDiscoverServicesResult*)toServicesResultProto:(CBPeripheral *)peripheral {
  594. ProtosDiscoverServicesResult *result = [[ProtosDiscoverServicesResult alloc] init];
  595. [result setRemoteId:[peripheral.identifier UUIDString]];
  596. NSMutableArray *servicesProtos = [NSMutableArray new];
  597. for(CBService *s in [peripheral services]) {
  598. [servicesProtos addObject:[self toServiceProto:peripheral service:s]];
  599. }
  600. [result setServicesArray:servicesProtos];
  601. return result;
  602. }
  603. - (ProtosConnectedDevicesResponse*)toConnectedDeviceResponseProto:(NSArray<CBPeripheral*>*)periphs {
  604. ProtosConnectedDevicesResponse *result = [[ProtosConnectedDevicesResponse alloc] init];
  605. NSMutableArray *deviceProtos = [NSMutableArray new];
  606. for(CBPeripheral *p in periphs) {
  607. [deviceProtos addObject:[self toDeviceProto:p]];
  608. }
  609. [result setDevicesArray:deviceProtos];
  610. return result;
  611. }
  612. - (ProtosBluetoothService*)toServiceProto:(CBPeripheral *)peripheral service:(CBService *)service {
  613. ProtosBluetoothService *result = [[ProtosBluetoothService alloc] init];
  614. NSLog(@"peripheral uuid:%@", [peripheral.identifier UUIDString]);
  615. NSLog(@"service uuid:%@", [service.UUID fullUUIDString]);
  616. [result setRemoteId:[peripheral.identifier UUIDString]];
  617. [result setUuid:[service.UUID fullUUIDString]];
  618. [result setIsPrimary:[service isPrimary]];
  619. // Characteristic Array
  620. NSMutableArray *characteristicProtos = [NSMutableArray new];
  621. for(CBCharacteristic *c in [service characteristics]) {
  622. [characteristicProtos addObject:[self toCharacteristicProto:peripheral characteristic:c]];
  623. }
  624. [result setCharacteristicsArray:characteristicProtos];
  625. // Included Services Array
  626. NSMutableArray *includedServicesProtos = [NSMutableArray new];
  627. for(CBService *s in [service includedServices]) {
  628. [includedServicesProtos addObject:[self toServiceProto:peripheral service:s]];
  629. }
  630. [result setIncludedServicesArray:includedServicesProtos];
  631. return result;
  632. }
  633. - (ProtosBluetoothCharacteristic*)toCharacteristicProto:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic {
  634. ProtosBluetoothCharacteristic *result = [[ProtosBluetoothCharacteristic alloc] init];
  635. [result setUuid:[characteristic.UUID fullUUIDString]];
  636. [result setRemoteId:[peripheral.identifier UUIDString]];
  637. [result setProperties:[self toCharacteristicPropsProto:characteristic.properties]];
  638. [result setValue:[characteristic value]];
  639. NSLog(@"uuid: %@ value: %@", [characteristic.UUID fullUUIDString], [characteristic value]);
  640. NSMutableArray *descriptorProtos = [NSMutableArray new];
  641. for(CBDescriptor *d in [characteristic descriptors]) {
  642. [descriptorProtos addObject:[self toDescriptorProto:peripheral descriptor:d]];
  643. }
  644. [result setDescriptorsArray:descriptorProtos];
  645. if([characteristic.service isPrimary]) {
  646. [result setServiceUuid:[characteristic.service.UUID fullUUIDString]];
  647. } else {
  648. // Reverse search to find service and secondary service UUID
  649. [result setSecondaryServiceUuid:[characteristic.service.UUID fullUUIDString]];
  650. CBService *primaryService = [self findPrimaryService:[characteristic service] peripheral:[characteristic.service peripheral]];
  651. [result setServiceUuid:[primaryService.UUID fullUUIDString]];
  652. }
  653. return result;
  654. }
  655. - (ProtosBluetoothDescriptor*)toDescriptorProto:(CBPeripheral *)peripheral descriptor:(CBDescriptor *)descriptor {
  656. ProtosBluetoothDescriptor *result = [[ProtosBluetoothDescriptor alloc] init];
  657. [result setUuid:[descriptor.UUID fullUUIDString]];
  658. [result setRemoteId:[peripheral.identifier UUIDString]];
  659. [result setCharacteristicUuid:[descriptor.characteristic.UUID fullUUIDString]];
  660. [result setServiceUuid:[descriptor.characteristic.service.UUID fullUUIDString]];
  661. int value = [descriptor.value intValue];
  662. [result setValue:[NSData dataWithBytes:&value length:sizeof(value)]];
  663. return result;
  664. }
  665. - (ProtosCharacteristicProperties*)toCharacteristicPropsProto:(CBCharacteristicProperties)props {
  666. ProtosCharacteristicProperties *result = [[ProtosCharacteristicProperties alloc] init];
  667. [result setBroadcast:(props & CBCharacteristicPropertyBroadcast) != 0];
  668. [result setRead:(props & CBCharacteristicPropertyRead) != 0];
  669. [result setWriteWithoutResponse:(props & CBCharacteristicPropertyWriteWithoutResponse) != 0];
  670. [result setWrite:(props & CBCharacteristicPropertyWrite) != 0];
  671. [result setNotify:(props & CBCharacteristicPropertyNotify) != 0];
  672. [result setIndicate:(props & CBCharacteristicPropertyIndicate) != 0];
  673. [result setAuthenticatedSignedWrites:(props & CBCharacteristicPropertyAuthenticatedSignedWrites) != 0];
  674. [result setExtendedProperties:(props & CBCharacteristicPropertyExtendedProperties) != 0];
  675. [result setNotifyEncryptionRequired:(props & CBCharacteristicPropertyNotifyEncryptionRequired) != 0];
  676. [result setIndicateEncryptionRequired:(props & CBCharacteristicPropertyIndicateEncryptionRequired) != 0];
  677. return result;
  678. }
  679. - (ProtosMtuSizeResponse*)toMtuSizeResponseProto:(CBPeripheral *)peripheral mtu:(uint32_t)mtu {
  680. ProtosMtuSizeResponse *result = [[ProtosMtuSizeResponse alloc] init];
  681. [result setRemoteId:[[peripheral identifier] UUIDString]];
  682. [result setMtu:mtu];
  683. return result;
  684. }
  685. - (void)log:(LogLevel)level format:(NSString *)format, ... {
  686. if(level <= _logLevel) {
  687. va_list args;
  688. va_start(args, format);
  689. // NSString* formattedMessage = [[NSString alloc] initWithFormat:format arguments:args];
  690. NSLog(format, args);
  691. va_end(args);
  692. }
  693. }
  694. - (uint32_t)getMtu:(CBPeripheral *)peripheral {
  695. if (@available(iOS 9.0, *)) {
  696. // Which type should we use? (issue #365)
  697. return (uint32_t)[peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse];
  698. } else {
  699. // Fallback to minimum on earlier versions. (issue #364)
  700. return 20;
  701. }
  702. }
  703. @end
  704. @implementation FlutterBlueStreamHandler
  705. - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink {
  706. self.sink = eventSink;
  707. return nil;
  708. }
  709. - (FlutterError*)onCancelWithArguments:(id)arguments {
  710. self.sink = nil;
  711. return nil;
  712. }
  713. @end