| /** |
| * |
| * Copyright (c) 2020 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| // module header |
| #import "QRCodeViewController.h" |
| |
| // local imports |
| #import "CHIPUIViewUtils.h" |
| #import "DefaultsUtils.h" |
| #import "DeviceSelector.h" |
| #import <Matter/MTRDeviceAttestationDelegate.h> |
| #import <Matter/MTRSetupPayload.h> |
| #import <Matter/Matter.h> |
| |
| // system imports |
| #import <AVFoundation/AVFoundation.h> |
| |
| #define INDICATOR_DELAY 0.5 * NSEC_PER_SEC |
| #define ERROR_DISPLAY_TIME 2.0 * NSEC_PER_SEC |
| #define QR_CODE_FREEZE 1.0 * NSEC_PER_SEC |
| |
| // The expected Vendor ID for CHIP demos |
| // 0xFFF1: Chip's Vendor Id |
| #define EXAMPLE_VENDOR_ID 0xFFF1 |
| |
| #define EXAMPLE_VENDOR_TAG_IP 1 |
| #define MAX_IP_LEN 46 |
| |
| #define NETWORK_CHIP_PREFIX @"CHIP-" |
| |
| #define NOT_APPLICABLE_STRING @"N/A" |
| |
| @interface MTRDeviceController (ToDoRemove) |
| |
| /** |
| * TODO: Temporary until PairingDelegate is fixed to clearly communicate this |
| * information to consumers. |
| * This should be migrated over to the proper pairing delegate path |
| */ |
| - (BOOL)_deviceBeingCommissionedOverBLE:(uint64_t)deviceId; |
| |
| @end |
| |
| @interface QRCodeViewController () |
| |
| @property (nonatomic, strong) AVCaptureSession * captureSession; |
| @property (nonatomic, strong) AVCaptureVideoPreviewLayer * videoPreviewLayer; |
| |
| @property (strong, nonatomic) UIView * qrCodeViewPreview; |
| |
| @property (strong, nonatomic) UITextField * manualCodeTextField; |
| @property (strong, nonatomic) UIButton * doneManualCodeButton; |
| |
| @property (strong, nonatomic) UIButton * nfcScanButton; |
| @property (readwrite) BOOL sessionIsActive; |
| |
| @property (strong, nonatomic) UIView * setupPayloadView; |
| @property (strong, nonatomic) UILabel * manualCodeLabel; |
| @property (strong, nonatomic) UIButton * resetButton; |
| @property (strong, nonatomic) UILabel * versionLabel; |
| @property (strong, nonatomic) UILabel * discriminatorLabel; |
| @property (strong, nonatomic) UILabel * setupPinCodeLabel; |
| @property (strong, nonatomic) UILabel * rendezVousInformation; |
| @property (strong, nonatomic) UILabel * vendorID; |
| @property (strong, nonatomic) UILabel * productID; |
| @property (strong, nonatomic) UILabel * serialNumber; |
| |
| @property (strong, nonatomic) UIButton * readFromLedgerButton; |
| @property (strong, nonatomic) UIButton * redirectButton; |
| @property (strong, nonatomic) UILabel * commissioningFlowLabel; |
| @property (strong, nonatomic) UILabel * commissioningCustomFlowUrl; |
| @property (strong, nonatomic) UIView * deviceModelInfoView; |
| @property (strong, nonatomic) NSDictionary * ledgerRespond; |
| |
| @property (strong, nonatomic) UIActivityIndicatorView * activityIndicator; |
| @property (strong, nonatomic) UILabel * errorLabel; |
| |
| @property (readwrite) MTRDeviceController * chipController; |
| @property (nonatomic, strong) MTRBaseClusterNetworkCommissioning * cluster; |
| |
| @property (strong, nonatomic) NFCNDEFReaderSession * session; |
| @property (strong, nonatomic) MTRSetupPayload * setupPayload; |
| @property (strong, nonatomic) DeviceSelector * deviceList; |
| @end |
| |
| @interface CHIPToolDeviceAttestationDelegate : NSObject <MTRDeviceAttestationDelegate> |
| |
| @property (weak, nonatomic) QRCodeViewController * viewController; |
| |
| - (instancetype)initWithViewController:(QRCodeViewController *)viewController; |
| |
| @end |
| |
| @implementation QRCodeViewController { |
| dispatch_queue_t _captureSessionQueue; |
| } |
| |
| // MARK: UI Setup |
| |
| - (void)changeNavBarButtonToCamera |
| { |
| UIBarButtonItem * camera = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCamera |
| target:self |
| action:@selector(startScanningQRCode:)]; |
| self.navigationItem.rightBarButtonItem = camera; |
| } |
| |
| - (void)changeNavBarButtonToCancel |
| { |
| UIBarButtonItem * cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel |
| target:self |
| action:@selector(stopScanningQRCode:)]; |
| self.navigationItem.rightBarButtonItem = cancel; |
| } |
| |
| - (void)setupUI |
| { |
| self.view.backgroundColor = UIColor.whiteColor; |
| |
| // Setup nav bar button |
| [self changeNavBarButtonToCamera]; |
| |
| // Initialize all Labels |
| [self initializeAllLabels]; |
| |
| // Title |
| UILabel * titleLabel = [CHIPUIViewUtils addTitle:@"QR Code Parser" toView:self.view]; |
| |
| // stack view |
| UIStackView * stackView = [UIStackView new]; |
| stackView.axis = UILayoutConstraintAxisVertical; |
| stackView.distribution = UIStackViewDistributionFill; |
| stackView.alignment = UIStackViewAlignmentLeading; |
| stackView.spacing = 10; |
| [self.view addSubview:stackView]; |
| |
| stackView.translatesAutoresizingMaskIntoConstraints = false; |
| [stackView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:30].active = YES; |
| [stackView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [stackView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| |
| // Manual entry view |
| _manualCodeTextField = [UITextField new]; |
| _doneManualCodeButton = [UIButton new]; |
| [_doneManualCodeButton addTarget:self action:@selector(enteredManualCode:) forControlEvents:UIControlEventTouchUpInside]; |
| _manualCodeTextField.placeholder = @"Manual Code"; |
| _manualCodeTextField.keyboardType = UIKeyboardTypeNumberPad; |
| [_doneManualCodeButton setTitle:@"Go" forState:UIControlStateNormal]; |
| UIView * manualEntryView = [CHIPUIViewUtils viewWithUITextField:_manualCodeTextField button:_doneManualCodeButton]; |
| [stackView addArrangedSubview:manualEntryView]; |
| |
| manualEntryView.translatesAutoresizingMaskIntoConstraints = false; |
| [manualEntryView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [manualEntryView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| [manualEntryView.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:30].active = YES; |
| |
| _nfcScanButton = [UIButton new]; |
| [_nfcScanButton setTitle:@"Scan NFC Tag" forState:UIControlStateNormal]; |
| [_nfcScanButton addTarget:self action:@selector(startScanningNFCTags:) forControlEvents:UIControlEventTouchDown]; |
| _nfcScanButton.titleLabel.font = [UIFont systemFontOfSize:17]; |
| _nfcScanButton.titleLabel.textColor = [UIColor blackColor]; |
| _nfcScanButton.layer.cornerRadius = 5; |
| _nfcScanButton.clipsToBounds = YES; |
| _nfcScanButton.backgroundColor = UIColor.systemBlueColor; |
| [stackView addArrangedSubview:_nfcScanButton]; |
| |
| _nfcScanButton.translatesAutoresizingMaskIntoConstraints = false; |
| [_nfcScanButton.leadingAnchor constraintEqualToAnchor:stackView.leadingAnchor].active = YES; |
| [_nfcScanButton.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor].active = YES; |
| [_nfcScanButton.heightAnchor constraintEqualToConstant:40].active = YES; |
| |
| _deviceList = [DeviceSelector new]; |
| [_deviceList setEnabled:NO]; |
| |
| UILabel * deviceIDLabel = [UILabel new]; |
| deviceIDLabel.text = @"Paired Devices:"; |
| UIView * deviceIDView = [CHIPUIViewUtils viewWithLabel:deviceIDLabel textField:_deviceList]; |
| [stackView addArrangedSubview:deviceIDView]; |
| |
| deviceIDView.translatesAutoresizingMaskIntoConstraints = false; |
| [deviceIDView.trailingAnchor constraintEqualToAnchor:stackView.trailingAnchor].active = true; |
| |
| // Results view |
| _setupPayloadView = [UIView new]; |
| [self.view addSubview:_setupPayloadView]; |
| |
| _setupPayloadView.translatesAutoresizingMaskIntoConstraints = false; |
| [_setupPayloadView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor constant:10].active = YES; |
| [_setupPayloadView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [_setupPayloadView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| [_setupPayloadView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-60].active = YES; |
| |
| _deviceModelInfoView = [UIView new]; |
| [self.view addSubview:_deviceModelInfoView]; |
| |
| _deviceModelInfoView.translatesAutoresizingMaskIntoConstraints = false; |
| [_deviceModelInfoView.topAnchor constraintEqualToAnchor:stackView.bottomAnchor constant:10].active = YES; |
| [_deviceModelInfoView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [_deviceModelInfoView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| [_deviceModelInfoView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-60].active |
| = YES; |
| |
| // manual entry field |
| _manualCodeLabel = [UILabel new]; |
| _manualCodeLabel.text = @"00000000000000000000"; |
| _manualCodeLabel.textColor = UIColor.systemBlueColor; |
| _manualCodeLabel.font = [UIFont systemFontOfSize:17]; |
| _manualCodeLabel.textAlignment = NSTextAlignmentRight; |
| [_setupPayloadView addSubview:_manualCodeLabel]; |
| |
| _manualCodeLabel.translatesAutoresizingMaskIntoConstraints = false; |
| [_manualCodeLabel.topAnchor constraintEqualToAnchor:_setupPayloadView.topAnchor].active = YES; |
| [_manualCodeLabel.trailingAnchor constraintEqualToAnchor:_setupPayloadView.trailingAnchor].active = YES; |
| |
| // activity indicator |
| _activityIndicator = [UIActivityIndicatorView new]; |
| [self.view addSubview:_activityIndicator]; |
| |
| _activityIndicator.translatesAutoresizingMaskIntoConstraints = false; |
| [_activityIndicator.centerXAnchor constraintEqualToAnchor:_setupPayloadView.centerXAnchor].active = YES; |
| [_activityIndicator.centerYAnchor constraintEqualToAnchor:_setupPayloadView.centerYAnchor].active = YES; |
| _activityIndicator.color = UIColor.blackColor; |
| |
| // QRCode preview |
| _qrCodeViewPreview = [UIView new]; |
| [self.view addSubview:_qrCodeViewPreview]; |
| |
| _qrCodeViewPreview.translatesAutoresizingMaskIntoConstraints = false; |
| [_qrCodeViewPreview.topAnchor constraintEqualToAnchor:_nfcScanButton.bottomAnchor constant:30].active = YES; |
| [_qrCodeViewPreview.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [_qrCodeViewPreview.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| [_qrCodeViewPreview.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-50].active = YES; |
| |
| // Error label |
| _errorLabel = [UILabel new]; |
| _errorLabel.text = @"Error Text"; |
| _errorLabel.textColor = UIColor.blackColor; |
| _errorLabel.font = [UIFont systemFontOfSize:17]; |
| [stackView addArrangedSubview:_errorLabel]; |
| |
| _errorLabel.translatesAutoresizingMaskIntoConstraints = false; |
| [_errorLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:30].active = YES; |
| [_errorLabel.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| |
| // Reset button |
| _resetButton = [UIButton new]; |
| [_resetButton setTitle:@"Reset" forState:UIControlStateNormal]; |
| [_resetButton addTarget:self action:@selector(resetView:) forControlEvents:UIControlEventTouchUpInside]; |
| _resetButton.backgroundColor = UIColor.systemBlueColor; |
| _resetButton.titleLabel.font = [UIFont systemFontOfSize:17]; |
| _resetButton.titleLabel.textColor = [UIColor whiteColor]; |
| _resetButton.layer.cornerRadius = 5; |
| _resetButton.clipsToBounds = YES; |
| [self.view addSubview:_resetButton]; |
| |
| _resetButton.translatesAutoresizingMaskIntoConstraints = false; |
| [_resetButton.widthAnchor constraintEqualToConstant:60].active = YES; |
| [_resetButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-30].active = YES; |
| [_resetButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| |
| // Read from Ledger button |
| _readFromLedgerButton = [UIButton new]; |
| [_readFromLedgerButton setTitle:@"Read from Ledger" forState:UIControlStateNormal]; |
| [_readFromLedgerButton addTarget:self action:@selector(readFromLedgerApi:) forControlEvents:UIControlEventTouchUpInside]; |
| _readFromLedgerButton.backgroundColor = UIColor.systemBlueColor; |
| _readFromLedgerButton.titleLabel.font = [UIFont systemFontOfSize:17]; |
| _readFromLedgerButton.titleLabel.textColor = [UIColor whiteColor]; |
| _readFromLedgerButton.layer.cornerRadius = 5; |
| _readFromLedgerButton.clipsToBounds = YES; |
| _readFromLedgerButton.hidden = YES; |
| [self.view addSubview:_readFromLedgerButton]; |
| |
| _readFromLedgerButton.translatesAutoresizingMaskIntoConstraints = false; |
| [_readFromLedgerButton.widthAnchor constraintEqualToConstant:200].active = YES; |
| [_readFromLedgerButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-30].active |
| = YES; |
| [_readFromLedgerButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| |
| // Redirect Custom Flow button |
| _redirectButton = [UIButton new]; |
| [_redirectButton setTitle:@"Redirect" forState:UIControlStateNormal]; |
| [_redirectButton addTarget:self action:@selector(redirectToUrl:) forControlEvents:UIControlEventTouchUpInside]; |
| _redirectButton.backgroundColor = UIColor.systemBlueColor; |
| _redirectButton.titleLabel.font = [UIFont systemFontOfSize:17]; |
| _redirectButton.titleLabel.textColor = [UIColor whiteColor]; |
| _redirectButton.layer.cornerRadius = 5; |
| _redirectButton.clipsToBounds = YES; |
| _redirectButton.hidden = YES; |
| [self.view addSubview:_redirectButton]; |
| |
| _redirectButton.translatesAutoresizingMaskIntoConstraints = false; |
| [_redirectButton.widthAnchor constraintEqualToConstant:200].active = YES; |
| [_redirectButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-30].active = YES; |
| [_redirectButton.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-30].active = YES; |
| } |
| |
| - (void)initializeAllLabels |
| { |
| _versionLabel = [UILabel new]; |
| _discriminatorLabel = [UILabel new]; |
| _setupPinCodeLabel = [UILabel new]; |
| _rendezVousInformation = [UILabel new]; |
| _vendorID = [UILabel new]; |
| _productID = [UILabel new]; |
| _serialNumber = [UILabel new]; |
| _commissioningFlowLabel = [UILabel new]; |
| _commissioningCustomFlowUrl = [UILabel new]; |
| } |
| |
| - (void)addDetailSubview:(UIView *)superView |
| { |
| // Results scroll view |
| UIScrollView * resultsScrollView = [UIScrollView new]; |
| [superView addSubview:resultsScrollView]; |
| |
| resultsScrollView.translatesAutoresizingMaskIntoConstraints = false; |
| [resultsScrollView.topAnchor constraintEqualToAnchor:superView.topAnchor constant:10].active = YES; |
| [resultsScrollView.leadingAnchor constraintEqualToAnchor:superView.leadingAnchor].active = YES; |
| [resultsScrollView.trailingAnchor constraintEqualToAnchor:superView.trailingAnchor].active = YES; |
| [resultsScrollView.bottomAnchor constraintEqualToAnchor:superView.bottomAnchor constant:-20].active = YES; |
| |
| UIStackView * parserResultsView = [UIStackView new]; |
| parserResultsView.axis = UILayoutConstraintAxisVertical; |
| parserResultsView.distribution = UIStackViewDistributionEqualSpacing; |
| parserResultsView.alignment = UIStackViewAlignmentLeading; |
| parserResultsView.spacing = 5; |
| [resultsScrollView addSubview:parserResultsView]; |
| |
| parserResultsView.translatesAutoresizingMaskIntoConstraints = false; |
| [parserResultsView.topAnchor constraintEqualToAnchor:resultsScrollView.topAnchor].active = YES; |
| [parserResultsView.leadingAnchor constraintEqualToAnchor:resultsScrollView.leadingAnchor].active = YES; |
| [parserResultsView.trailingAnchor constraintEqualToAnchor:resultsScrollView.trailingAnchor].active = YES; |
| [parserResultsView.bottomAnchor constraintEqualToAnchor:resultsScrollView.bottomAnchor].active = YES; |
| |
| if (superView == _setupPayloadView) { |
| [superView addSubview:_manualCodeLabel]; |
| [self addResultsUIToStackView:parserResultsView]; |
| } else if (superView == _deviceModelInfoView) { |
| [self addDeviceInfoUIToStackView:parserResultsView]; |
| } |
| } |
| |
| - (void)addResultsUIToStackView:(UIStackView *)stackView |
| { |
| NSArray<NSString *> * resultLabelTexts = @[ |
| @"Version", @"Vendor ID", @"Product ID", @"Discriminator", @"Setup PIN Code", @"Rendez Vous Information", @"Serial #", |
| @"Commissioning Flow" |
| ]; |
| NSArray<UILabel *> * resultLabels = @[ |
| _versionLabel, _vendorID, _productID, _discriminatorLabel, _setupPinCodeLabel, _rendezVousInformation, _serialNumber, |
| _commissioningFlowLabel |
| ]; |
| [self addItemToStackView:stackView resultLabels:resultLabels resultLabelTexts:resultLabelTexts]; |
| } |
| |
| - (void)addDeviceInfoUIToStackView:(UIStackView *)stackView |
| { |
| NSArray<NSString *> * resultLabelTexts = @[ @"Vendor ID", @"Product ID", @"Commissioning URL" ]; |
| NSArray<UILabel *> * resultLabels = @[ _vendorID, _productID, _commissioningCustomFlowUrl ]; |
| [self addItemToStackView:stackView resultLabels:resultLabels resultLabelTexts:resultLabelTexts]; |
| } |
| |
| - (void)addItemToStackView:(UIStackView *)stackView |
| resultLabels:(NSArray<UILabel *> *)resultLabels |
| resultLabelTexts:(NSArray<NSString *> *)resultLabelTexts |
| { |
| for (int i = 0; i < resultLabels.count && i < resultLabelTexts.count; i++) { |
| UILabel * label = [UILabel new]; |
| label.text = [resultLabelTexts objectAtIndex:i]; |
| UILabel * result = [resultLabels objectAtIndex:i]; |
| if (!result.text) |
| result.text = @"N/A"; |
| UIStackView * labelStackView = [CHIPUIViewUtils stackViewWithLabel:label result:result]; |
| labelStackView.translatesAutoresizingMaskIntoConstraints = false; |
| [stackView addArrangedSubview:labelStackView]; |
| } |
| } |
| |
| - (void)updateResultViewUI:(UIView *)superView |
| { |
| NSArray * viewsToRemove = [superView subviews]; |
| for (UIView * v in viewsToRemove) { |
| [v removeFromSuperview]; |
| } |
| [self addDetailSubview:superView]; |
| } |
| |
| // MARK: UIViewController methods |
| |
| - (void)viewDidDisappear:(BOOL)animated |
| { |
| [super viewDidDisappear:animated]; |
| [_session invalidateSession]; |
| _session = nil; |
| } |
| |
| - (void)dismissKeyboard |
| { |
| [_manualCodeTextField resignFirstResponder]; |
| } |
| |
| - (void)viewDidLoad |
| { |
| [super viewDidLoad]; |
| [self setupUI]; |
| |
| dispatch_queue_t callbackQueue = dispatch_queue_create("com.csa.matter.qrcodevc.callback", DISPATCH_QUEUE_SERIAL); |
| self.chipController = InitializeMTR(); |
| [self.chipController setPairingDelegate:self queue:callbackQueue]; |
| |
| UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)]; |
| [self.view addGestureRecognizer:tap]; |
| |
| [self manualCodeInitialState]; |
| [self qrCodeInitialState]; |
| } |
| |
| // MARK: NFCNDEFReaderSessionDelegate |
| |
| - (void)readerSession:(nonnull NFCNDEFReaderSession *)session didDetectNDEFs:(nonnull NSArray<NFCNDEFMessage *> *)messages |
| { |
| [_session invalidateSession]; |
| NSString * errorMessage; |
| if (messages.count == 1) { |
| for (NFCNDEFMessage * message in messages) { |
| if (message.records.count == 1) { |
| for (NFCNDEFPayload * payload in message.records) { |
| NSString * payloadType = [[NSString alloc] initWithData:payload.type encoding:NSUTF8StringEncoding]; |
| if ([payloadType isEqualToString:@"U"]) { |
| NSURL * payloadURI = [payload wellKnownTypeURIPayload]; |
| NSLog(@"Payload text:%@", payloadURI); |
| if (payloadURI) { |
| NSString * qrCode = [payloadURI absoluteString]; |
| NSLog(@"Scanned code string:%@", qrCode); |
| [self scannedQRCode:qrCode]; |
| } |
| } else { |
| errorMessage = @"Record must be of type text."; |
| } |
| } |
| } else { |
| errorMessage = @"Only one record in NFC tag is accepted."; |
| } |
| } |
| } else { |
| errorMessage = @"Only one message in NFC tag is accepted."; |
| } |
| if ([errorMessage length] > 0) { |
| NSError * error = [[NSError alloc] initWithDomain:@"com.chiptool.nfctagscanning" |
| code:1 |
| userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_NOW), dispatch_get_main_queue(), ^{ |
| [self showError:error]; |
| }); |
| } |
| } |
| |
| - (void)readerSession:(nonnull NFCNDEFReaderSession *)session didInvalidateWithError:(nonnull NSError *)error |
| { |
| NSLog(@"If no NFC reading UI is appearing, target may me missing the appropriate capability. Turn on Near Field Communication " |
| @"Tag Reading under the Capabilities tab for the project’s target. A paid developer account is needed for this."); |
| _session = nil; |
| } |
| |
| - (void)setVendorIDOnAccessory |
| { |
| NSLog(@"Call to setVendorIDOnAccessory"); |
| if (MTRGetConnectedDevice(^(MTRBaseDevice * _Nullable device, NSError * _Nullable error) { |
| if (!device) { |
| NSLog(@"Status: Failed to establish a connection with the device"); |
| } |
| })) { |
| NSLog(@"Status: Waiting for connection with the device"); |
| } else { |
| NSLog(@"Status: Failed to trigger the connection with the device"); |
| } |
| } |
| |
| // MARK: MTRDevicePairingDelegate |
| - (void)onPairingComplete:(NSError * _Nullable)error |
| { |
| if (error != nil) { |
| NSLog(@"Got pairing error back %@", error); |
| } else { |
| MTRDeviceController * controller = InitializeMTR(); |
| uint64_t deviceId = MTRGetLastPairedDeviceId(); |
| if ([controller respondsToSelector:@selector(_deviceBeingCommissionedOverBLE:)] && |
| [controller _deviceBeingCommissionedOverBLE:deviceId]) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [self->_deviceList refreshDeviceList]; |
| [self retrieveAndSendWiFiCredentials]; |
| }); |
| } else { |
| MTRCommissioningParameters * params = [[MTRCommissioningParameters alloc] init]; |
| params.deviceAttestationDelegate = [[CHIPToolDeviceAttestationDelegate alloc] initWithViewController:self]; |
| params.failSafeExpiryTimeoutSecs = @600; |
| NSError * error; |
| if (![controller commissionDevice:deviceId commissioningParams:params error:&error]) { |
| NSLog(@"Failed to commission Device %llu, with error %@", deviceId, error); |
| } |
| } |
| } |
| } |
| |
| // MARK: UI Helper methods |
| |
| - (void)manualCodeInitialState |
| { |
| _deviceModelInfoView.hidden = YES; |
| _readFromLedgerButton.hidden = YES; |
| _redirectButton.hidden = YES; |
| _setupPayloadView.hidden = YES; |
| _resetButton.hidden = YES; |
| _activityIndicator.hidden = YES; |
| _errorLabel.hidden = YES; |
| } |
| |
| - (void)qrCodeInitialState |
| { |
| if ([_captureSession isRunning]) { |
| [_captureSession stopRunning]; |
| } |
| if ([_activityIndicator isAnimating]) { |
| [_activityIndicator stopAnimating]; |
| } |
| _resetButton.hidden = YES; |
| [self changeNavBarButtonToCamera]; |
| _activityIndicator.hidden = YES; |
| _captureSession = nil; |
| [_videoPreviewLayer removeFromSuperlayer]; |
| } |
| |
| - (void)scanningStartState |
| { |
| [self changeNavBarButtonToCancel]; |
| _setupPayloadView.hidden = YES; |
| _resetButton.hidden = YES; |
| _errorLabel.hidden = YES; |
| _deviceModelInfoView.hidden = YES; |
| _redirectButton.hidden = YES; |
| _readFromLedgerButton.hidden = YES; |
| } |
| |
| - (void)manualCodeEnteredStartState |
| { |
| self->_activityIndicator.hidden = NO; |
| [self->_activityIndicator startAnimating]; |
| _setupPayloadView.hidden = YES; |
| _resetButton.hidden = YES; |
| _errorLabel.hidden = YES; |
| _manualCodeTextField.text = @""; |
| } |
| |
| - (void)postScanningQRCodeState |
| { |
| _captureSession = nil; |
| [self changeNavBarButtonToCamera]; |
| |
| [_videoPreviewLayer removeFromSuperlayer]; |
| |
| self->_activityIndicator.hidden = NO; |
| [self->_activityIndicator startAnimating]; |
| } |
| |
| - (void)showError:(NSError *)error |
| { |
| [self->_activityIndicator stopAnimating]; |
| self->_activityIndicator.hidden = YES; |
| self->_manualCodeLabel.hidden = YES; |
| _resetButton.hidden = YES; |
| |
| self->_errorLabel.text = error.localizedDescription; |
| self->_errorLabel.hidden = NO; |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, ERROR_DISPLAY_TIME), dispatch_get_main_queue(), ^{ |
| self->_errorLabel.hidden = YES; |
| }); |
| } |
| |
| - (void)showPayload:(MTRSetupPayload *)payload rawPayload:(NSString *)rawPayload isManualCode:(BOOL)isManualCode |
| { |
| [self->_activityIndicator stopAnimating]; |
| self->_activityIndicator.hidden = YES; |
| self->_errorLabel.hidden = YES; |
| // reset the view and remove any preferences that were stored from a previous scan |
| self->_setupPayloadView.hidden = NO; |
| self->_resetButton.hidden = NO; |
| |
| [self updateUIFields:payload rawPayload:rawPayload isManualCode:isManualCode]; |
| [self parseOptionalData:payload]; |
| [self handleRendezVous:payload rawPayload:rawPayload]; |
| } |
| |
| - (void)retrieveAndSendWiFiCredentials |
| { |
| UIAlertController * alertController = |
| [UIAlertController alertControllerWithTitle:@"WiFi Configuration" |
| message:@"Input network SSID and password that your phone is connected to." |
| preferredStyle:UIAlertControllerStyleAlert]; |
| [alertController addTextFieldWithConfigurationHandler:^(UITextField * textField) { |
| textField.placeholder = @"Network SSID"; |
| textField.clearButtonMode = UITextFieldViewModeWhileEditing; |
| textField.borderStyle = UITextBorderStyleRoundedRect; |
| |
| NSString * networkSSID = MTRGetDomainValueForKey(MTRToolDefaultsDomain, kNetworkSSIDDefaultsKey); |
| if ([networkSSID length] > 0) { |
| textField.text = networkSSID; |
| } |
| }]; |
| [alertController addTextFieldWithConfigurationHandler:^(UITextField * textField) { |
| [textField setSecureTextEntry:YES]; |
| textField.placeholder = @"Password"; |
| textField.clearButtonMode = UITextFieldViewModeWhileEditing; |
| textField.borderStyle = UITextBorderStyleRoundedRect; |
| textField.secureTextEntry = YES; |
| |
| NSString * networkPassword = MTRGetDomainValueForKey(MTRToolDefaultsDomain, kNetworkPasswordDefaultsKey); |
| if ([networkPassword length] > 0) { |
| textField.text = networkPassword; |
| } |
| }]; |
| [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" |
| style:UIAlertActionStyleDefault |
| handler:^(UIAlertAction * action) { |
| }]]; |
| |
| __weak typeof(self) weakSelf = self; |
| [alertController |
| addAction:[UIAlertAction actionWithTitle:@"Send" |
| style:UIAlertActionStyleDefault |
| handler:^(UIAlertAction * action) { |
| typeof(self) strongSelf = weakSelf; |
| if (strongSelf) { |
| NSArray * textfields = alertController.textFields; |
| UITextField * networkSSID = textfields[0]; |
| UITextField * networkPassword = textfields[1]; |
| if ([networkSSID.text length] > 0) { |
| MTRSetDomainValueForKey( |
| MTRToolDefaultsDomain, kNetworkSSIDDefaultsKey, networkSSID.text); |
| } |
| |
| if ([networkPassword.text length] > 0) { |
| MTRSetDomainValueForKey( |
| MTRToolDefaultsDomain, kNetworkPasswordDefaultsKey, networkPassword.text); |
| } |
| NSLog(@"New SSID: %@ Password: %@", networkSSID.text, networkPassword.text); |
| |
| [strongSelf commissionWithSSID:networkSSID.text password:networkPassword.text]; |
| } |
| }]]; |
| [self presentViewController:alertController animated:YES completion:nil]; |
| } |
| |
| - (void)commissionWithSSID:(NSString *)ssid password:(NSString *)password |
| { |
| |
| NSError * error; |
| MTRDeviceController * controller = InitializeMTR(); |
| // create commissioning params in ObjC. Pass those in here with network credentials. |
| // maybe this just becomes the new norm |
| MTRCommissioningParameters * params = [[MTRCommissioningParameters alloc] init]; |
| params.wifiSSID = [ssid dataUsingEncoding:NSUTF8StringEncoding]; |
| params.wifiCredentials = [password dataUsingEncoding:NSUTF8StringEncoding]; |
| params.deviceAttestationDelegate = [[CHIPToolDeviceAttestationDelegate alloc] initWithViewController:self]; |
| params.failSafeExpiryTimeoutSecs = @600; |
| |
| uint64_t deviceId = MTRGetNextAvailableDeviceID() - 1; |
| |
| if (![controller commissionDevice:deviceId commissioningParams:params error:&error]) { |
| NSLog(@"Failed to commission Device %llu, with error %@", deviceId, error); |
| } |
| } |
| |
| - (void)onCommissioningComplete:(NSError * _Nullable)error |
| { |
| if (error != nil) { |
| NSLog(@"Error retrieving device informations over Mdns: %@", error); |
| return; |
| } |
| // track this device |
| uint64_t deviceId = MTRGetNextAvailableDeviceID() - 1; |
| MTRSetDevicePaired(deviceId, YES); |
| [self setVendorIDOnAccessory]; |
| } |
| |
| - (void)updateUIFields:(MTRSetupPayload *)payload rawPayload:(nullable NSString *)rawPayload isManualCode:(BOOL)isManualCode |
| { |
| if (isManualCode) { |
| _manualCodeLabel.hidden = NO; |
| _manualCodeLabel.text = rawPayload; |
| _versionLabel.text = NOT_APPLICABLE_STRING; |
| _rendezVousInformation.text = NOT_APPLICABLE_STRING; |
| _serialNumber.text = NOT_APPLICABLE_STRING; |
| } else { |
| _manualCodeLabel.hidden = YES; |
| _versionLabel.text = [NSString stringWithFormat:@"%@", payload.version]; |
| if (payload.rendezvousInformation == nil) { |
| _rendezVousInformation.text = NOT_APPLICABLE_STRING; |
| } else { |
| _rendezVousInformation.text = [NSString stringWithFormat:@"%lu", [payload.rendezvousInformation unsignedLongValue]]; |
| } |
| if ([payload.serialNumber length] > 0) { |
| self->_serialNumber.text = payload.serialNumber; |
| } else { |
| self->_serialNumber.text = NOT_APPLICABLE_STRING; |
| } |
| } |
| |
| _discriminatorLabel.text = [NSString stringWithFormat:@"%@", payload.discriminator]; |
| _setupPinCodeLabel.text = [NSString stringWithFormat:@"%@", payload.setUpPINCode]; |
| // TODO: Only display vid and pid if present |
| _vendorID.text = [NSString stringWithFormat:@"%@", payload.vendorID]; |
| _productID.text = [NSString stringWithFormat:@"%@", payload.productID]; |
| _commissioningFlowLabel.text = [NSString stringWithFormat:@"%lu", payload.commissioningFlow]; |
| |
| [self updateResultViewUI:_setupPayloadView]; |
| |
| if (payload.commissioningFlow == MTRCommissioningFlowCustom) { |
| _readFromLedgerButton.hidden = NO; |
| } |
| } |
| |
| - (void)parseOptionalData:(MTRSetupPayload *)payload |
| { |
| NSLog(@"Payload vendorID %@", payload.vendorID); |
| BOOL isSameVendorID = [payload.vendorID isEqualToNumber:[NSNumber numberWithInt:EXAMPLE_VENDOR_ID]]; |
| if (!isSameVendorID) { |
| return; |
| } |
| |
| NSArray * optionalInfo = [payload getAllOptionalVendorData:nil]; |
| for (MTROptionalQRCodeInfo * info in optionalInfo) { |
| NSNumber * tag = info.tag; |
| if (!tag) { |
| continue; |
| } |
| |
| BOOL isTypeString = [info.infoType isEqualToNumber:[NSNumber numberWithInt:MTROptionalQRCodeInfoTypeString]]; |
| if (!isTypeString) { |
| return; |
| } |
| |
| NSString * infoValue = info.stringValue; |
| switch (tag.unsignedCharValue) { |
| case EXAMPLE_VENDOR_TAG_IP: |
| if ([infoValue length] > MAX_IP_LEN) { |
| NSLog(@"Unexpected IP String... %@", infoValue); |
| } |
| break; |
| } |
| } |
| } |
| |
| // MARK: Rendez Vous |
| |
| - (void)handleRendezVous:(MTRSetupPayload *)payload rawPayload:(NSString *)rawPayload |
| { |
| if (payload.rendezvousInformation == nil) { |
| NSLog(@"Rendezvous Default"); |
| [self handleRendezVousDefault:rawPayload]; |
| return; |
| } |
| |
| // TODO: This is a pretty broken way to handle a bitmask. |
| switch ([payload.rendezvousInformation unsignedLongValue]) { |
| case MTRDiscoveryCapabilitiesNone: |
| case MTRDiscoveryCapabilitiesOnNetwork: |
| case MTRDiscoveryCapabilitiesBLE: |
| case MTRDiscoveryCapabilitiesAllMask: |
| NSLog(@"Rendezvous Default"); |
| [self handleRendezVousDefault:rawPayload]; |
| break; |
| case MTRDiscoveryCapabilitiesSoftAP: |
| NSLog(@"Rendezvous Wi-Fi"); |
| [self handleRendezVousWiFi:[self getNetworkName:payload.discriminator]]; |
| break; |
| } |
| } |
| |
| - (NSString *)getNetworkName:(NSNumber *)discriminator |
| { |
| NSString * peripheralDiscriminator = [NSString stringWithFormat:@"%04u", discriminator.unsignedShortValue]; |
| NSString * peripheralFullName = [NSString stringWithFormat:@"%@%@", NETWORK_CHIP_PREFIX, peripheralDiscriminator]; |
| return peripheralFullName; |
| } |
| |
| - (void)_restartMatterStack |
| { |
| self.chipController = MTRRestartController(self.chipController); |
| dispatch_queue_t callbackQueue = dispatch_queue_create("com.csa.matter.qrcodevc.callback", DISPATCH_QUEUE_SERIAL); |
| [self.chipController setPairingDelegate:self queue:callbackQueue]; |
| } |
| |
| - (void)handleRendezVousDefault:(NSString *)payload |
| { |
| NSError * error; |
| uint64_t deviceID = MTRGetNextAvailableDeviceID(); |
| |
| // restart the Matter Stack before pairing (for reliability + testing restarts) |
| [self _restartMatterStack]; |
| |
| if ([self.chipController pairDevice:deviceID onboardingPayload:payload error:&error]) { |
| deviceID++; |
| MTRSetNextAvailableDeviceID(deviceID); |
| } |
| } |
| |
| - (void)handleRendezVousWiFi:(NSString *)name |
| { |
| NSString * message = [NSString stringWithFormat:@"SSID: %@\n\nUse WiFi Settings to connect to it.", name]; |
| UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"SoftAP Detected" |
| message:message |
| preferredStyle:UIAlertControllerStyleActionSheet]; |
| UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]; |
| [alert addAction:cancelAction]; |
| [self presentViewController:alert animated:YES completion:nil]; |
| } |
| |
| // MARK: QR Code |
| |
| - (BOOL)startScanning |
| { |
| NSError * error; |
| AVCaptureDevice * captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; |
| |
| AVCaptureDeviceInput * input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; |
| if (error) { |
| NSLog(@"Could not setup device input: %@", [error localizedDescription]); |
| return NO; |
| } |
| |
| AVCaptureMetadataOutput * captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init]; |
| |
| _captureSession = [[AVCaptureSession alloc] init]; |
| [_captureSession addInput:input]; |
| [_captureSession addOutput:captureMetadataOutput]; |
| |
| if (!_captureSessionQueue) { |
| _captureSessionQueue = dispatch_queue_create("captureSessionQueue", NULL); |
| } |
| |
| [captureMetadataOutput setMetadataObjectsDelegate:self queue:_captureSessionQueue]; |
| [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]]; |
| |
| _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession]; |
| [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; |
| [_videoPreviewLayer setFrame:_qrCodeViewPreview.layer.bounds]; |
| [_qrCodeViewPreview.layer addSublayer:_videoPreviewLayer]; |
| |
| [_captureSession startRunning]; |
| |
| return YES; |
| } |
| |
| - (void)displayQRCodeInSetupPayloadView:(MTRSetupPayload *)payload rawPayload:(NSString *)rawPayload error:(NSError *)error |
| { |
| if (error) { |
| [self showError:error]; |
| } else { |
| [self showPayload:payload rawPayload:rawPayload isManualCode:NO]; |
| } |
| } |
| |
| - (void)scannedQRCode:(NSString *)qrCode |
| { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [self->_captureSession stopRunning]; |
| [self->_session invalidateSession]; |
| }); |
| MTRQRCodeSetupPayloadParser * parser = [[MTRQRCodeSetupPayloadParser alloc] initWithBase38Representation:qrCode]; |
| NSError * error; |
| _setupPayload = [parser populatePayload:&error]; |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ |
| [self postScanningQRCodeState]; |
| |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, INDICATOR_DELAY), dispatch_get_main_queue(), ^{ |
| [self displayQRCodeInSetupPayloadView:self->_setupPayload rawPayload:qrCode error:error]; |
| }); |
| }); |
| } |
| |
| - (void)captureOutput:(AVCaptureOutput *)captureOutput |
| didOutputMetadataObjects:(NSArray *)metadataObjects |
| fromConnection:(AVCaptureConnection *)connection |
| { |
| if (metadataObjects != nil && [metadataObjects count] > 0) { |
| AVMetadataMachineReadableCodeObject * metadataObj = [metadataObjects objectAtIndex:0]; |
| if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) { |
| [self scannedQRCode:[metadataObj stringValue]]; |
| } |
| } |
| } |
| |
| // MARK: Manual Code |
| - (void)displayManualCodeInSetupPayloadView:(MTRSetupPayload *)payload |
| decimalString:(NSString *)decimalString |
| withError:(NSError *)error |
| { |
| [self->_activityIndicator stopAnimating]; |
| self->_activityIndicator.hidden = YES; |
| if (error) { |
| [self showError:error]; |
| } else { |
| [self showPayload:payload rawPayload:decimalString isManualCode:YES]; |
| } |
| } |
| |
| // MARK: IBActions |
| |
| - (IBAction)startScanningQRCode:(id)sender |
| { |
| [self scanningStartState]; |
| [self startScanning]; |
| } |
| |
| - (IBAction)stopScanningQRCode:(id)sender |
| { |
| [self qrCodeInitialState]; |
| } |
| |
| - (IBAction)resetView:(id)sender |
| { |
| // reset the view and remove any preferences that were stored from scanning the QRCode |
| [self manualCodeInitialState]; |
| [self qrCodeInitialState]; |
| } |
| |
| - (IBAction)startScanningNFCTags:(id)sender |
| { |
| if (!_session) { |
| _session = [[NFCNDEFReaderSession alloc] initWithDelegate:self |
| queue:dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT) |
| invalidateAfterFirstRead:NO]; |
| } |
| [_session beginSession]; |
| } |
| |
| - (IBAction)enteredManualCode:(id)sender |
| { |
| NSString * decimalString = _manualCodeTextField.text; |
| [self manualCodeEnteredStartState]; |
| |
| MTRManualSetupPayloadParser * parser = [[MTRManualSetupPayloadParser alloc] initWithDecimalStringRepresentation:decimalString]; |
| NSError * error; |
| _setupPayload = [parser populatePayload:&error]; |
| dispatch_after(dispatch_time(DISPATCH_TIME_NOW, INDICATOR_DELAY), dispatch_get_main_queue(), ^{ |
| [self displayManualCodeInSetupPayloadView:self->_setupPayload decimalString:decimalString withError:error]; |
| }); |
| [_manualCodeTextField resignFirstResponder]; |
| } |
| |
| // Ledger |
| |
| - (IBAction)readFromLedgerApi:(id)sender |
| { |
| NSLog(@"Clicked readFromLedger..."); |
| _readFromLedgerButton.hidden = YES; |
| _setupPayloadView.hidden = YES; |
| _activityIndicator.hidden = NO; |
| [_activityIndicator startAnimating]; |
| |
| [self updateResultViewUI:_deviceModelInfoView]; |
| [self updateLedgerFields]; |
| } |
| |
| - (void)updateLedgerFields |
| { |
| // check vendor Id and product Id |
| NSLog(@"Validating Vender Id and Product Id..."); |
| if ([_vendorID.text isEqual:@"N/A"] || [_productID.text isEqual:@"N/A"]) { |
| NSError * error = [[NSError alloc] initWithDomain:@"com.chiptool.customflow" |
| code:1 |
| userInfo:@{ NSLocalizedDescriptionKey : @"Vendor ID or Product Id is invalid." }]; |
| [self showError:error]; |
| return; |
| } |
| // make API call |
| NSLog(@"Making API call..."); |
| [self getRequest:[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSEnvironment"] |
| objectForKey:@"CommissioningCustomFlowLedgerUrl"] |
| vendorId:self->_vendorID.text |
| productId:self->_productID.text]; |
| } |
| |
| - (void)getRequest:(NSString *)url vendorId:(NSString *)vendorId productId:(NSString *)productId |
| { |
| [_activityIndicator startAnimating]; |
| _activityIndicator.hidden = NO; |
| NSString * targetUrl = [NSString stringWithFormat:@"%@/%@/%@", url, vendorId, productId]; |
| NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init]; |
| [request setHTTPMethod:@"GET"]; |
| [request setURL:[NSURL URLWithString:targetUrl]]; |
| |
| [[[NSURLSession sharedSession] |
| dataTaskWithRequest:request |
| completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { |
| NSString * myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; |
| NSLog(@"Data received: %@", myString); |
| self->_ledgerRespond = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; |
| [self getRequestCallback]; |
| }] resume]; |
| } |
| |
| - (void)getRequestCallback |
| { |
| BOOL commissioningCustomFlowUseMockFlag = (BOOL) |
| [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSEnvironment"] objectForKey:@"CommissioningCustomFlowUseMockFlag"]; |
| // use mock respond if useMockFlag is TRUE |
| if (commissioningCustomFlowUseMockFlag) { |
| NSLog(@"Using mock respond"); |
| _ledgerRespond = @{ |
| @"height" : @"mockHeight", |
| @"result" : @ { |
| @"vid" : @1, |
| @"pid" : @1, |
| @"cid" : @1, |
| @"name" : @"mockName", |
| @"owner" : @"mockOwner", |
| @"description" : @"mockDescription", |
| @"sku" : @"mockSku", |
| @"firmware_version" : @"mockFirmware", |
| @"hardware_version" : @"mockHardware", |
| @"tis_or_trp_testing_completed" : @TRUE, |
| @"CommissioningCustomFlowUrl" : @"https://lijusankar.github.io/commissioning-react-app/" |
| } |
| }; |
| } |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| self->_commissioningCustomFlowUrl.text = |
| [[self->_ledgerRespond objectForKey:@"result"] objectForKey:@"CommissioningCustomFlowUrl"]; |
| [self->_activityIndicator stopAnimating]; |
| self->_activityIndicator.hidden = YES; |
| self->_deviceModelInfoView.hidden = NO; |
| self->_redirectButton.hidden = NO; |
| }); |
| } |
| |
| // redirect |
| - (IBAction)redirectToUrl:(id)sender |
| { |
| [self redirectToUrl]; |
| } |
| |
| - (void)redirectToUrl |
| { |
| NSArray * redirectPayload = @[ @{ |
| @"version" : _versionLabel.text, |
| @"vendorID" : _vendorID.text, |
| @"productID" : _productID.text, |
| @"commissioingFlow" : _commissioningFlowLabel.text, |
| @"discriminator" : _discriminatorLabel.text, |
| @"setupPinCode" : _setupPinCodeLabel.text, |
| @"serialNumber" : _serialNumber.text, |
| @"rendezvousInformation" : _rendezVousInformation.text |
| } ]; |
| NSString * returnUrl = |
| [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSEnvironment"] objectForKey:@"CommissioningCustomFlowReturnUrl"]; |
| NSString * base64EncodedString = [self encodeStringTo64:redirectPayload]; |
| NSString * urlString = |
| [NSString stringWithFormat:@"%@?payload=%@&returnUrl=%@", _commissioningCustomFlowUrl.text, base64EncodedString, returnUrl]; |
| NSURL * url = [NSURL URLWithString:urlString]; |
| [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; |
| } |
| |
| - (NSString *)encodeStringTo64:(NSArray *)fromArray |
| { |
| NSData * jsonData = [NSJSONSerialization dataWithJSONObject:fromArray options:NSJSONWritingWithoutEscapingSlashes error:nil]; |
| NSString * base64String = [jsonData base64EncodedStringWithOptions:kNilOptions]; |
| return base64String; |
| } |
| |
| @synthesize description; |
| |
| @end |
| |
| @implementation CHIPToolDeviceAttestationDelegate |
| |
| - (instancetype)initWithViewController:(QRCodeViewController *)viewController |
| { |
| if (self = [super init]) { |
| _viewController = viewController; |
| } |
| return self; |
| } |
| |
| - (void)deviceAttestation:(MTRDeviceController *)controller failedForDevice:(void *)device error:(NSError * _Nonnull)error |
| { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| UIAlertController * alertController = [UIAlertController |
| alertControllerWithTitle:@"Device Attestation" |
| message:@"Device Attestation failed for device under commissioning. Do you wish to continue pairing?" |
| preferredStyle:UIAlertControllerStyleAlert]; |
| |
| [alertController addAction:[UIAlertAction actionWithTitle:@"No" |
| style:UIAlertActionStyleDefault |
| handler:^(UIAlertAction * action) { |
| NSError * err; |
| [controller continueCommissioningDevice:device |
| ignoreAttestationFailure:NO |
| error:&err]; |
| }]]; |
| |
| [alertController addAction:[UIAlertAction actionWithTitle:@"Continue" |
| style:UIAlertActionStyleDefault |
| handler:^(UIAlertAction * action) { |
| NSError * err; |
| [controller continueCommissioningDevice:device |
| ignoreAttestationFailure:YES |
| error:&err]; |
| }]]; |
| |
| [self.viewController presentViewController:alertController animated:YES completion:nil]; |
| }); |
| } |
| |
| @end |