Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / src / darwin / CHIPTool / CHIPTool / View Controllers / QRCode / QRCodeViewController.m
1 /**
2  *
3  *    Copyright (c) 2020 Project CHIP Authors
4  *
5  *    Licensed under the Apache License, Version 2.0 (the "License");
6  *    you may not use this file except in compliance with the License.
7  *    You may obtain a copy of the License at
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *    Unless required by applicable law or agreed to in writing, software
12  *    distributed under the License is distributed on an "AS IS" BASIS,
13  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *    See the License for the specific language governing permissions and
15  *    limitations under the License.
16  */
17 // module header
18 #import "QRCodeViewController.h"
19
20 // local imports
21 #import "CHIPUIViewUtils.h"
22 #import "DefaultsUtils.h"
23 #import "DeviceSelector.h"
24 #import <CHIP/CHIP.h>
25
26 // system imports
27 #import <AVFoundation/AVFoundation.h>
28
29 #define INDICATOR_DELAY 0.5 * NSEC_PER_SEC
30 #define ERROR_DISPLAY_TIME 2.0 * NSEC_PER_SEC
31 #define QR_CODE_FREEZE 1.0 * NSEC_PER_SEC
32
33 // The expected Vendor ID for CHIP demos
34 // 0x235A: Chip's Vendor Id
35 #define EXAMPLE_VENDOR_ID 0x235A
36
37 #define EXAMPLE_VENDOR_TAG_IP 1
38 #define MAX_IP_LEN 46
39
40 #define NETWORK_CHIP_PREFIX @"CHIP-"
41
42 #define NOT_APPLICABLE_STRING @"N/A"
43
44 @interface QRCodeViewController ()
45
46 @property (nonatomic, strong) AVCaptureSession * captureSession;
47 @property (nonatomic, strong) AVCaptureVideoPreviewLayer * videoPreviewLayer;
48
49 @property (strong, nonatomic) UIView * qrCodeViewPreview;
50
51 @property (strong, nonatomic) UITextField * manualCodeTextField;
52 @property (strong, nonatomic) UIButton * doneManualCodeButton;
53
54 @property (strong, nonatomic) UIButton * nfcScanButton;
55 @property (readwrite) BOOL sessionIsActive;
56
57 @property (strong, nonatomic) UIView * setupPayloadView;
58 @property (strong, nonatomic) UILabel * manualCodeLabel;
59 @property (strong, nonatomic) UIButton * resetButton;
60 @property (strong, nonatomic) UILabel * versionLabel;
61 @property (strong, nonatomic) UILabel * discriminatorLabel;
62 @property (strong, nonatomic) UILabel * setupPinCodeLabel;
63 @property (strong, nonatomic) UILabel * rendezVousInformation;
64 @property (strong, nonatomic) UILabel * vendorID;
65 @property (strong, nonatomic) UILabel * productID;
66 @property (strong, nonatomic) UILabel * serialNumber;
67
68 @property (strong, nonatomic) UIActivityIndicatorView * activityIndicator;
69 @property (strong, nonatomic) UILabel * errorLabel;
70
71 @property (readwrite) CHIPDeviceController * chipController;
72
73 @property (strong, nonatomic) NFCNDEFReaderSession * session;
74 @property (strong, nonatomic) CHIPSetupPayload * setupPayload;
75 @property (strong, nonatomic) DeviceSelector * deviceList;
76 @end
77
78 @implementation QRCodeViewController {
79     dispatch_queue_t _captureSessionQueue;
80 }
81
82 // MARK: UI Setup
83
84 - (void)changeNavBarButtonToCamera
85 {
86     UIBarButtonItem * camera = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
87                                                                              target:self
88                                                                              action:@selector(startScanningQRCode:)];
89     self.navigationItem.rightBarButtonItem = camera;
90 }
91
92 - (void)changeNavBarButtonToCancel
93 {
94     UIBarButtonItem * cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
95                                                                              target:self
96                                                                              action:@selector(stopScanningQRCode:)];
97     self.navigationItem.rightBarButtonItem = cancel;
98 }
99
100 - (void)setupUI
101 {
102     self.view.backgroundColor = UIColor.whiteColor;
103
104     // Setup nav bar button
105     [self changeNavBarButtonToCamera];
106
107     // Title
108     UILabel * titleLabel = [CHIPUIViewUtils addTitle:@"QR Code Parser" toView:self.view];
109
110     // stack view
111     UIStackView * stackView = [UIStackView new];
112     stackView.axis = UILayoutConstraintAxisVertical;
113     stackView.distribution = UIStackViewDistributionFill;
114     stackView.alignment = UIStackViewAlignmentLeading;
115     stackView.spacing = 15;
116     [self.view addSubview:stackView];
117
118     stackView.translatesAutoresizingMaskIntoConstraints = false;
119     [stackView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:30].active = YES;
120     [stackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES;
121     [stackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
122
123     // Manual entry view
124     _manualCodeTextField = [UITextField new];
125     _doneManualCodeButton = [UIButton new];
126     [_doneManualCodeButton addTarget:self action:@selector(enteredManualCode:) forControlEvents:UIControlEventTouchUpInside];
127     _manualCodeTextField.placeholder = @"Manual Code";
128     _manualCodeTextField.keyboardType = UIKeyboardTypeNumberPad;
129     [_doneManualCodeButton setTitle:@"Go" forState:UIControlStateNormal];
130     UIView * manualEntryView = [CHIPUIViewUtils viewWithUITextField:_manualCodeTextField button:_doneManualCodeButton];
131     [stackView addArrangedSubview:manualEntryView];
132
133     manualEntryView.translatesAutoresizingMaskIntoConstraints = false;
134     [manualEntryView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES;
135     [manualEntryView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
136     [manualEntryView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:30].active = YES;
137
138     _nfcScanButton = [UIButton new];
139     [_nfcScanButton setTitle:@"Scan NFC Tag" forState:UIControlStateNormal];
140     [_nfcScanButton addTarget:self action:@selector(startScanningNFCTags:) forControlEvents:UIControlEventTouchDown];
141     _nfcScanButton.titleLabel.font = [UIFont systemFontOfSize:17];
142     _nfcScanButton.titleLabel.textColor = [UIColor blackColor];
143     _nfcScanButton.layer.cornerRadius = 5;
144     _nfcScanButton.clipsToBounds = YES;
145     _nfcScanButton.backgroundColor = UIColor.systemBlueColor;
146     [stackView addArrangedSubview:_nfcScanButton];
147
148     _nfcScanButton.translatesAutoresizingMaskIntoConstraints = false;
149     [_nfcScanButton.leadingAnchor constraintEqualToAnchor:stackView.leadingAnchor].active = YES;
150     [_nfcScanButton.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor].active = YES;
151     [_nfcScanButton.heightAnchor constraintEqualToConstant:40].active = YES;
152
153     _deviceList = [DeviceSelector new];
154     [_deviceList setEnabled:NO];
155
156     UILabel * deviceIDLabel = [UILabel new];
157     deviceIDLabel.text = @"Paired Devices:";
158     UIView * deviceIDView = [CHIPUIViewUtils viewWithLabel:deviceIDLabel textField:_deviceList];
159     [stackView addArrangedSubview:deviceIDView];
160
161     deviceIDView.translatesAutoresizingMaskIntoConstraints = false;
162     [deviceIDView.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor].active = true;
163
164     // Results view
165     _setupPayloadView = [UIView new];
166     [self.view addSubview:_setupPayloadView];
167
168     _setupPayloadView.translatesAutoresizingMaskIntoConstraints = false;
169     [_setupPayloadView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor constant:10].active = YES;
170     [_setupPayloadView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES;
171     [_setupPayloadView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
172     [_setupPayloadView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-30].active = YES;
173
174     [self addViewsToSetupPayloadView];
175
176     // activity indicator
177     _activityIndicator = [UIActivityIndicatorView new];
178     [self.view addSubview:_activityIndicator];
179
180     _activityIndicator.translatesAutoresizingMaskIntoConstraints = false;
181     [_activityIndicator.centerXAnchor constraintEqualToAnchor:_setupPayloadView.centerXAnchor].active = YES;
182     [_activityIndicator.centerYAnchor constraintEqualToAnchor:_setupPayloadView.centerYAnchor].active = YES;
183     _activityIndicator.color = UIColor.blackColor;
184
185     // QRCode preview
186     _qrCodeViewPreview = [UIView new];
187     [self.view addSubview:_qrCodeViewPreview];
188
189     _qrCodeViewPreview.translatesAutoresizingMaskIntoConstraints = false;
190     [_qrCodeViewPreview.topAnchor constraintEqualToAnchor:_nfcScanButton.bottomAnchor constant:30].active = YES;
191     [_qrCodeViewPreview.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES;
192     [_qrCodeViewPreview.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
193     [_qrCodeViewPreview.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-50].active = YES;
194
195     // Error label
196     _errorLabel = [UILabel new];
197     _errorLabel.text = @"Error Text";
198     _errorLabel.textColor = UIColor.blackColor;
199     _errorLabel.font = [UIFont systemFontOfSize:17];
200     [stackView addArrangedSubview:_errorLabel];
201
202     _errorLabel.translatesAutoresizingMaskIntoConstraints = false;
203     [_errorLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES;
204     [_errorLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
205
206     // Reset button
207     _resetButton = [UIButton new];
208     [_resetButton setTitle:@"Reset" forState:UIControlStateNormal];
209     [_resetButton addTarget:self action:@selector(resetView:) forControlEvents:UIControlEventTouchUpInside];
210     _resetButton.backgroundColor = UIColor.systemBlueColor;
211     _resetButton.titleLabel.font = [UIFont systemFontOfSize:17];
212     _resetButton.titleLabel.textColor = [UIColor whiteColor];
213     _resetButton.layer.cornerRadius = 5;
214     _resetButton.clipsToBounds = YES;
215     [self.view addSubview:_resetButton];
216
217     _resetButton.translatesAutoresizingMaskIntoConstraints = false;
218     [_resetButton.widthAnchor constraintEqualToConstant:60].active = YES;
219     [_resetButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-30].active = YES;
220     [_resetButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES;
221 }
222
223 - (void)addViewsToSetupPayloadView
224 {
225     // manual entry field
226     _manualCodeLabel = [UILabel new];
227     _manualCodeLabel.text = @"00000000000000000000";
228     _manualCodeLabel.textColor = UIColor.systemBlueColor;
229     _manualCodeLabel.font = [UIFont systemFontOfSize:17];
230     _manualCodeLabel.textAlignment = NSTextAlignmentRight;
231     [_setupPayloadView addSubview:_manualCodeLabel];
232
233     _manualCodeLabel.translatesAutoresizingMaskIntoConstraints = false;
234     [_manualCodeLabel.topAnchor constraintEqualToAnchor:_setupPayloadView.topAnchor].active = YES;
235     [_manualCodeLabel.trailingAnchor constraintEqualToAnchor:_setupPayloadView.trailingAnchor].active = YES;
236
237     // Results scroll view
238     UIScrollView * resultsScrollView = [UIScrollView new];
239     [_setupPayloadView addSubview:resultsScrollView];
240
241     resultsScrollView.translatesAutoresizingMaskIntoConstraints = false;
242     [resultsScrollView.topAnchor constraintEqualToAnchor:_manualCodeLabel.bottomAnchor constant:10].active = YES;
243     [resultsScrollView.leadingAnchor constraintEqualToAnchor:_setupPayloadView.leadingAnchor].active = YES;
244     [resultsScrollView.trailingAnchor constraintEqualToAnchor:_setupPayloadView.trailingAnchor].active = YES;
245     [resultsScrollView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-20].active = YES;
246
247     UIStackView * parserResultsView = [UIStackView new];
248     parserResultsView.axis = UILayoutConstraintAxisVertical;
249     parserResultsView.distribution = UIStackViewDistributionEqualSpacing;
250     parserResultsView.alignment = UIStackViewAlignmentLeading;
251     parserResultsView.spacing = 15;
252     [resultsScrollView addSubview:parserResultsView];
253
254     parserResultsView.translatesAutoresizingMaskIntoConstraints = false;
255     [parserResultsView.topAnchor constraintEqualToAnchor:resultsScrollView.topAnchor].active = YES;
256     [parserResultsView.leadingAnchor constraintEqualToAnchor:resultsScrollView.leadingAnchor].active = YES;
257     [parserResultsView.trailingAnchor constraintEqualToAnchor:resultsScrollView.trailingAnchor].active = YES;
258     [parserResultsView.bottomAnchor constraintEqualToAnchor:resultsScrollView.bottomAnchor].active = YES;
259     [self addResultsUIToStackView:parserResultsView];
260 }
261
262 - (void)addResultsUIToStackView:(UIStackView *)stackView
263 {
264     NSArray<NSString *> * resultLabelTexts =
265         @[ @"version", @"discriminator", @"setup pin code", @"rendez vous information", @"vendor ID", @"product ID", @"serial #" ];
266     _versionLabel = [UILabel new];
267     _discriminatorLabel = [UILabel new];
268     _setupPinCodeLabel = [UILabel new];
269     _rendezVousInformation = [UILabel new];
270     _vendorID = [UILabel new];
271     _productID = [UILabel new];
272     _serialNumber = [UILabel new];
273     NSArray<UILabel *> * resultLabels =
274         @[ _versionLabel, _discriminatorLabel, _setupPinCodeLabel, _rendezVousInformation, _vendorID, _productID, _serialNumber ];
275     for (int i = 0; i < resultLabels.count && i < resultLabels.count; i++) {
276         UILabel * label = [UILabel new];
277         label.text = [resultLabelTexts objectAtIndex:i];
278         UILabel * result = [resultLabels objectAtIndex:i];
279         result.text = @"N/A";
280         UIStackView * labelStackView = [CHIPUIViewUtils stackViewWithLabel:label result:result];
281         labelStackView.translatesAutoresizingMaskIntoConstraints = false;
282         [stackView addArrangedSubview:labelStackView];
283     }
284 }
285
286 // MARK: UIViewController methods
287
288 - (void)viewDidDisappear:(BOOL)animated
289 {
290     [super viewDidDisappear:animated];
291     [_session invalidateSession];
292     _session = nil;
293 }
294
295 - (void)dismissKeyboard
296 {
297     [_manualCodeTextField resignFirstResponder];
298 }
299
300 - (void)viewDidLoad
301 {
302     [super viewDidLoad];
303     [self setupUI];
304
305     dispatch_queue_t callbackQueue = dispatch_queue_create("com.zigbee.chip.qrcodevc.callback", DISPATCH_QUEUE_SERIAL);
306     self.chipController = InitializeCHIP();
307     [self.chipController setPairingDelegate:self queue:callbackQueue];
308
309     UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)];
310     [self.view addGestureRecognizer:tap];
311
312     [self manualCodeInitialState];
313     [self qrCodeInitialState];
314 }
315
316 // MARK: NFCNDEFReaderSessionDelegate
317
318 - (void)readerSession:(nonnull NFCNDEFReaderSession *)session didDetectNDEFs:(nonnull NSArray<NFCNDEFMessage *> *)messages
319 {
320     [_session invalidateSession];
321     NSString * errorMessage;
322     if (messages.count == 1) {
323         for (NFCNDEFMessage * message in messages) {
324             if (message.records.count == 1) {
325                 for (NFCNDEFPayload * payload in message.records) {
326                     NSString * payloadType = [[NSString alloc] initWithData:payload.type encoding:NSUTF8StringEncoding];
327                     if ([payloadType isEqualToString:@"U"]) {
328                         NSURL * payloadURI = [payload wellKnownTypeURIPayload];
329                         NSLog(@"Payload text:%@", payloadURI);
330                         if (payloadURI) {
331                             /* CHIP Issue #415
332                              Once #415 goes in, there will b no need to replace _ with spaces.
333                             */
334                             NSString * qrCode = [[payloadURI absoluteString] stringByReplacingOccurrencesOfString:@"_"
335                                                                                                        withString:@" "];
336                             NSLog(@"Scanned code string:%@", qrCode);
337                             [self scannedQRCode:qrCode];
338                         }
339                     } else {
340                         errorMessage = @"Record must be of type text.";
341                     }
342                 }
343             } else {
344                 errorMessage = @"Only one record in NFC tag is accepted.";
345             }
346         }
347     } else {
348         errorMessage = @"Only one message in NFC tag is accepted.";
349     }
350     if ([errorMessage length] > 0) {
351         NSError * error = [[NSError alloc] initWithDomain:@"com.chiptool.nfctagscanning"
352                                                      code:1
353                                                  userInfo:@{ NSLocalizedDescriptionKey : errorMessage }];
354         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_NOW), dispatch_get_main_queue(), ^{
355             [self showError:error];
356         });
357     }
358 }
359
360 - (void)readerSession:(nonnull NFCNDEFReaderSession *)session didInvalidateWithError:(nonnull NSError *)error
361 {
362     NSLog(@"If no NFC reading UI is appearing, target may me missing the appropriate capability. Turn on Near Field Communication "
363           @"Tag Reading under the Capabilities tab for the project’s target. A paid developer account is needed for this.");
364     _session = nil;
365 }
366
367 // MARK: CHIPDevicePairingDelegate
368 - (void)onNetworkCredentialsRequested:(CHIPNetworkCredentialType)type
369 {
370     NSLog(@"Network credential requested for pairing for type %lu", (unsigned long) type);
371     if (type == kNetworkCredentialTypeWiFi) {
372         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_NOW), dispatch_get_main_queue(), ^{
373             [self retrieveAndSendWifiCredentials];
374         });
375     } else {
376         NSLog(@"Unsupported credentials requested");
377     }
378 }
379
380 - (void)onPairingComplete:(NSError *)error
381 {
382     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_NOW), dispatch_get_main_queue(), ^{
383         [self->_deviceList refreshDeviceList];
384     });
385 }
386
387 // MARK: UI Helper methods
388
389 - (void)manualCodeInitialState
390 {
391     _setupPayloadView.hidden = YES;
392     _resetButton.hidden = YES;
393     _activityIndicator.hidden = YES;
394     _errorLabel.hidden = YES;
395 }
396
397 - (void)qrCodeInitialState
398 {
399     if ([_captureSession isRunning]) {
400         [_captureSession stopRunning];
401     }
402     if ([_activityIndicator isAnimating]) {
403         [_activityIndicator stopAnimating];
404     }
405     _resetButton.hidden = YES;
406     [self changeNavBarButtonToCamera];
407     _activityIndicator.hidden = YES;
408     _captureSession = nil;
409     [_videoPreviewLayer removeFromSuperlayer];
410 }
411
412 - (void)scanningStartState
413 {
414     [self changeNavBarButtonToCancel];
415     _setupPayloadView.hidden = YES;
416     _resetButton.hidden = YES;
417     _errorLabel.hidden = YES;
418 }
419
420 - (void)manualCodeEnteredStartState
421 {
422     self->_activityIndicator.hidden = NO;
423     [self->_activityIndicator startAnimating];
424     _setupPayloadView.hidden = YES;
425     _resetButton.hidden = YES;
426     _errorLabel.hidden = YES;
427     _manualCodeTextField.text = @"";
428 }
429
430 - (void)postScanningQRCodeState
431 {
432     _captureSession = nil;
433     [self changeNavBarButtonToCamera];
434
435     [_videoPreviewLayer removeFromSuperlayer];
436
437     self->_activityIndicator.hidden = NO;
438     [self->_activityIndicator startAnimating];
439 }
440
441 - (void)showError:(NSError *)error
442 {
443     [self->_activityIndicator stopAnimating];
444     self->_activityIndicator.hidden = YES;
445     self->_manualCodeLabel.hidden = YES;
446     _resetButton.hidden = YES;
447
448     self->_errorLabel.text = error.localizedDescription;
449     self->_errorLabel.hidden = NO;
450     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, ERROR_DISPLAY_TIME), dispatch_get_main_queue(), ^{
451         self->_errorLabel.hidden = YES;
452     });
453 }
454
455 - (void)showPayload:(CHIPSetupPayload *)payload decimalString:(nullable NSString *)decimalString
456 {
457     [self->_activityIndicator stopAnimating];
458     self->_activityIndicator.hidden = YES;
459     self->_errorLabel.hidden = YES;
460     // reset the view and remove any preferences that were stored from a previous scan
461     self->_setupPayloadView.hidden = NO;
462     self->_resetButton.hidden = NO;
463
464     [self updateUIFields:payload decimalString:decimalString];
465     [self parseOptionalData:payload];
466     [self handleRendezVous:payload];
467 }
468
469 - (void)retrieveAndSendWifiCredentials
470 {
471     UIAlertController * alertController =
472         [UIAlertController alertControllerWithTitle:@"Wifi Configuration"
473                                             message:@"Input network SSID and password that your phone is connected to."
474                                      preferredStyle:UIAlertControllerStyleAlert];
475     [alertController addTextFieldWithConfigurationHandler:^(UITextField * textField) {
476         textField.placeholder = @"Network SSID";
477         textField.clearButtonMode = UITextFieldViewModeWhileEditing;
478         textField.borderStyle = UITextBorderStyleRoundedRect;
479
480         NSString * networkSSID = CHIPGetDomainValueForKey(kCHIPToolDefaultsDomain, kNetworkSSIDDefaultsKey);
481         if ([networkSSID length] > 0) {
482             textField.text = networkSSID;
483         }
484     }];
485     [alertController addTextFieldWithConfigurationHandler:^(UITextField * textField) {
486         [textField setSecureTextEntry:YES];
487         textField.placeholder = @"Password";
488         textField.clearButtonMode = UITextFieldViewModeWhileEditing;
489         textField.borderStyle = UITextBorderStyleRoundedRect;
490         textField.secureTextEntry = YES;
491
492         NSString * networkPassword = CHIPGetDomainValueForKey(kCHIPToolDefaultsDomain, kNetworkPasswordDefaultsKey);
493         if ([networkPassword length] > 0) {
494             textField.text = networkPassword;
495         }
496     }];
497     [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
498                                                         style:UIAlertActionStyleDefault
499                                                       handler:^(UIAlertAction * action) {
500                                                       }]];
501
502     __weak typeof(self) weakSelf = self;
503     [alertController
504         addAction:[UIAlertAction actionWithTitle:@"Send"
505                                            style:UIAlertActionStyleDefault
506                                          handler:^(UIAlertAction * action) {
507                                              typeof(self) strongSelf = weakSelf;
508                                              if (strongSelf) {
509                                                  NSArray * textfields = alertController.textFields;
510                                                  UITextField * networkSSID = textfields[0];
511                                                  UITextField * networkPassword = textfields[1];
512                                                  if ([networkSSID.text length] > 0) {
513                                                      CHIPSetDomainValueForKey(
514                                                          kCHIPToolDefaultsDomain, kNetworkSSIDDefaultsKey, networkSSID.text);
515                                                  }
516
517                                                  if ([networkPassword.text length] > 0) {
518                                                      CHIPSetDomainValueForKey(kCHIPToolDefaultsDomain, kNetworkPasswordDefaultsKey,
519                                                          networkPassword.text);
520                                                  }
521                                                  NSLog(@"New SSID: %@ Password: %@", networkSSID.text, networkPassword.text);
522
523                                                  [strongSelf.chipController sendWiFiCredentials:networkSSID.text
524                                                                                        password:networkPassword.text];
525                                              }
526                                          }]];
527     [self presentViewController:alertController animated:YES completion:nil];
528 }
529
530 - (void)updateUIFields:(CHIPSetupPayload *)payload decimalString:(nullable NSString *)decimalString
531 {
532     if (decimalString) {
533         _manualCodeLabel.hidden = NO;
534         _manualCodeLabel.text = decimalString;
535         _versionLabel.text = NOT_APPLICABLE_STRING;
536         _rendezVousInformation.text = NOT_APPLICABLE_STRING;
537         _serialNumber.text = NOT_APPLICABLE_STRING;
538     } else {
539         _manualCodeLabel.hidden = YES;
540         _versionLabel.text = [NSString stringWithFormat:@"%@", payload.version];
541         _rendezVousInformation.text = [NSString stringWithFormat:@"%lu", payload.rendezvousInformation];
542         if ([payload.serialNumber length] > 0) {
543             self->_serialNumber.text = payload.serialNumber;
544         } else {
545             self->_serialNumber.text = NOT_APPLICABLE_STRING;
546         }
547     }
548
549     _discriminatorLabel.text = [NSString stringWithFormat:@"%@", payload.discriminator];
550     _setupPinCodeLabel.text = [NSString stringWithFormat:@"%@", payload.setUpPINCode];
551     // TODO: Only display vid and pid if present
552     _vendorID.text = [NSString stringWithFormat:@"%@", payload.vendorID];
553     _productID.text = [NSString stringWithFormat:@"%@", payload.productID];
554 }
555
556 - (void)parseOptionalData:(CHIPSetupPayload *)payload
557 {
558     NSLog(@"Payload vendorID %@", payload.vendorID);
559     BOOL isSameVendorID = [payload.vendorID isEqualToNumber:[NSNumber numberWithInt:EXAMPLE_VENDOR_ID]];
560     if (!isSameVendorID) {
561         return;
562     }
563
564     NSArray * optionalInfo = [payload getAllOptionalVendorData:nil];
565     for (CHIPOptionalQRCodeInfo * info in optionalInfo) {
566         NSNumber * tag = info.tag;
567         if (!tag) {
568             continue;
569         }
570
571         BOOL isTypeString = [info.infoType isEqualToNumber:[NSNumber numberWithInt:kOptionalQRCodeInfoTypeString]];
572         if (!isTypeString) {
573             return;
574         }
575
576         NSString * infoValue = info.stringValue;
577         switch (tag.unsignedCharValue) {
578         case EXAMPLE_VENDOR_TAG_IP:
579             if ([infoValue length] > MAX_IP_LEN) {
580                 NSLog(@"Unexpected IP String... %@", infoValue);
581             }
582             break;
583         }
584     }
585 }
586
587 // MARK: Rendez Vous
588
589 - (void)handleRendezVous:(CHIPSetupPayload *)payload
590 {
591     switch (payload.rendezvousInformation) {
592     case kRendezvousInformationThread:
593     case kRendezvousInformationEthernet:
594     case kRendezvousInformationAllMask:
595         NSLog(@"Rendezvous Unknown");
596         break;
597     case kRendezvousInformationWiFi:
598         NSLog(@"Rendezvous Wi-Fi");
599         [self handleRendezVousWiFi:[self getNetworkName:payload.discriminator]];
600         break;
601     case kRendezvousInformationNone:
602     case kRendezvousInformationBLE:
603         NSLog(@"Rendezvous BLE");
604         [self handleRendezVousBLE:payload.discriminator.unsignedShortValue setupPINCode:payload.setUpPINCode.unsignedIntValue];
605         break;
606     }
607 }
608
609 - (NSString *)getNetworkName:(NSNumber *)discriminator
610 {
611     NSString * peripheralDiscriminator = [NSString stringWithFormat:@"%04u", discriminator.unsignedShortValue];
612     NSString * peripheralFullName = [NSString stringWithFormat:@"%@%@", NETWORK_CHIP_PREFIX, peripheralDiscriminator];
613     return peripheralFullName;
614 }
615
616 - (void)handleRendezVousBLE:(uint16_t)discriminator setupPINCode:(uint32_t)setupPINCode
617 {
618     NSError * error;
619     uint64_t deviceID = CHIPGetNextAvailableDeviceID();
620     if ([self.chipController pairDevice:deviceID discriminator:discriminator setupPINCode:setupPINCode error:&error]) {
621         deviceID++;
622         CHIPSetNextAvailableDeviceID(deviceID);
623     }
624 }
625
626 - (void)handleRendezVousWiFi:(NSString *)name
627 {
628     NSString * message = [NSString stringWithFormat:@"SSID: %@\n\nUse WiFi Settings to connect to it.", name];
629     UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"SoftAP Detected"
630                                                                     message:message
631                                                              preferredStyle:UIAlertControllerStyleActionSheet];
632     UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil];
633     [alert addAction:cancelAction];
634     [self presentViewController:alert animated:YES completion:nil];
635 }
636
637 // MARK: QR Code
638
639 - (BOOL)startScanning
640 {
641     NSError * error;
642     AVCaptureDevice * captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
643
644     AVCaptureDeviceInput * input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
645     if (error) {
646         NSLog(@"Could not setup device input: %@", [error localizedDescription]);
647         return NO;
648     }
649
650     AVCaptureMetadataOutput * captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
651
652     _captureSession = [[AVCaptureSession alloc] init];
653     [_captureSession addInput:input];
654     [_captureSession addOutput:captureMetadataOutput];
655
656     if (!_captureSessionQueue) {
657         _captureSessionQueue = dispatch_queue_create("captureSessionQueue", NULL);
658     }
659
660     [captureMetadataOutput setMetadataObjectsDelegate:self queue:_captureSessionQueue];
661     [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
662
663     _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
664     [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
665     [_videoPreviewLayer setFrame:_qrCodeViewPreview.layer.bounds];
666     [_qrCodeViewPreview.layer addSublayer:_videoPreviewLayer];
667
668     [_captureSession startRunning];
669
670     return YES;
671 }
672
673 - (void)displayQRCodeInSetupPayloadView:(CHIPSetupPayload *)payload withError:(NSError *)error
674 {
675     if (error) {
676         [self showError:error];
677     } else {
678         [self showPayload:payload decimalString:nil];
679     }
680 }
681
682 - (void)scannedQRCode:(NSString *)qrCode
683 {
684     dispatch_async(dispatch_get_main_queue(), ^{
685         [self->_captureSession stopRunning];
686         [self->_session invalidateSession];
687     });
688     CHIPQRCodeSetupPayloadParser * parser = [[CHIPQRCodeSetupPayloadParser alloc] initWithBase41Representation:qrCode];
689     NSError * error;
690     _setupPayload = [parser populatePayload:&error];
691     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
692         [self postScanningQRCodeState];
693
694         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, INDICATOR_DELAY), dispatch_get_main_queue(), ^{
695             [self displayQRCodeInSetupPayloadView:self->_setupPayload withError:error];
696         });
697     });
698 }
699
700 - (void)captureOutput:(AVCaptureOutput *)captureOutput
701     didOutputMetadataObjects:(NSArray *)metadataObjects
702               fromConnection:(AVCaptureConnection *)connection
703 {
704     if (metadataObjects != nil && [metadataObjects count] > 0) {
705         AVMetadataMachineReadableCodeObject * metadataObj = [metadataObjects objectAtIndex:0];
706         if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
707             [self scannedQRCode:[metadataObj stringValue]];
708         }
709     }
710 }
711
712 // MARK: Manual Code
713 - (void)displayManualCodeInSetupPayloadView:(CHIPSetupPayload *)payload
714                               decimalString:(NSString *)decimalString
715                                   withError:(NSError *)error
716 {
717     [self->_activityIndicator stopAnimating];
718     self->_activityIndicator.hidden = YES;
719     if (error) {
720         [self showError:error];
721     } else {
722         [self showPayload:payload decimalString:decimalString];
723     }
724 }
725
726 // MARK: IBActions
727
728 - (IBAction)startScanningQRCode:(id)sender
729 {
730     [self scanningStartState];
731     [self startScanning];
732 }
733
734 - (IBAction)stopScanningQRCode:(id)sender
735 {
736     [self qrCodeInitialState];
737 }
738
739 - (IBAction)resetView:(id)sender
740 {
741     // reset the view and remove any preferences that were stored from scanning the QRCode
742     [self manualCodeInitialState];
743     [self qrCodeInitialState];
744 }
745
746 - (IBAction)startScanningNFCTags:(id)sender
747 {
748     if (!_session) {
749         _session = [[NFCNDEFReaderSession alloc] initWithDelegate:self
750                                                             queue:dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT)
751                                          invalidateAfterFirstRead:NO];
752     }
753     [_session beginSession];
754 }
755
756 - (IBAction)enteredManualCode:(id)sender
757 {
758     NSString * decimalString = _manualCodeTextField.text;
759     [self manualCodeEnteredStartState];
760
761     CHIPManualSetupPayloadParser * parser =
762         [[CHIPManualSetupPayloadParser alloc] initWithDecimalStringRepresentation:decimalString];
763     NSError * error;
764     _setupPayload = [parser populatePayload:&error];
765     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, INDICATOR_DELAY), dispatch_get_main_queue(), ^{
766         [self displayManualCodeInSetupPayloadView:self->_setupPayload decimalString:decimalString withError:error];
767     });
768     [_manualCodeTextField resignFirstResponder];
769 }
770
771 @synthesize description;
772
773 @end