diff --git a/EspressifProvision/EspressifProvision.xcodeproj/project.pbxproj b/EspressifProvision/EspressifProvision.xcodeproj/project.pbxproj index 4451932..d22e1c1 100644 --- a/EspressifProvision/EspressifProvision.xcodeproj/project.pbxproj +++ b/EspressifProvision/EspressifProvision.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -44,6 +44,7 @@ 14DE671C20EC94C600941522 /* ConfigureAVS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DE671B20EC94C600941522 /* ConfigureAVS.swift */; }; 14DE671E20ECD04100941522 /* BLETransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14DE671D20ECD04100941522 /* BLETransport.swift */; }; 7E89B3398606A35D1BCB7893 /* Pods_EspressifProvisionAll_EspressifProvision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FC5952F5F6F4A07A0ED190E /* Pods_EspressifProvisionAll_EspressifProvision.framework */; }; + F11B547422DDE41A00F7D0D3 /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11B547322DDE41A00F7D0D3 /* StatusViewController.swift */; }; F1342A48229D697B00AD8626 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1342A47229D697B00AD8626 /* Constants.swift */; }; F1342A4B229E7D8700AD8626 /* DeviceListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1342A4A229E7D8700AD8626 /* DeviceListTableViewCell.swift */; }; F184C9E4228C3E8B0034C5B7 /* AESDecryptor.m in Sources */ = {isa = PBXBuildFile; fileRef = F184C9E3228C3E8B0034C5B7 /* AESDecryptor.m */; }; @@ -51,6 +52,7 @@ F18C3AB8229BEDB2007C197F /* ScannedLocalDevicesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18C3AB7229BEDB2007C197F /* ScannedLocalDevicesVC.swift */; }; F18C3ABE229BF4D4007C197F /* SSDPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18C3ABC229BF4D4007C197F /* SSDPService.swift */; }; F18C3ABF229BF4D4007C197F /* SSDPDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = F18C3ABD229BF4D4007C197F /* SSDPDiscovery.swift */; }; + F1B464B5239A50BE0090B346 /* AppInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B464B4239A50BE0090B346 /* AppInfoViewController.swift */; }; F1DCEC292296CB94005918FE /* ScanWifiList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DCEC282296CB94005918FE /* ScanWifiList.swift */; }; F1DCEC2D2297D577005918FE /* WifiListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DCEC2C2297D577005918FE /* WifiListTableViewCell.swift */; }; F1DD7095229FC8620092DAB9 /* AmazonEmberDisplay_Lt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1DD7094229FC8590092DAB9 /* AmazonEmberDisplay_Lt.ttf */; }; @@ -58,6 +60,13 @@ F1DD7099229FCEDE0092DAB9 /* DeviceDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD7098229FCEDE0092DAB9 /* DeviceDetailViewController.swift */; }; F1DD709B229FDE6C0092DAB9 /* AmazonEmberDisplay_Md.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1DD709A229FDE6C0092DAB9 /* AmazonEmberDisplay_Md.ttf */; }; F1DD709D229FDF5E0092DAB9 /* AmazonEmberDisplay_Rg.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F1DD709C229FDF5E0092DAB9 /* AmazonEmberDisplay_Rg.ttf */; }; + F1DDA06C2359CE590005F982 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA06B2359CE590005F982 /* Utility.swift */; }; + F1DDA06E2360198E0005F982 /* ConfigureDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA06D2360198E0005F982 /* ConfigureDevice.swift */; }; + F1DDA070237AB9210005F982 /* DeviceSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA06F237AB9210005F982 /* DeviceSettingViewController.swift */; }; + F1DDA0722382AD880005F982 /* SoundSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA0712382AD880005F982 /* SoundSettingViewController.swift */; }; + F1DDA0742383C7130005F982 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA0732383C7130005F982 /* AboutViewController.swift */; }; + F1DDA07623855BFB0005F982 /* LanguageListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DDA07523855BFB0005F982 /* LanguageListTableViewController.swift */; }; + F1EB558923043B1400AEEBE3 /* BLEDeviceListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1EB558823043B1400AEEBE3 /* BLEDeviceListViewCell.swift */; }; F1F339572296703C000F53A0 /* wifi_scan.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F339562296703C000F53A0 /* wifi_scan.pb.swift */; }; F6D7C9B0F5FDCEEFFA78A8C8 /* Pods_EspressifProvisionAll_EspressifProvisionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BEBDCCD5B5E7E9D6B8F9AE3 /* Pods_EspressifProvisionAll_EspressifProvisionTests.framework */; }; /* End PBXBuildFile section */ @@ -151,6 +160,7 @@ D2B4EC5704D46C3EA9A47720 /* Pods-EspressifProvisionAll-EspressifProvisionTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EspressifProvisionAll-EspressifProvisionTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-EspressifProvisionAll-EspressifProvisionTests/Pods-EspressifProvisionAll-EspressifProvisionTests.release.xcconfig"; sourceTree = ""; }; D3C0A52210E731723240B81F /* Pods-EspressifProvisionAll-EspressifProvision.blesec1avs-release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EspressifProvisionAll-EspressifProvision.blesec1avs-release.xcconfig"; path = "Pods/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision.blesec1avs-release.xcconfig"; sourceTree = ""; }; EA24507D63588F8C9C0C451B /* Pods-EspressifProvisionAll-EspressifProvision.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EspressifProvisionAll-EspressifProvision.debug.xcconfig"; path = "Pods/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision.debug.xcconfig"; sourceTree = ""; }; + F11B547322DDE41A00F7D0D3 /* StatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewController.swift; sourceTree = ""; }; F1342A47229D697B00AD8626 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; F1342A4A229E7D8700AD8626 /* DeviceListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListTableViewCell.swift; sourceTree = ""; }; F184C9E2228C3E8B0034C5B7 /* AESDecryptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AESDecryptor.h; sourceTree = ""; }; @@ -159,6 +169,7 @@ F18C3AB7229BEDB2007C197F /* ScannedLocalDevicesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedLocalDevicesVC.swift; sourceTree = ""; }; F18C3ABC229BF4D4007C197F /* SSDPService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSDPService.swift; sourceTree = ""; }; F18C3ABD229BF4D4007C197F /* SSDPDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSDPDiscovery.swift; sourceTree = ""; }; + F1B464B4239A50BE0090B346 /* AppInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoViewController.swift; sourceTree = ""; }; F1DCEC282296CB94005918FE /* ScanWifiList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanWifiList.swift; sourceTree = ""; }; F1DCEC2C2297D577005918FE /* WifiListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WifiListTableViewCell.swift; sourceTree = ""; }; F1DD7094229FC8590092DAB9 /* AmazonEmberDisplay_Lt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = AmazonEmberDisplay_Lt.ttf; sourceTree = ""; }; @@ -166,6 +177,13 @@ F1DD7098229FCEDE0092DAB9 /* DeviceDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceDetailViewController.swift; sourceTree = ""; }; F1DD709A229FDE6C0092DAB9 /* AmazonEmberDisplay_Md.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = AmazonEmberDisplay_Md.ttf; sourceTree = ""; }; F1DD709C229FDF5E0092DAB9 /* AmazonEmberDisplay_Rg.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = AmazonEmberDisplay_Rg.ttf; sourceTree = ""; }; + F1DDA06B2359CE590005F982 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; + F1DDA06D2360198E0005F982 /* ConfigureDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigureDevice.swift; sourceTree = ""; }; + F1DDA06F237AB9210005F982 /* DeviceSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettingViewController.swift; sourceTree = ""; }; + F1DDA0712382AD880005F982 /* SoundSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundSettingViewController.swift; sourceTree = ""; }; + F1DDA0732383C7130005F982 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; + F1DDA07523855BFB0005F982 /* LanguageListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageListTableViewController.swift; sourceTree = ""; }; + F1EB558823043B1400AEEBE3 /* BLEDeviceListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEDeviceListViewCell.swift; sourceTree = ""; }; F1F339562296703C000F53A0 /* wifi_scan.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = wifi_scan.pb.swift; sourceTree = ""; }; F5A6E92635049FCEB2D5787E /* Pods-EspressifProvisionAll-EspressifProvision.wifisec1avs-debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EspressifProvisionAll-EspressifProvision.wifisec1avs-debug.xcconfig"; path = "Pods/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision.wifisec1avs-debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -336,6 +354,9 @@ 143146B420EB273B0017E119 /* ProvisionViewController.swift */, 14706916211050EE00F8E1D8 /* BLELandingViewController.swift */, 144149432126AE09002AD5E1 /* SuccessViewController.swift */, + F11B547322DDE41A00F7D0D3 /* StatusViewController.swift */, + F1EB558823043B1400AEEBE3 /* BLEDeviceListViewCell.swift */, + F1B464B4239A50BE0090B346 /* AppInfoViewController.swift */, ); path = ui; sourceTree = ""; @@ -357,6 +378,7 @@ isa = PBXGroup; children = ( 14DE671B20EC94C600941522 /* ConfigureAVS.swift */, + F1DDA06D2360198E0005F982 /* ConfigureDevice.swift */, ); path = avs; sourceTree = ""; @@ -411,6 +433,7 @@ children = ( F18C3ABB229BF4D4007C197F /* SSDPClient */, F1342A47229D697B00AD8626 /* Constants.swift */, + F1DDA06B2359CE590005F982 /* Utility.swift */, ); path = Utilities; sourceTree = ""; @@ -421,6 +444,10 @@ F18C3AB7229BEDB2007C197F /* ScannedLocalDevicesVC.swift */, F1342A4A229E7D8700AD8626 /* DeviceListTableViewCell.swift */, F1DD7098229FCEDE0092DAB9 /* DeviceDetailViewController.swift */, + F1DDA06F237AB9210005F982 /* DeviceSettingViewController.swift */, + F1DDA0712382AD880005F982 /* SoundSettingViewController.swift */, + F1DDA0732383C7130005F982 /* AboutViewController.swift */, + F1DDA07523855BFB0005F982 /* LanguageListTableViewController.swift */, ); path = ManageDevice; sourceTree = ""; @@ -662,19 +689,12 @@ buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/BlueSocket/Socket.framework", - "${BUILT_PRODUCTS_DIR}/Curve25519/Curve25519.framework", - "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Socket.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvision/Pods-EspressifProvisionAll-EspressifProvision-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -686,19 +706,12 @@ buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvisionTests/Pods-EspressifProvisionAll-EspressifProvisionTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/BlueSocket/Socket.framework", - "${BUILT_PRODUCTS_DIR}/Curve25519/Curve25519.framework", - "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SwiftProtobuf/SwiftProtobuf.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvisionTests/Pods-EspressifProvisionAll-EspressifProvisionTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Socket.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftProtobuf.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-EspressifProvisionAll-EspressifProvisionTests/Pods-EspressifProvisionAll-EspressifProvisionTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -734,29 +747,37 @@ F1342A48229D697B00AD8626 /* Constants.swift in Sources */, F1DD7099229FCEDE0092DAB9 /* DeviceDetailViewController.swift in Sources */, 1431469820E387C60017E119 /* sec1.pb.swift in Sources */, + F1DDA07623855BFB0005F982 /* LanguageListTableViewController.swift in Sources */, F18C3AB6229BECE1007C197F /* AlexaDevice.swift in Sources */, F18C3ABE229BF4D4007C197F /* SSDPService.swift in Sources */, 14DE671E20ECD04100941522 /* BLETransport.swift in Sources */, 149774DF210C87AB00022841 /* DataExtensions.swift in Sources */, 1431468E20E3823F0017E119 /* Provision.swift in Sources */, + F1DDA0722382AD880005F982 /* SoundSettingViewController.swift in Sources */, 1431469920E387C60017E119 /* sec0.pb.swift in Sources */, 14DE671C20EC94C600941522 /* ConfigureAVS.swift in Sources */, 1431468420DF69C30017E119 /* Security1.swift in Sources */, F1DCEC2D2297D577005918FE /* WifiListTableViewCell.swift in Sources */, 143146B520EB273B0017E119 /* ProvisionViewController.swift in Sources */, + F1DDA06C2359CE590005F982 /* Utility.swift in Sources */, 1431462920DE994B0017E119 /* ViewController.swift in Sources */, 149774DD210C799900022841 /* CryptoAES.swift in Sources */, + F11B547422DDE41A00F7D0D3 /* StatusViewController.swift in Sources */, 1431466920DEAD1A0017E119 /* SoftAPTransport.swift in Sources */, 1431469B20E387C60017E119 /* constants.pb.swift in Sources */, F1342A4B229E7D8700AD8626 /* DeviceListTableViewCell.swift in Sources */, + F1DDA0742383C7130005F982 /* AboutViewController.swift in Sources */, 149774E1210C89E600022841 /* ArrayExtensions.swift in Sources */, + F1DDA06E2360198E0005F982 /* ConfigureDevice.swift in Sources */, 1431466720DEAC220017E119 /* Security.swift in Sources */, 1431468720DFA58E0017E119 /* HexUtils.swift in Sources */, + F1EB558923043B1400AEEBE3 /* BLEDeviceListViewCell.swift in Sources */, F18C3AB8229BEDB2007C197F /* ScannedLocalDevicesVC.swift in Sources */, F1F339572296703C000F53A0 /* wifi_scan.pb.swift in Sources */, F18C3ABF229BF4D4007C197F /* SSDPDiscovery.swift in Sources */, 143146B320EB22470017E119 /* ProvisionLandingViewController.swift in Sources */, 1431468B20E37B980017E119 /* Session.swift in Sources */, + F1B464B5239A50BE0090B346 /* AppInfoViewController.swift in Sources */, 143146AA20EA29E90017E119 /* LoginWithAmazonViewController.swift in Sources */, 1431469F20E387C60017E119 /* wifi_constants.pb.swift in Sources */, 149774E3210C8A0000022841 /* StringExtensions.swift in Sources */, @@ -769,6 +790,7 @@ F184C9E4228C3E8B0034C5B7 /* AESDecryptor.m in Sources */, 1431469E20E387C60017E119 /* avsconfig.pb.swift in Sources */, F1DCEC292296CB94005918FE /* ScanWifiList.swift in Sources */, + F1DDA070237AB9210005F982 /* DeviceSettingViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/EspressifProvision/EspressifProvision/AppDelegate.swift b/EspressifProvision/EspressifProvision/AppDelegate.swift index 70954d2..436a18d 100644 --- a/EspressifProvision/EspressifProvision/AppDelegate.swift +++ b/EspressifProvision/EspressifProvision/AppDelegate.swift @@ -25,6 +25,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. +// let BarButtonItemAppearance = UIBarButtonItem.appearance() +// BarButtonItemAppearance.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal) return true } @@ -51,6 +53,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + // Source application is not available in iOS 13 and above + if #available(iOS 13, *) { + return AMZNAuthorizationManager.handleOpen(url, sourceApplication: "") + } return AMZNAuthorizationManager.handleOpen(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as! String) } } diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/Contents.json index e196d82..b966d19 100644 --- a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,109 +3,109 @@ { "size" : "20x20", "idiom" : "iphone", - "filename" : "espressif_app_icon-20@2x.png", + "filename" : "iconfinder-icon_4_40x40.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", - "filename" : "espressif_app_icon-20@3x.png", + "filename" : "iconfinder-icon_6_60x60.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "espressif_app_icon-29@2x.png", + "filename" : "iconfinder-icon_5_58x58.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "espressif_app_icon-29@3x.png", + "filename" : "iconfinder-icon_7_87x87.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "espressif_app_icon-40@2x.png", + "filename" : "iconfinder-icon_11_80x80.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "espressif_app_icon-40@3x.png", + "filename" : "iconfinder-icon_120x120-1.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "espressif_app_icon-60@2x.png", + "filename" : "iconfinder-icon_120x120.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "espressif_app_icon-60@3x.png", + "filename" : "iconfinder-icon_1_180x180.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "espressif_app_icon-20.png", + "filename" : "iconfinder-icon_2_20x20.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "espressif_app_icon-20@2x.png", + "filename" : "iconfinder-icon_4_40x40-1.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "espressif_app_icon-29.png", + "filename" : "iconfinder-icon_3_29x29.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "espressif_app_icon-29@2x.png", + "filename" : "iconfinder-icon_5_58x58-2.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "espressif_app_icon-40.png", + "filename" : "iconfinder-icon_4_40x40-2.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "espressif_app_icon-40@2x.png", + "filename" : "iconfinder-icon_11_80x80-1.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "espressif_app_icon-76.png", + "filename" : "iconfinder-icon_12_76x76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "espressif_app_icon-76@2x.png", + "filename" : "iconfinder-icon_8_152x152.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", - "filename" : "espressif_app_icon-83.5@2x.png", + "filename" : "iconfinder-icon_9_167x167.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", - "filename" : "espressif_app_icon-1024.png", + "filename" : "iconfinder-icon_10_1024x1024.png", "scale" : "1x" } ], diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-1024.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-1024.png deleted file mode 100644 index b2aad0d..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-1024.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20.png deleted file mode 100644 index 8ebe7b1..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@2x.png deleted file mode 100644 index 8364f33..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@3x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@3x.png deleted file mode 100644 index 3b25c90..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-20@3x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29.png deleted file mode 100644 index 3608cd4..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@2x.png deleted file mode 100644 index a6a50e3..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@3x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@3x.png deleted file mode 100644 index 35f872e..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-29@3x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40.png deleted file mode 100644 index 8364f33..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@2x.png deleted file mode 100644 index 4b49f4c..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@3x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@3x.png deleted file mode 100644 index ee40b58..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-40@3x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@2x.png deleted file mode 100644 index ee40b58..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@3x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@3x.png deleted file mode 100644 index 7ba26df..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-60@3x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76.png deleted file mode 100644 index 06a48fe..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76@2x.png deleted file mode 100644 index 6c88ae5..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-76@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-83.5@2x.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-83.5@2x.png deleted file mode 100644 index 199d977..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/espressif_app_icon-83.5@2x.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_10_1024x1024.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_10_1024x1024.png new file mode 100644 index 0000000..23cc02b Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_10_1024x1024.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80-1.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80-1.png new file mode 100644 index 0000000..31fe88f Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80-1.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80.png new file mode 100644 index 0000000..31fe88f Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_11_80x80.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120-1.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120-1.png new file mode 100644 index 0000000..e3c6c70 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120-1.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120.png new file mode 100644 index 0000000..e3c6c70 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_120x120.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_12_76x76.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_12_76x76.png new file mode 100644 index 0000000..48d0a48 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_12_76x76.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_1_180x180.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_1_180x180.png new file mode 100644 index 0000000..599873f Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_1_180x180.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_2_20x20.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_2_20x20.png new file mode 100644 index 0000000..4023550 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_2_20x20.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_3_29x29.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_3_29x29.png new file mode 100644 index 0000000..0bc3c36 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_3_29x29.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-1.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-1.png new file mode 100644 index 0000000..fa3ead6 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-1.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-2.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-2.png new file mode 100644 index 0000000..fa3ead6 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40-2.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40.png new file mode 100644 index 0000000..fa3ead6 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_4_40x40.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58-2.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58-2.png new file mode 100644 index 0000000..7d893be Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58-2.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58.png new file mode 100644 index 0000000..7d893be Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_5_58x58.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_6_60x60.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_6_60x60.png new file mode 100644 index 0000000..80f14fb Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_6_60x60.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_7_87x87.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_7_87x87.png new file mode 100644 index 0000000..15d9278 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_7_87x87.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_8_152x152.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_8_152x152.png new file mode 100644 index 0000000..a1856b4 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_8_152x152.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_9_167x167.png b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_9_167x167.png new file mode 100644 index 0000000..4ad2b99 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/AppIcon.appiconset/iconfinder-icon_9_167x167.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-128.png b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-128.png new file mode 100644 index 0000000..90fe0f0 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-128.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-256.png b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-256.png new file mode 100644 index 0000000..649af39 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-256.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-512.png b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-512.png new file mode 100644 index 0000000..402b45d Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/211802-512.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/Contents.json new file mode 100644 index 0000000..05e9654 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/add_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "211802-128.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "211802-256.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "211802-512.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-3.PNG b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-3.PNG new file mode 100644 index 0000000..1c0e12c Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-3.PNG differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-4.PNG b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-4.PNG new file mode 100644 index 0000000..1c0e12c Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-4.PNG differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-5.PNG b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-5.PNG new file mode 100644 index 0000000..1c0e12c Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/18.8.6 logo design-5.PNG differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/Contents.json new file mode 100644 index 0000000..fb2d307 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/app_info_logo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "18.8.6 logo design-3.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "18.8.6 logo design-4.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "18.8.6 logo design-5.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-128.png b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-128.png new file mode 100644 index 0000000..f81acef Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-128.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-256.png b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-256.png new file mode 100644 index 0000000..d41eaff Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-256.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-512.png b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-512.png new file mode 100644 index 0000000..2b2ff77 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/925802-512.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/Contents.json index e46f1f0..9bc869e 100644 --- a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/Contents.json +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/Contents.json @@ -2,15 +2,17 @@ "images" : [ { "idiom" : "universal", + "filename" : "925802-128.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "bluetooth_icon.png", + "filename" : "925802-256.png", "scale" : "2x" }, { "idiom" : "universal", + "filename" : "925802-512.png", "scale" : "3x" } ], diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/bluetooth_icon.png b/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/bluetooth_icon.png deleted file mode 100644 index 9dd15b6..0000000 Binary files a/EspressifProvision/EspressifProvision/Assets.xcassets/bluetooth_icon.imageset/bluetooth_icon.png and /dev/null differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-128 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-128 (1).png new file mode 100644 index 0000000..759b524 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-128 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-256 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-256 (1).png new file mode 100644 index 0000000..8dce058 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-256 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-512 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-512 (1).png new file mode 100644 index 0000000..b1b1a1f Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/4280479-512 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/Contents.json new file mode 100644 index 0000000..6c6331e --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/hide_password.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "4280479-128 (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "4280479-256 (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "4280479-512 (1).png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-32 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-32 (1).png new file mode 100644 index 0000000..010cee5 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-32 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-48 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-48 (1).png new file mode 100644 index 0000000..361c5a4 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-48 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-64 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-64 (1).png new file mode 100644 index 0000000..76495f6 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/326659-64 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/Contents.json new file mode 100644 index 0000000..79b8c93 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/info_icon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "326659-32 (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "326659-48 (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "326659-64 (1).png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "original" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-128 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-128 (1).png new file mode 100644 index 0000000..bed1998 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-128 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-256 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-256 (1).png new file mode 100644 index 0000000..59438b0 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-256 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-512 (1).png b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-512 (1).png new file mode 100644 index 0000000..57e46cb Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/211882-512 (1).png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/Contents.json new file mode 100644 index 0000000..21f5ef5 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/refresh_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "211882-128 (1).png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "211882-256 (1).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "211882-512 (1).png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-128.png b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-128.png new file mode 100644 index 0000000..e7d163a Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-128.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-256.png b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-256.png new file mode 100644 index 0000000..946c425 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-256.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-64.png b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-64.png new file mode 100644 index 0000000..1335a25 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/3209393-64.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/Contents.json new file mode 100644 index 0000000..998b2d2 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/right_arrow.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "3209393-64.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "3209393-128.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "3209393-256.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-128.png b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-128.png new file mode 100644 index 0000000..3a260f5 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-128.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-256.png b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-256.png new file mode 100644 index 0000000..d5c514b Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-256.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-512.png b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-512.png new file mode 100644 index 0000000..f4c49e8 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/4280479-512.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/Contents.json new file mode 100644 index 0000000..72534b4 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/show_password.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "4280479-128.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "4280479-256.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "4280479-512.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-128.png b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-128.png new file mode 100644 index 0000000..5cc103d Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-128.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-32.png b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-32.png new file mode 100644 index 0000000..ec0f6ff Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-32.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-64.png b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-64.png new file mode 100644 index 0000000..a91acf4 Binary files /dev/null and b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/293687-64.png differ diff --git a/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/Contents.json b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/Contents.json new file mode 100644 index 0000000..d5fc6b8 --- /dev/null +++ b/EspressifProvision/EspressifProvision/Assets.xcassets/wifi_security.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "293687-32.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "293687-64.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "293687-128.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/EspressifProvision/EspressifProvision/Base.lproj/Main.storyboard b/EspressifProvision/EspressifProvision/Base.lproj/Main.storyboard index dab744c..e4e8f02 100644 --- a/EspressifProvision/EspressifProvision/Base.lproj/Main.storyboard +++ b/EspressifProvision/EspressifProvision/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -26,19 +24,85 @@ - + + + + + + + + + + + + + Your device includes access to Alexa. +Connect your Amazon account to access personalized features. + + + + + + + + + + + + Alexa allows you to use your voice to play music and get news, sports, scores, weather and more. +All you have to do is ask Alexa. + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + @@ -71,15 +135,15 @@ - + - diff --git a/EspressifProvision/EspressifProvision/Info.plist b/EspressifProvision/EspressifProvision/Info.plist index ebfff1b..563efd8 100644 --- a/EspressifProvision/EspressifProvision/Info.plist +++ b/EspressifProvision/EspressifProvision/Info.plist @@ -3,7 +3,7 @@ APIKey - eyJhbGciOiJSU0EtU0hBMjU2IiwidmVyIjoiMSJ9.eyJ2ZXIiOiIzIiwiZW5kcG9pbnRzIjp7ImF1dGh6IjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS9hcC9vYSIsInRva2VuRXhjaGFuZ2UiOiJodHRwczovL2FwaS5hbWF6b24uY29tL2F1dGgvbzIvdG9rZW4ifSwiY2xpZW50SWQiOiJhbXpuMS5hcHBsaWNhdGlvbi1vYTItY2xpZW50Ljk0NTA0YjhjZjQ0ZDQ2OTQ4YmJkNmMwYjdlNmViNTljIiwiYXBwRmFtaWx5SWQiOiJhbXpuMS5hcHBsaWNhdGlvbi45ZjA5NDkzYzQxNDI0YjczOGE0YWNmYThkNWVlN2Y3MCIsImJ1bmRsZVNlZWRJZCI6ImNvbS5lc3ByZXNzaWYuaW50cHJvdmJsZWF2cyIsImJ1bmRsZUlkIjoiY29tLmVzcHJlc3NpZi5pbnRwcm92YmxlYXZzIiwiaXNzIjoiQW1hem9uIiwidHlwZSI6IkFQSUtleSIsImFwcFZhcmlhbnRJZCI6ImFtem4xLmFwcGxpY2F0aW9uLWNsaWVudC41ZjRjZTE2MWU1MzQ0Zjk0OWU2YTVmMTA3Yzc5ZDhmNCIsInRydXN0UG9vbCI6bnVsbCwiYXBwSWQiOiJhbXpuMS5hcHBsaWNhdGlvbi1jbGllbnQuNWY0Y2UxNjFlNTM0NGY5NDllNmE1ZjEwN2M3OWQ4ZjQiLCJpZCI6IjA0MGEwNjkyLTc4ODUtMTFlOS1hM2EwLWRiYzNmZDYzMmEyZiIsImlhdCI6IjE1NTgwODQ4MzA1MjUifQ==.HiXv0UKO7zl7wZABuAkXrRhUAwa4f4nu5X2ThcDL/ZUqgRTZpWGyhwvmWKeLVYV81QcjLJaNU1lr3wWGYT9utEWvsDzMZm4eLHtnkg38n8QZZjaCPC8EO+3ExU3mU+Zs5RJHRq0we4lZypM5SCna0qKFZ5BX5D6iCgkn+l/bhHIvc8M3a3EWt6689BLVQNqQer8+wKYhG96/xXz5kH+7YMKcu0hgpp5JIeBSLcx1bOi+ymK3QWhCJPNKV1t98joBAVPI9Z1/0Qa0WW6WJ97yhRZTs7XFm8gyz6vlnUsrfBArLrfb+S2/Rz1U2eTQzzkSC9uF0LNKHGdtUYRQXKNtcw== + eyJhbGciOiJSU0EtU0hBMjU2IiwidmVyIjoiMSJ9.eyJ2ZXIiOiIzIiwiZW5kcG9pbnRzIjp7ImF1dGh6IjoiaHR0cHM6Ly93d3cuYW1hem9uLmNvbS9hcC9vYSIsInRva2VuRXhjaGFuZ2UiOiJodHRwczovL2FwaS5hbWF6b24uY29tL2F1dGgvbzIvdG9rZW4ifSwiY2xpZW50SWQiOiJhbXpuMS5hcHBsaWNhdGlvbi1vYTItY2xpZW50LmU4YzA2NTQ1ZDk3NjRhMDViOGI2ZDkxZWVjZmViMjNhIiwiYXBwRmFtaWx5SWQiOiJhbXpuMS5hcHBsaWNhdGlvbi4wNjMyOTliODRiMWM0NzAwYThhODRhNDcwNzc3MmQ0MCIsImJ1bmRsZVNlZWRJZCI6ImNvbS5lc3ByZXNzaWYucHJvdmJsZWF2cyIsImJ1bmRsZUlkIjoiY29tLmVzcHJlc3NpZi5wcm92YmxlYXZzIiwiaXNzIjoiQW1hem9uIiwidHlwZSI6IkFQSUtleSIsImFwcFZhcmlhbnRJZCI6ImFtem4xLmFwcGxpY2F0aW9uLWNsaWVudC4yM2ZiYTMzMWIzZDk0MDM5OTMyMmJiYzUwY2YyNzQ5NSIsInRydXN0UG9vbCI6bnVsbCwiYXBwSWQiOiJhbXpuMS5hcHBsaWNhdGlvbi1jbGllbnQuMjNmYmEzMzFiM2Q5NDAzOTkzMjJiYmM1MGNmMjc0OTUiLCJpZCI6IjJjNjc2ZDk1LTc4ODUtMTFlOS1hNjdiLWMzOGNmMjU3YTgwYSIsImlhdCI6IjE1NTgwODQ4OTgyMzMifQ==.UUg/wDgQHhGwbXIq9ev06zXeODbrdN1wv/hlvSTdN4s7zMLMnC4FJ17Ra1EmlK0FkD6QmoTKWlgBss/ne8WF7dUWgtsPG3KLaZv7MrQwx7skTXn05xdFENiWPPCvmCAWAvgW0lNp9QoxQzsIDS3KwD6k9KI4YhDQp5CtgxgZ/JfBVwsVyJ0XeBYpNnmQpphMU1U9zNh7Npzn66AMmJ0RCbsPL8mkP8na/7mGH5/HqDr+f605F1/tO2qo/WVZ/h0BEZO9y9Wwc4anEcFzbYeqyZU8jpR+Oyl4+tdDGZUB2lrj3L1d+uV4a3572ADNhqb7iOzWH4bSPmsFoDPbgouJRg== BLEAVSConfigUUID ff54 BLEConfigUUID @@ -19,7 +19,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Int-BLE-AVS-Prov + ESP Alexa CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -31,26 +31,32 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1 + $(MARKETING_VERSION) CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLName - com.espressif.intprovbleavs + com.espressif.provbleavs CFBundleURLSchemes - amzn-com.espressif.intprovbleavs + amzn-com.espressif.provbleavs CFBundleVersion - 2 + $(CURRENT_PROJECT_VERSION) CodeVerifier abcd1234 + LSApplicationQueriesSchemes + + alexa + LSRequiresIPhoneOS + NSBluetoothAlwaysUsageDescription + Connect with BLE Compatible devices for Provisioning NSBluetoothPeripheralUsageDescription Connect with BLE Compatible devices for Provisioning ProductId @@ -69,6 +75,8 @@ armv7 + UIRequiresFullScreen + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -76,10 +84,9 @@ UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight + UIUserInterfaceStyle + Light WifiBaseUrl 192.168.4.1:80 WifiNetworkNamePrefix diff --git a/EspressifProvision/EspressifProvision/Model/AlexaDevice.swift b/EspressifProvision/EspressifProvision/Model/AlexaDevice.swift index 346ab05..9f27191 100644 --- a/EspressifProvision/EspressifProvision/Model/AlexaDevice.swift +++ b/EspressifProvision/EspressifProvision/Model/AlexaDevice.swift @@ -15,9 +15,17 @@ class AlexaDevice: NSObject { var softwareVersion: String? var friendlyname: String? var uuid: String? + var deviceName: String? + var connectedWifi: String? + var fwVersion: String? + var mac: String? + var serialNumber: String? + var startToneEnabled: Bool? + var endToneEnabled: Bool? + var volume: UInt32? + var language: Avs_Locale? init(hostAddr: String) { hostAddress = hostAddr - friendlyname = nil } } diff --git a/EspressifProvision/EspressifProvision/Utilities/Constants.swift b/EspressifProvision/EspressifProvision/Utilities/Constants.swift index 1ff7fbe..bbf98bf 100644 --- a/EspressifProvision/EspressifProvision/Utilities/Constants.swift +++ b/EspressifProvision/EspressifProvision/Utilities/Constants.swift @@ -11,13 +11,31 @@ import MBProgressHUD import UIKit struct Constants { + static let scanCharacteristic = "scan" + static let sessionCharacterstic = "session" + static let configCharacterstic = "config" + static let versionCharacterstic = "ver" + static let avsConfigCharacterstic = "avsconfig" + static let deviceInfoStoryboardID = "versionInfo" + + // Device version info + static let provKey = "prov" + static let capabilitiesKey = "cap" + static let wifiScanCapability = "wifi_scan" + static let noProofCapability = "no_pop" + static let friendlynameKey = "friendlyname" static let UUIDKey = "uuid" // Reuse identifier static let deviceListCellReuseIdentifier = "deviceListCell" static let deviceDetailVCIndentifier = "deviceDetailVC" + static let deviceSettingVCIndentifier = "deviceSettingVC" + static let soundSettingVCIdentifier = "soundSettingVC" + static let aboutVCIdentifier = "aboutVC" + static let languageListVCIdentifier = "languageListVC" + // Method to show loader on any view static func showLoader(message: String, view: UIView) { DispatchQueue.main.async { let loader = MBProgressHUD.showAdded(to: view, animated: true) @@ -25,4 +43,11 @@ struct Constants { loader.label.text = message } } + + // Method to hide loader from any view + static func hideLoader(view: UIView) { + DispatchQueue.main.async { + MBProgressHUD.hide(for: view, animated: true) + } + } } diff --git a/EspressifProvision/EspressifProvision/Utilities/Utility.swift b/EspressifProvision/EspressifProvision/Utilities/Utility.swift new file mode 100644 index 0000000..e71989c --- /dev/null +++ b/EspressifProvision/EspressifProvision/Utilities/Utility.swift @@ -0,0 +1,55 @@ +// +// Utility.swift +// EspressifProvision +// +// Created by Vikas Chandra on 18/10/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import CoreBluetooth +import Foundation + +class Utility { + static let deviceNamePrefix = Bundle.main.infoDictionary?["BLEDeviceNamePrefix"] as? String ?? "ESP-Alexa-" + static let allowPrefixFilter = Bundle.main.infoDictionary?["AllowFilteringByPrefix"] as? Bool ?? false + static let baseUrl = Bundle.main.infoDictionary?["WifiBaseUrl"] as? String ?? "192.168.4.1:80" + + var deviceName = "" + var configPath = "prov-config" + var versionPath = "proto-ver" + var scanPath: String? + var sessionPath = "prov-session" + var avsConfigPath = "avsconfig" + var peripheralConfigured = false + var sessionCharacteristic: CBCharacteristic! + var configUUIDMap: [String: CBUUID] = [:] + var deviceVersionInfo: NSDictionary? + + init() { + configUUIDMap[configPath] = CBUUID(string: "ff52") + configUUIDMap[sessionPath] = CBUUID(string: "ff51") + } + + func processDescriptor(descriptor: CBDescriptor) { + if let value = descriptor.value as? String { + if value.contains(Constants.scanCharacteristic) { + scanPath = value + configUUIDMap.updateValue(descriptor.characteristic.uuid, forKey: scanPath!) + } else if value.contains(Constants.sessionCharacterstic) { + sessionPath = value + peripheralConfigured = true + sessionCharacteristic = descriptor.characteristic + configUUIDMap.updateValue(descriptor.characteristic.uuid, forKey: sessionPath) + } else if value.contains(Constants.configCharacterstic) { + configPath = value + configUUIDMap.updateValue(descriptor.characteristic.uuid, forKey: configPath) + } else if value.contains(Constants.versionCharacterstic) { + versionPath = value + configUUIDMap.updateValue(descriptor.characteristic.uuid, forKey: versionPath) + } else if value.contains(Constants.avsConfigCharacterstic) { + avsConfigPath = value + configUUIDMap.updateValue(descriptor.characteristic.uuid, forKey: avsConfigPath) + } + } + } +} diff --git a/EspressifProvision/EspressifProvision/ViewController.swift b/EspressifProvision/EspressifProvision/ViewController.swift index 753e2bb..3673fcf 100644 --- a/EspressifProvision/EspressifProvision/ViewController.swift +++ b/EspressifProvision/EspressifProvision/ViewController.swift @@ -23,8 +23,6 @@ import UIKit class ViewController: UIViewController { // Provisioning private let pop = Bundle.main.infoDictionary?["ProofOfPossession"] as! String - private let ssid = "ESPIndia" - private let passphrase = "" private let avsdetails = ["codeChallenge": "6c7nGrky_ehjM40Ivk3p3-OeoEm9r7NCzmWexUULaa4", "redirectUri": "amzn-com.espressif.avs.provisioning.ble://?methodName=signin", "authCode": "", "clientId": "amzn1.application-oa2-"] // AVS private let productId = Bundle.main.infoDictionary?["ProductId"] as! String @@ -48,32 +46,8 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() productDSN = generateProductDSN() - } - - func provisionWithAPIs(_: Any) { - #if SEC1 - security = Security1(proofOfPossession: pop) - #else - security = Security0() - #endif - - #if BLE - var configUUIDMap: [String: String] = [Provision.PROVISIONING_CONFIG_PATH: configUUIDString] - #if AVS - configUUIDMap[ConfigureAVS.AVS_CONFIG_PATH] = avsconfigUUIDString - #endif - bleTransport = BLETransport(serviceUUIDString: serviceUUIDString, - sessionUUIDString: sessionUUIDString, - configUUIDMap: configUUIDMap, - deviceNamePrefix: deviceNamePrefix, - scanTimeout: 5.0) - bleTransport?.scan(delegate: self) - transport = bleTransport - - #else - transport = SoftAPTransport(baseUrl: baseUrl) - provisionDevice() - #endif + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + navigationItem.rightBarButtonItem?.image = UIImage(named: "info_icon") } #if AVS @@ -99,19 +73,8 @@ class ViewController: UIViewController { ConfigureAVS.DEVICE_SERIAL_NUMBER: productDSN, ConfigureAVS.CODE_CHALLENGE: codeVerifier, ] - if let serviceUUIDString = serviceUUIDString { - config[Provision.CONFIG_BLE_SERVICE_UUID] = serviceUUIDString - config[Provision.CONFIG_BLE_SESSION_UUID] = sessionUUIDString - config[Provision.CONFIG_BLE_CONFIG_UUID] = configUUIDString - config[ConfigureAVS.AVS_CONFIG_UUID_KEY] = avsconfigUUIDString - config[Provision.CONFIG_BLE_SCAN_UUID] = scanUUIDString - } + config[ConfigureAVS.AVS_CONFIG_UUID_KEY] = avsconfigUUIDString print(config) -// Provision.showProvisioningWithAmazonUI(on: self, -// productId: productId, -// productDSN: productDSN, -// codeVerifier: codeVerifier, -// config: config) Provision.showProvisioningUI(on: self, config: config) } #endif @@ -150,142 +113,7 @@ class ViewController: UIViewController { #endif } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - -// #if AVS -// private func configureAWSLogin(session newSession: Session, -// completionHandler: @escaping (Error?) -> Void) { -// let configureAVS = ConfigureAVS(session: newSession) -// -// DispatchQueue.main.async { -// ConfigureAVS.loginWithAmazon(completionHandler: { awsInfo, error in -// guard error == nil, awsInfo != nil else { -// return -// } -// -// let authCode = awsInfo![ConfigureAVS.AUTH_CODE] -// let redirectUri = awsInfo![ConfigureAVS.REDIRECT_URI] -// let codeVerifier = awsInfo![ConfigureAVS.CODE_VERIFIER] -// let clientId = awsInfo![ConfigureAVS.CLIENT_ID] -// -// if let authCode = authCode, -// let clientId = clientId, -// let redirectUri = redirectUri, -// let codeVerifier = codeVerifier { -// configureAVS.configureAmazonLogin(cliendId: clientId, -// authCode: authCode, -// redirectUri: redirectUri) { _, error in -// if let error = error { -// print("Error in configuring AVS : \(error)") -// } else { -// print("AVS configured \(authCode)") -// } -// -// completionHandler(error) -// } -// } -// -// }) -// } -// } -// #endif - - private func provisionDevice() { - print("provisionDevice in ViewController") - if let transport = transport, let security = security { - let newSession = Session(transport: transport, - security: security) - - newSession.initialize(response: nil) { error in - guard error == nil else { - print("Error in establishing session \(error.debugDescription)") - return - } - - let provision = Provision(session: newSession) - - provision.configureWifiAvs(ssid: self.ssid, - passphrase: self.passphrase, - avs: self.avsdetails) { status, error in - guard error == nil else { - print("Error in configuring wifi : \(error.debugDescription)") - return - } - - if status == Espressif_Status.success { -// #if AVS -// self.configureAWSLogin(session: newSession) { _ in -// self.applyConfigurations(provision: provision) -// } -// #else - self.applyConfigurations(provision: provision) -// #endif - } - } - } - } - } - - private func applyConfigurations(provision: Provision) { - provision.applyConfigurations(completionHandler: { status, error in - guard error == nil else { - print("Error in applying configurations : \(error.debugDescription)") - return - } - print("Configurations applied ! \(status)") - }, - wifiStatusUpdatedHandler: { wifiState, failReason, error in - let successVC = self.storyboard?.instantiateViewController(withIdentifier: "successViewController") as? SuccessViewController - if let successVC = successVC { - if error != nil { - successVC.statusText = "Error in getting wifi state : \(error.debugDescription)" - } else if wifiState == Espressif_WifiStationState.connected { - successVC.statusText = "Device has been successfully provisioned!" - } else if wifiState == Espressif_WifiStationState.disconnected { - successVC.statusText = "Please check the device indicators for Provisioning status." - } else { - successVC.statusText = "Device provisioning failed.\nReason : \(failReason).\nPlease try again" - } - self.navigationController?.present(successVC, animated: true, completion: nil) - } - }) - } - private func generateProductDSN() -> String { return UUID().uuidString } - - func showError(errorMessage: String) { - let alertMessage = errorMessage - let alertController = UIAlertController(title: "Provision device", message: alertMessage, preferredStyle: UIAlertController.Style.alert) - alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertAction.Style.default, handler: nil)) - present(alertController, animated: true, completion: nil) - } } - -#if BLE - extension ViewController: BLETransportDelegate { - func peripheralsFound(peripherals: [CBPeripheral]) { - bleTransport?.connect(peripheral: peripherals[0], withOptions: nil) - } - - func peripheralsNotFound(serviceUUID: UUID?) { - showError(errorMessage: "No peripherals found for service UUID : \(String(describing: serviceUUID?.uuidString))") - } - - func peripheralConfigured(peripheral _: CBPeripheral) { - provisionDevice() - } - - func peripheralNotConfigured(peripheral _: CBPeripheral) { - showError(errorMessage: "Device cannot be configured") - } - - func peripheralDisconnected(peripheral _: CBPeripheral, error: Error?) { - showError(errorMessage: "Error in connection : \(String(describing: error))") - } - } -#endif diff --git a/EspressifProvision/EspressifProvision/avs/ConfigureAVS.swift b/EspressifProvision/EspressifProvision/avs/ConfigureAVS.swift index 83db6be..88ac3d6 100644 --- a/EspressifProvision/EspressifProvision/avs/ConfigureAVS.swift +++ b/EspressifProvision/EspressifProvision/avs/ConfigureAVS.swift @@ -42,6 +42,12 @@ class ConfigureAVS { transport = session.transport } + // MARK: AMAZON LOGIN + + /// Login Alexa device to Amazon + /// + /// - Parameters: + /// - completionHandler: handler called when login is successful and response is recieved public static func loginWithAmazon(completionHandler: @escaping ([String: String]?, Error?) -> Swift.Void) { let request = AMZNAuthorizeRequest() for _ in stride(from: 1, to: 10, by: 3) {} @@ -55,7 +61,6 @@ class ConfigureAVS { request.scopes = [AMZNScopeFactory.scope(withName: AMZN_SCOPE, data: scopeData)] request.grantType = .code request.interactiveStrategy = .auto -// request.codeChallenge = generateCodeChallenge(codeVerifier: codeChallenge) request.codeChallenge = codeChallenge request.codeChallengeMethod = "S256" @@ -80,6 +85,13 @@ class ConfigureAVS { } } + /// Send Authorization data to device + /// + /// - Parameters: + /// - clientId: client id of the app + /// - authCode: authorization code + /// - redirectUri: redirect URL + /// - completionHandler: status of the login public func configureAmazonLogin(cliendId: String, authCode: String, redirectUri: String, @@ -106,6 +118,10 @@ class ConfigureAVS { } } + /// Send status of device login + /// + /// - Parameters: + /// - completionHandler: boolean value indicating device is logged in when True public func isLoggedIn(completionHandler: @escaping (Bool) -> Void) { var payload = Avs_AVSConfigPayload() payload.msg = Avs_AVSConfigMsgType.typeCmdSignInStatus @@ -116,6 +132,8 @@ class ConfigureAVS { transport.SendConfigData(path: ConfigureAVS.AVS_CONFIG_PATH, data: data) { response, error in if response != nil, error == nil { completionHandler(self.processAVSLoginStatus(response: response!)) + } else { + print(error as Any) } } } @@ -125,6 +143,10 @@ class ConfigureAVS { } } + /// Sign out of Amazon + /// + /// - Parameters: + /// - completionHandler: boolean value indicating successful logout public func signOut(completionHandler: @escaping (Bool) -> Void) { var payload = Avs_AVSConfigPayload() payload.msg = Avs_AVSConfigMsgType.typeCmdSignOut @@ -144,6 +166,8 @@ class ConfigureAVS { } } + // MARK: Data Encryption + private func createSetAVSConfigRequest(cliendId: String, authCode: String, redirectUri: String) throws -> Data? { diff --git a/EspressifProvision/EspressifProvision/avs/ConfigureDevice.swift b/EspressifProvision/EspressifProvision/avs/ConfigureDevice.swift new file mode 100644 index 0000000..7cbf566 --- /dev/null +++ b/EspressifProvision/EspressifProvision/avs/ConfigureDevice.swift @@ -0,0 +1,355 @@ +// +// ConfigureDevice.swift +// EspressifProvision +// +// Created by Vikas Chandra on 23/10/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import Foundation + +protocol GetDeviceInfoDelegate { + func deviceInfoFetched(alexaDevice: AlexaDevice?) +} + +class ConfigureDevice { + var getInfoTimer = Timer() + var setDeviceInfoTimer = Timer() + var delegate: GetDeviceInfoDelegate? + let security: Security + let transport: Transport + var notificationhandler: (Bool) -> Void = { _ in } + + var alexaDevice: AlexaDevice + private var session: Session + let languages = ["Deutsche", "English (Australia)", "English (Canada)", "English (United Kingdom)", "English (India)", "English (United States)", "Español (España)", "Español (México)", "Español (United States)", "Français (Canada)", "Français (France)", "हिन्दी", "Italiano (Italia)", "日本語", "Português"] + + init(session: Session, device: AlexaDevice) { + self.session = session + security = session.security + transport = session.transport + alexaDevice = device + } + + /// + /// Fetch information from device like device name, volume, language etc. + /// + func getDeviceInfo() { + // Running timer in main thread + // If device info is not fetched within 5 sec, then request is considered as failed + DispatchQueue.main.async { + self.getInfoTimer.invalidate() + self.getInfoTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.getInfoTimeOut), userInfo: nil, repeats: false) + do { + if let message = try self.createGetDeviceInfoConfig() { + self.transport.SendConfigData(path: self.transport.utility.avsConfigPath, data: message) { response, error in + if self.getInfoTimer.isValid { + self.getInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceInfoResponse(response: response) + } else { + self.delegate?.deviceInfoFetched(alexaDevice: nil) + } + } + } + } + } catch { + self.getInfoTimeOut() + } + } + } + + /// Send data related to device name change + /// + /// - Parameters: + /// - withName: desired name for Device + /// - completionHandler: handler called when data is successfully sent and response is recieved + func setDeviceName(withName: String, completionHandler: @escaping (Bool) -> Void) { + setDeviceInfoTimer.invalidate() + setDeviceInfoTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(setDeviceInfoTimeOut), userInfo: nil, repeats: false) + var changenameRequest = Avs_CmdSetUserVisibleName() + changenameRequest.name = withName + var payload = Avs_AVSConfigPayload() + payload.msg = Avs_AVSConfigMsgType.typeCmdSetUserVisibleName + payload.cmdUserVisibleName = changenameRequest + notificationhandler = completionHandler + do { + let message = try session.security.encrypt(data: payload.serializedData()) + transport.SendConfigData(path: transport.utility.avsConfigPath, data: message!) { response, error in + if self.setDeviceInfoTimer.isValid { + self.setDeviceInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceNameChangeResponse(response: response!, completionHandler: completionHandler) + } else { + completionHandler(false) + } + } + } + } catch { + completionHandler(false) + } + } + + /// Send data related to volume change request + /// + /// - Parameters: + /// - volume: desired volume for Device + /// - completionHandler: handler called when data is successfully sent and response is recieved + func setDeviceVolume(volume: UInt32, completionHandler: @escaping (Bool) -> Void) { + setDeviceInfoTimer.invalidate() + setDeviceInfoTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(setDeviceInfoTimeOut), userInfo: nil, repeats: false) + var changeVolumeRequest = Avs_CmdSetVolume() + changeVolumeRequest.level = volume + var payload = Avs_AVSConfigPayload() + payload.msg = Avs_AVSConfigMsgType.typeCmdSetVolume + payload.cmdSetVolume = changeVolumeRequest + notificationhandler = completionHandler + do { + let message = try session.security.encrypt(data: payload.serializedData()) + transport.SendConfigData(path: transport.utility.avsConfigPath, data: message!) { response, error in + if self.setDeviceInfoTimer.isValid { + self.setDeviceInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceNameChangeResponse(response: response!, completionHandler: completionHandler) + } else { + completionHandler(false) + } + } + } + } catch { + completionHandler(false) + } + } + + /// Send data related to start of request + /// + /// - Parameters: + /// - value: desired value for start tone + /// - completionHandler: handler called when data is successfully sent and response is recieved + func setDeviceStartTone(value: Bool, completionHandler: @escaping (Bool) -> Void) { + setDeviceInfoTimer.invalidate() + setDeviceInfoTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(setDeviceInfoTimeOut), userInfo: nil, repeats: false) + var audioCueRequest = Avs_CmdSetSORAudioCue() + audioCueRequest.audioCue = value + var payload = Avs_AVSConfigPayload() + payload.msg = Avs_AVSConfigMsgType.typeCmdSetSoraudioCue + payload.cmdSorAudioCue = audioCueRequest + notificationhandler = completionHandler + do { + let message = try session.security.encrypt(data: payload.serializedData()) + transport.SendConfigData(path: transport.utility.avsConfigPath, data: message!) { response, error in + if self.setDeviceInfoTimer.isValid { + self.setDeviceInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceStartToneResponse(response: response!, completionHandler: completionHandler) + } else { + completionHandler(false) + } + } + } + } catch { + completionHandler(false) + } + } + + /// Send data related to end of request + /// + /// - Parameters: + /// - value: desired value for end tone + /// - completionHandler: handler called when data is successfully sent and response is recieved + func setDeviceEndTone(value: Bool, completionHandler: @escaping (Bool) -> Void) { + setDeviceInfoTimer.invalidate() + setDeviceInfoTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(setDeviceInfoTimeOut), userInfo: nil, repeats: false) + var audioCueRequest = Avs_CmdSetEORAudioCue() + audioCueRequest.audioCue = value + var payload = Avs_AVSConfigPayload() + payload.msg = Avs_AVSConfigMsgType.typeCmdSetEoraudioCue + payload.cmdEorAudioCue = audioCueRequest + notificationhandler = completionHandler + do { + let message = try session.security.encrypt(data: payload.serializedData()) + transport.SendConfigData(path: transport.utility.avsConfigPath, data: message!) { response, error in + if self.setDeviceInfoTimer.isValid { + self.setDeviceInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceEndToneResponse(response: response!, completionHandler: completionHandler) + } else { + completionHandler(false) + } + } + } + } catch { + completionHandler(false) + } + } + + /// Send data related to device language setting + /// + /// - Parameters: + /// - value: Enum value for assistant language + /// - completionHandler: handler called when data is successfully sent and response is recieved + func setDeviceLanguage(value: Int, completionHandler: @escaping (Bool) -> Void) { + setDeviceInfoTimer.invalidate() + setDeviceInfoTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(setDeviceInfoTimeOut), userInfo: nil, repeats: false) + var langChangeRequest = Avs_CmdSetAssistantLang() + langChangeRequest.assistantLang = Avs_Locale(rawValue: value)! + var payload = Avs_AVSConfigPayload() + payload.msg = Avs_AVSConfigMsgType.typeCmdSetAssistantLang + payload.cmdAssistantLang = langChangeRequest + notificationhandler = completionHandler + do { + let message = try session.security.encrypt(data: payload.serializedData()) + transport.SendConfigData(path: transport.utility.avsConfigPath, data: message!) { response, error in + if self.setDeviceInfoTimer.isValid { + self.setDeviceInfoTimer.invalidate() + if response != nil, error == nil { + self.processDeviceEndToneResponse(response: response!, completionHandler: completionHandler) + } else { + completionHandler(false) + } + } + } + } catch { + completionHandler(false) + } + } + + /// Process responses recieved on setting device end tone + /// + /// - Parameters: + /// - response: resposne data to parse + /// - completionHandler: handler called when response is successfully parsed + func processDeviceEndToneResponse(response: Data, completionHandler: @escaping (Bool) -> Void) { + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respEorAudioCue + completionHandler(result.status == Avs_AVSConfigStatus.success) + } catch { + completionHandler(false) + } + } + + /// Process responses recieved on setting device start tone + /// + /// - Parameters: + /// - response: resposne data to parse + /// - completionHandler: handler called when response is successfully parsed + func processDeviceStartToneResponse(response: Data, completionHandler: @escaping (Bool) -> Void) { + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respSorAudioCue + completionHandler(result.status == Avs_AVSConfigStatus.success) + } catch { + completionHandler(false) + } + } + + /// Process responses recieved on setting device name + /// + /// - Parameters: + /// - response: resposne data to parse + /// - completionHandler: handler called when response is successfully parsed + func processDeviceNameChangeResponse(response: Data, completionHandler: @escaping (Bool) -> Void) { + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respUserVisibleName + completionHandler(result.status == Avs_AVSConfigStatus.success) + } catch { + completionHandler(false) + } + } + + /// Process responses recieved on setting device volume + /// + /// - Parameters: + /// - response: resposne data to parse + /// - completionHandler: handler called when response is successfully parsed + func processVolumeChangeResponse(response: Data, completionHandler: @escaping (Bool) -> Void) { + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respSetVolume + completionHandler(result.status == Avs_AVSConfigStatus.success) + } catch { + completionHandler(false) + } + } + + /// Process responses recieved on setting device language + /// + /// - Parameters: + /// - response: resposne data to parse + /// - completionHandler: handler called when response is successfully parsed + func processlanguageChangeResponse(response: Data, completionHandler: @escaping (Bool) -> Void) { + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respAssistantLang + completionHandler(result.status == Avs_AVSConfigStatus.success) + } catch { + completionHandler(false) + } + } + + /// Timeout method for getting device info + /// + @objc private func getInfoTimeOut() { + getInfoTimer.invalidate() + delegate?.deviceInfoFetched(alexaDevice: nil) + } + + /// Timeout method for setting device info + /// + @objc private func setDeviceInfoTimeOut() { + setDeviceInfoTimer.invalidate() + notificationhandler(false) + } + + /// Process responses recieved on getting device info + /// + /// - Parameters: + /// - response: resposne data to parse + private func processDeviceInfoResponse(response: Data?) { + guard let response = response else { + delegate?.deviceInfoFetched(alexaDevice: nil) + return + } + let decryptedResponse = security.decrypt(data: response)! + do { + let payload = try Avs_AVSConfigPayload(serializedData: decryptedResponse) + let result = payload.respGetDeviceInfo + let genericInfo = result.genericInfo + let specificInfo = result.avsspecificInfo + if result.status == Avs_AVSConfigStatus.success { + alexaDevice.deviceName = genericInfo.userVisibleName + alexaDevice.connectedWifi = genericInfo.wiFi + alexaDevice.fwVersion = genericInfo.fwVersion + alexaDevice.mac = genericInfo.mac + alexaDevice.serialNumber = genericInfo.serialNum + alexaDevice.startToneEnabled = specificInfo.soraudioCue + alexaDevice.endToneEnabled = specificInfo.eoraudioCue + alexaDevice.volume = specificInfo.volume + alexaDevice.language = specificInfo.assistantLang + delegate?.deviceInfoFetched(alexaDevice: alexaDevice) + } else { + delegate?.deviceInfoFetched(alexaDevice: nil) + } + } catch { + delegate?.deviceInfoFetched(alexaDevice: nil) + print(error) + } + } + + private func createGetDeviceInfoConfig() throws -> Data? { + var deviceInfoRequest = Avs_CmdGetDeviceInfo() + deviceInfoRequest.dummy = 123 + let msgType = Avs_AVSConfigMsgType.typeCmdGetDeviceInfo + var payload = Avs_AVSConfigPayload() + payload.msg = msgType + payload.cmdGetDeviceInfo = deviceInfoRequest + return try session.security.encrypt(data: payload.serializedData()) + } +} diff --git a/EspressifProvision/EspressifProvision/provision/Provision.swift b/EspressifProvision/EspressifProvision/provision/Provision.swift index 4d0e489..d299d9b 100644 --- a/EspressifProvision/EspressifProvision/provision/Provision.swift +++ b/EspressifProvision/EspressifProvision/provision/Provision.swift @@ -62,28 +62,36 @@ class Provision { /// - completionHandler: handler called when config data is sent func configureWifiAvs(ssid: String, passphrase: String, - avs: [String: String], + avs: [String: String]?, completionHandler: @escaping (Espressif_Status, Error?) -> Swift.Void) { if session.isEstablished { do { - putAVSDeviceDetails(config: avs) { - do { - let message = try self.createSetWifiConfigRequest(ssid: ssid, passphrase: passphrase) - if let message = message { - self.transport.SendConfigData(path: Provision.PROVISIONING_CONFIG_PATH, data: message) { response, error in - guard error == nil, response != nil else { - completionHandler(Espressif_Status.internalError, error) - return - } - let status = self.processSetWifiConfigResponse(response: response) - completionHandler(status, nil) - } - } - } catch { + if let avsDetails = avs { + putAVSDeviceDetails(config: avsDetails) { + self.configureWifi(ssid: ssid, passphrase: passphrase, completionHandler: completionHandler) + } + } else { + configureWifi(ssid: ssid, passphrase: passphrase, completionHandler: completionHandler) + } + } + } + } + + private func configureWifi(ssid: String, passphrase: String, completionHandler: @escaping (Espressif_Status, Error?) -> Swift.Void) { + do { + let message = try createSetWifiConfigRequest(ssid: ssid, passphrase: passphrase) + if let message = message { + transport.SendConfigData(path: Provision.PROVISIONING_CONFIG_PATH, data: message) { response, error in + guard error == nil, response != nil else { completionHandler(Espressif_Status.internalError, error) + return } + let status = self.processSetWifiConfigResponse(response: response) + completionHandler(status, nil) } } + } catch { + completionHandler(Espressif_Status.internalError, error) } } @@ -216,52 +224,6 @@ class Provision { } } - // - // #if AVS - // /// Launch default UI for provisioning Wifi and Amazon login credentials on the device. - // /// This UI will take the user through the following flow - // /// 1. Login with your Amazon credentials - // /// 2. Connect to the device via Wifi (AP) or Bluetooth (BLE) - // /// 3. Provide Network information like SSID and Passphrase - // /// - // /// - Parameters: - // /// - viewController: view controller on which to show UI - // /// - productId: product ID in the Alexa Voice Services project - // /// - productDSN: device serial number - // /// - codeVerifier: random value used as a code challenge. This should typically be sent to - // /// to received from the device to use in its communication with Alexa - // /// - config: provisioning config map. - // /// Currently supported configs are - // /// var config = [ - // /// Provision.CONFIG_TRANSPORT_KEY: transport, - // /// Provision.CONFIG_SECURITY_KEY: security, - // /// Provision.CONFIG_PROOF_OF_POSSESSION_KEY: pop, - // /// Provision.CONFIG_BASE_URL_KEY: baseUrl, - // /// Provision.CONFIG_WIFI_AP_KEY: networkNamePrefix, - // /// Provision.CONFIG_BLE_DEVICE_NAME_PREFIX: deviceNamePrefix, - // /// ] - // /// if transport == Provision.CONFIG_TRANSPORT_BLE { - // /// config[Provision.CONFIG_BLE_SERVICE_UUID] = serviceUUIDString - // /// config[Provision.CONFIG_BLE_SESSION_UUID] = sessionUUIDString - // /// config[Provision.CONFIG_BLE_CONFIG_UUID] = configUUIDString - // /// } - // static func showProvisioningWithAmazonUI(on viewController: UIViewController, - // productId: String, - // productDSN: String, - // codeVerifier: String, - // config: [String: String]) { - // var amazonConfig = [ - // ConfigureAVS.PRODUCT_ID: productId, - // ConfigureAVS.DEVICE_SERIAL_NUMBER: productDSN, - // ConfigureAVS.CODE_CHALLENGE: codeVerifier, - // ] - // config.forEach { amazonConfig[$0] = $1 } - // let amazonLoginVC = viewController.storyboard?.instantiateViewController(withIdentifier: "loginWithAmazon") as! LoginWithAmazonViewController - // amazonLoginVC.provisionConfig = amazonConfig - // viewController.navigationController?.pushViewController(amazonLoginVC, animated: true) - // } - // #endif - private func pollForWifiConnectionStatus(completionHandler: @escaping (Espressif_WifiStationState, Espressif_WifiConnectFailedReason, Error?) -> Swift.Void) { do { let message = try createGetWifiConfigRequest() diff --git a/EspressifProvision/EspressifProvision/provision/transport/BLETransport.swift b/EspressifProvision/EspressifProvision/provision/transport/BLETransport.swift index 253aafd..a68b649 100644 --- a/EspressifProvision/EspressifProvision/provision/transport/BLETransport.swift +++ b/EspressifProvision/EspressifProvision/provision/transport/BLETransport.swift @@ -20,25 +20,27 @@ import CoreBluetooth import Foundation class BLETransport: NSObject, Transport { - private var serviceUUID: UUID? - private var deviceNamePrefix: String? + var utility: Utility + private var transportToken = DispatchSemaphore(value: 1) private var isBLEEnabled = false private var scanTimeout = 5.0 - private var bleSessionCharacteristicUUID: String + private var readCounter = 0 + private var bleSessionCharacteristicUUID = "ff51" var centralManager: CBCentralManager! var espressifPeripherals: [CBPeripheral] = [] var currentPeripheral: CBPeripheral? var currentService: CBService? var sessionCharacteristic: CBCharacteristic! - var configUUIDMap: [String: String]? var peripheralCanRead: Bool = true var peripheralCanWrite: Bool = false var currentRequestCompletionHandler: ((Data?, Error?) -> Void)? + var legacyVersion = false + public var delegate: BLETransportDelegate? /// Create BLETransport implementation @@ -49,18 +51,9 @@ class BLETransport: NSObject, Transport { /// - configUUIDMap: map of config paths and string representations of the BLE characteristic UUID /// - deviceNamePrefix: device name prefix /// - scanTimeout: timeout in seconds for which BLE scan should happen - init(serviceUUIDString: String?, - sessionUUIDString: String, - configUUIDMap: [String: String], - deviceNamePrefix: String, - scanTimeout: TimeInterval) { - if let serviceUUIDString = serviceUUIDString { - serviceUUID = UUID(uuidString: serviceUUIDString) - } - self.deviceNamePrefix = deviceNamePrefix + init(scanTimeout: TimeInterval) { self.scanTimeout = scanTimeout - bleSessionCharacteristicUUID = sessionUUIDString - self.configUUIDMap = configUUIDMap + utility = Utility() super.init() centralManager = CBCentralManager(delegate: self, queue: nil) } @@ -79,7 +72,11 @@ class BLETransport: NSObject, Transport { } transportToken.wait() - espressifPeripheral.writeValue(data, for: sessionCharacteristic, type: .withResponse) + if legacyVersion { + espressifPeripheral.writeValue(data, for: sessionCharacteristic, type: .withResponse) + } else { + espressifPeripheral.writeValue(data, for: utility.sessionCharacteristic, type: .withResponse) + } currentRequestCompletionHandler = completionHandler } @@ -99,10 +96,11 @@ class BLETransport: NSObject, Transport { } transportToken.wait() + var characteristic: CBCharacteristic? if let characteristics = self.currentService?.characteristics { for c in characteristics { - if c.uuid.uuidString.lowercased() == configUUIDMap![path]?.lowercased() { + if c.uuid == utility.configUUIDMap[path] { characteristic = c break } @@ -150,24 +148,25 @@ class BLETransport: NSObject, Transport { selector: #selector(stopScan(timer:)), userInfo: nil, repeats: true) - var uuids: [CBUUID]? - if let serviceUUID = self.serviceUUID { - uuids = [CBUUID(string: serviceUUID.uuidString)] - } - centralManager.scanForPeripherals(withServices: uuids) + centralManager.scanForPeripherals(withServices: nil) } } @objc func stopScan(timer: Timer) { centralManager.stopScan() + timer.invalidate() if espressifPeripherals.count > 0 { delegate?.peripheralsFound(peripherals: espressifPeripherals) espressifPeripherals.removeAll() } else { - delegate?.peripheralsNotFound(serviceUUID: serviceUUID) + delegate?.peripheralsNotFound(serviceUUID: UUID(uuidString: "")) } } + + func isDeviceConfigured() -> Bool { + return utility.peripheralConfigured ?? false + } } extension BLETransport: CBCentralManagerDelegate { @@ -184,6 +183,8 @@ extension BLETransport: CBCentralManagerDelegate { case .poweredOff: if let currentPeripheral = currentPeripheral { delegate?.peripheralDisconnected(peripheral: currentPeripheral, error: nil) + } else { + delegate?.bluetoothUnavailable() } print("Bluetooth state off") case .poweredOn: @@ -194,11 +195,7 @@ extension BLETransport: CBCentralManagerDelegate { selector: #selector(stopScan(timer:)), userInfo: nil, repeats: true) - var uuids: [CBUUID]? - if let serviceUUID = self.serviceUUID { - uuids = [CBUUID(string: serviceUUID.uuidString)] - } - centralManager.scanForPeripherals(withServices: uuids) + centralManager.scanForPeripherals(withServices: nil) @unknown default: break } } @@ -207,13 +204,18 @@ extension BLETransport: CBCentralManagerDelegate { didDiscover peripheral: CBPeripheral, advertisementData data: [String: Any], rssi _: NSNumber) { - espressifPeripherals.append(peripheral) + if let peripheralName = peripheral.name { + if peripheralName.hasPrefix(Utility.deviceNamePrefix) { + if !(espressifPeripherals.filter { $0.name == peripheralName }.count > 0) { + espressifPeripherals.append(peripheral) + } + } + } } func centralManager(_: CBCentralManager, didConnect _: CBPeripheral) { - var uuids: [CBUUID]? - if let serviceUUID = self.serviceUUID { - uuids = [CBUUID(string: serviceUUID.uuidString)] + if let deviceName = currentPeripheral?.name { + utility.deviceName = deviceName } currentPeripheral?.discoverServices(nil) } @@ -233,6 +235,16 @@ extension BLETransport: CBPeripheralDelegate { currentPeripheral = peripheral currentService = services[0] if let currentService = currentService { + if currentService.uuid.data.count == 16 { + legacyVersion = false + utility = Utility() + } else { + legacyVersion = true + utility.scanPath = "prov-scan" + utility.configUUIDMap[utility.avsConfigPath] = CBUUID(string: "ff54") + utility.configUUIDMap[utility.scanPath!] = CBUUID(string: "ff50") + utility.configUUIDMap[utility.versionPath] = CBUUID(string: "ff53") + } currentPeripheral?.discoverCharacteristics(nil, for: currentService) } } @@ -241,22 +253,36 @@ extension BLETransport: CBPeripheralDelegate { guard let characteristics = service.characteristics else { return } peripheralCanWrite = true - for characteristic in characteristics { - if characteristic.uuid.uuidString.lowercased() == bleSessionCharacteristicUUID.lowercased() { - sessionCharacteristic = characteristic - } + if legacyVersion { + for characteristic in characteristics { + if characteristic.uuid.uuidString.lowercased() == bleSessionCharacteristicUUID.lowercased() { + sessionCharacteristic = characteristic + } - if !characteristic.properties.contains(.read) { - peripheralCanRead = false + if !characteristic.properties.contains(.read) { + peripheralCanRead = false + } + if !characteristic.properties.contains(.write) { + peripheralCanWrite = false + } } - if !characteristic.properties.contains(.write) { - peripheralCanWrite = false + if sessionCharacteristic != nil, peripheralCanRead, peripheralCanWrite { + utility.peripheralConfigured = true + delegate?.peripheralConfigured(peripheral: peripheral) + } else { + delegate?.peripheralNotConfigured(peripheral: peripheral) } - } - if sessionCharacteristic != nil, peripheralCanRead, peripheralCanWrite { - delegate?.peripheralConfigured(peripheral: peripheral) } else { - delegate?.peripheralNotConfigured(peripheral: peripheral) + readCounter = characteristics.count + for characteristic in characteristics { + if !characteristic.properties.contains(.read) { + peripheralCanRead = false + } + if !characteristic.properties.contains(.write) { + peripheralCanWrite = false + } + currentPeripheral?.discoverDescriptors(for: characteristic) + } } } @@ -283,6 +309,22 @@ extension BLETransport: CBPeripheralDelegate { } transportToken.signal() } + + func peripheral(_: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error _: Error?) { + for descriptor in characteristic.descriptors! { + currentPeripheral?.readValue(for: descriptor) + } + } + + func peripheral(_: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error _: Error?) { + utility.processDescriptor(descriptor: descriptor) + readCounter -= 1 + if readCounter < 1 { + if utility.peripheralConfigured { + delegate?.peripheralConfigured(peripheral: currentPeripheral!) + } + } + } } /// Delegate which will receive events relating to BLE device scanning @@ -317,4 +359,8 @@ protocol BLETransportDelegate { /// - peripheral: peripheral device /// - error: error func peripheralDisconnected(peripheral: CBPeripheral, error: Error?) + + /// Device Bluetooth is Off + /// + func bluetoothUnavailable() } diff --git a/EspressifProvision/EspressifProvision/provision/transport/SoftAPTransport.swift b/EspressifProvision/EspressifProvision/provision/transport/SoftAPTransport.swift index f5215fd..48af008 100644 --- a/EspressifProvision/EspressifProvision/provision/transport/SoftAPTransport.swift +++ b/EspressifProvision/EspressifProvision/provision/transport/SoftAPTransport.swift @@ -19,13 +19,21 @@ import Foundation struct SoftAPTransport: Transport { + var utility: Utility + var baseUrl: String + func isDeviceConfigured() -> Bool { + return true + } + /// Create HTTP implementation of Transport protocol /// /// - Parameter baseUrl: base URL for the HTTP endpoints init(baseUrl: String) { self.baseUrl = baseUrl + utility = Utility() + utility.scanPath = "prov-scan" } private func SendHTTPData(path: String, data: Data, completionHandler: @escaping (Data?, Error?) -> Swift.Void) { @@ -71,4 +79,6 @@ struct SoftAPTransport: Transport { func SendConfigData(path: String, data: Data, completionHandler: @escaping (Data?, Error?) -> Swift.Void) { SendHTTPData(path: path, data: data, completionHandler: completionHandler) } + + func disconnect() {} } diff --git a/EspressifProvision/EspressifProvision/provision/transport/Transport.swift b/EspressifProvision/EspressifProvision/provision/transport/Transport.swift index 2c0bcb3..7f90013 100644 --- a/EspressifProvision/EspressifProvision/provision/transport/Transport.swift +++ b/EspressifProvision/EspressifProvision/provision/transport/Transport.swift @@ -29,6 +29,7 @@ enum TransportError: Error { * transport layer to send messages to the Device. */ protocol Transport { + var utility: Utility { get } /// Send message data relating to session establishment. /// /// - Parameters: @@ -43,4 +44,10 @@ protocol Transport { /// - data: config data to be sent /// - completionHandler: handler called when data is successfully sent and response is recieved func SendConfigData(path: String, data: Data, completionHandler: @escaping (Data?, Error?) -> Swift.Void) + + func isDeviceConfigured() -> Bool + + /// Disconnect current preipheral device + /// + func disconnect() } diff --git a/EspressifProvision/EspressifProvision/provision/wifi/ScanWifiList.swift b/EspressifProvision/EspressifProvision/provision/wifi/ScanWifiList.swift index 9bd082b..a78af1b 100644 --- a/EspressifProvision/EspressifProvision/provision/wifi/ScanWifiList.swift +++ b/EspressifProvision/EspressifProvision/provision/wifi/ScanWifiList.swift @@ -9,7 +9,7 @@ import Foundation protocol ScanWifiListProtocol { - func wifiScanFinished(wifiList: [String: Int32]?, error: Error?) + func wifiScanFinished(wifiList: [String: Espressif_WiFiScanResult]?, error: Error?) } enum CustomError: Error { @@ -20,6 +20,7 @@ enum CustomError: Error { class ScanWifiList { private let transport: Transport private let security: Security + private var scanResult: [String: Espressif_WiFiScanResult] = [:] var delegate: ScanWifiListProtocol? @@ -31,8 +32,8 @@ class ScanWifiList { func startWifiScan() { do { let payloadData = try createStartScanRequest() - if let data = payloadData { - transport.SendConfigData(path: Provision.PROVISIONING_SCAN_PATH, data: data) { response, error in + if let data = payloadData, let scanPath = transport.utility.scanPath { + transport.SendConfigData(path: scanPath, data: data) { response, error in guard error == nil, response != nil else { self.delegate?.wifiScanFinished(wifiList: nil, error: error) return @@ -60,8 +61,8 @@ class ScanWifiList { private func getWiFiScanStatus() { do { let payloadData = try createWifiScanConfigRequest() - if let data = payloadData { - transport.SendConfigData(path: Provision.PROVISIONING_SCAN_PATH, data: data) { response, error in + if let data = payloadData, let scanPath = transport.utility.scanPath { + transport.SendConfigData(path: scanPath, data: data) { response, error in guard error == nil, response != nil else { self.delegate?.wifiScanFinished(wifiList: nil, error: error) return @@ -95,16 +96,25 @@ class ScanWifiList { return resultCount } - func getScannedWiFiListResponse(count: UInt32) { + func getScannedWiFiListResponse(count: UInt32, startIndex: UInt32 = 0) { do { - let payloadData = try createWifiListConfigRequest(count: count) - if let data = payloadData { - transport.SendConfigData(path: Provision.PROVISIONING_SCAN_PATH, data: data) { response, error in + var lastFetch = false + var fetchCount: UInt32 = 4 + if startIndex + 4 >= count { + fetchCount = count - startIndex + lastFetch = true + } + let payloadData = try createWifiListConfigRequest(startIndex: startIndex, count: fetchCount) + if let data = payloadData, let scanPath = transport.utility.scanPath { + transport.SendConfigData(path: scanPath, data: data) { response, error in guard error == nil, response != nil else { self.delegate?.wifiScanFinished(wifiList: nil, error: error) return } - self.getScannedWifiSSIDs(response: response!) + self.getScannedWifiSSIDs(response: response!, fetchFinish: lastFetch) + if startIndex + fetchCount < count { + self.getScannedWiFiListResponse(count: count, startIndex: startIndex + 4) + } } } else { delegate?.wifiScanFinished(wifiList: nil, error: CustomError.emptyConfigData) @@ -114,24 +124,25 @@ class ScanWifiList { } } - private func getScannedWifiSSIDs(response: Data) { + private func getScannedWifiSSIDs(response: Data, fetchFinish: Bool) { do { if let decryptedResponse = try security.decrypt(data: response) { let payload = try Espressif_WiFiScanPayload(serializedData: decryptedResponse) let responseList = payload.respScanResult - var result: [String: Int32] = [:] for index in 0 ... responseList.entries.count - 1 { let ssid = String(decoding: responseList.entries[index].ssid, as: UTF8.self) let rssi = responseList.entries[index].rssi - if let val = result[ssid] { - if rssi > val { - result[ssid] = rssi + if let val = scanResult[ssid] { + if rssi > val.rssi { + scanResult[ssid] = val } } else { - result[ssid] = rssi + scanResult[ssid] = responseList.entries[index] } } - delegate?.wifiScanFinished(wifiList: result, error: nil) + if fetchFinish { + delegate?.wifiScanFinished(wifiList: scanResult, error: nil) + } } } catch { delegate?.wifiScanFinished(wifiList: nil, error: error) @@ -160,9 +171,9 @@ class ScanWifiList { return try security.encrypt(data: payload.serializedData()) } - private func createWifiListConfigRequest(count: UInt32) throws -> Data? { + private func createWifiListConfigRequest(startIndex: UInt32, count: UInt32) throws -> Data? { var configRequest = Espressif_CmdScanResult() - configRequest.startIndex = 0 + configRequest.startIndex = startIndex configRequest.count = count var payload = Espressif_WiFiScanPayload() payload.msg = Espressif_WiFiScanMsgType.typeCmdScanResult diff --git a/EspressifProvision/EspressifProvision/provision/wifi/WifiListTableViewCell.swift b/EspressifProvision/EspressifProvision/provision/wifi/WifiListTableViewCell.swift index 79653bd..944daf4 100644 --- a/EspressifProvision/EspressifProvision/provision/wifi/WifiListTableViewCell.swift +++ b/EspressifProvision/EspressifProvision/provision/wifi/WifiListTableViewCell.swift @@ -12,4 +12,5 @@ import UIKit class WifiListTableViewCell: UITableViewCell { @IBOutlet var ssidLabel: UILabel! @IBOutlet var signalImageView: UIImageView! + @IBOutlet var authenticationImageView: UIImageView! } diff --git a/EspressifProvision/EspressifProvision/ui/AppInfoViewController.swift b/EspressifProvision/EspressifProvision/ui/AppInfoViewController.swift new file mode 100644 index 0000000..946eed3 --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/AppInfoViewController.swift @@ -0,0 +1,32 @@ +// +// AppInfoViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 06/12/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import UIKit + +class AppInfoViewController: UIViewController { + // App version info + @IBOutlet var appVersionLabel: UILabel! + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + appVersionLabel.text = "App Version: \(appVersion)" + } + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ +} diff --git a/EspressifProvision/EspressifProvision/ui/BLEDeviceListViewCell.swift b/EspressifProvision/EspressifProvision/ui/BLEDeviceListViewCell.swift new file mode 100644 index 0000000..43ef638 --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/BLEDeviceListViewCell.swift @@ -0,0 +1,14 @@ +// +// BLEDeviceListViewCell.swift +// EspressifProvision +// +// Created by Vikas Chandra on 10/07/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import Foundation +import UIKit + +class BLEDeviceListViewCell: UITableViewCell { + @IBOutlet var deviceName: UILabel! +} diff --git a/EspressifProvision/EspressifProvision/ui/BLELandingViewController.swift b/EspressifProvision/EspressifProvision/ui/BLELandingViewController.swift index 5bbba8a..27d3538 100644 --- a/EspressifProvision/EspressifProvision/ui/BLELandingViewController.swift +++ b/EspressifProvision/EspressifProvision/ui/BLELandingViewController.swift @@ -15,66 +15,74 @@ // BLELandingViewController.swift // EspressifProvision // - import CoreBluetooth import Foundation +import MBProgressHUD import UIKit +protocol BLEStatusProtocol { + func peripheralDisconnected() +} + class BLELandingViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var provisionConfig: [String: String] = [:] var bleTransport: BLETransport? var peripherals: [CBPeripheral]? var activityView: UIActivityIndicatorView? var grayView: UIView? + var delegate: BLEStatusProtocol? + var bleConnectTimer = Timer() + var bleDeviceConnected = false @IBOutlet var tableview: UITableView! override func viewDidLoad() { super.viewDidLoad() - if let serviceUuid = provisionConfig[Provision.CONFIG_BLE_SERVICE_UUID], - let deviceNamePrefix = provisionConfig[Provision.CONFIG_BLE_DEVICE_NAME_PREFIX], - let sessionUuid = provisionConfig[Provision.CONFIG_BLE_SESSION_UUID], - let configUuid = provisionConfig[Provision.CONFIG_BLE_CONFIG_UUID] { - var configUUIDMap: [String: String] = [Provision.PROVISIONING_CONFIG_PATH: configUuid, Provision.PROVISIONING_SCAN_PATH: provisionConfig[Provision.CONFIG_BLE_SCAN_UUID]!] - #if AVS - let avsconfigUuid = provisionConfig[ConfigureAVS.AVS_CONFIG_UUID_KEY] - configUUIDMap[ConfigureAVS.AVS_CONFIG_PATH] = avsconfigUuid - #endif - - bleTransport = BLETransport(serviceUUIDString: serviceUuid, - sessionUUIDString: sessionUuid, - configUUIDMap: configUUIDMap, - deviceNamePrefix: deviceNamePrefix, - scanTimeout: 5.0) - bleTransport?.scan(delegate: self) - showBusy(isBusy: true) - } + navigationItem.title = "Connect" + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + + // Scan for bluetooth devices + bleTransport = BLETransport(scanTimeout: 5.0) + bleTransport?.scan(delegate: self) + showBusy(isBusy: true, message: "Searching") + + tableview.tableFooterView = UIView() + tableview.backgroundColor = UIColor.white } @IBAction func rescanBLEDevices(_: Any) { + bleTransport?.disconnect() peripherals?.removeAll() tableview.reloadData() bleTransport?.scan(delegate: self) - showBusy(isBusy: true) + showBusy(isBusy: true, message: "Searching") } + /// + /// Go to loging page when bluetooth device is successfully configured + /// func bleDeviceConfigured() { showBusy(isBusy: false) - let provisionVC = storyboard?.instantiateViewController(withIdentifier: "loginWithAmazon") as! LoginWithAmazonViewController - provisionVC.transport = bleTransport - - provisionVC.provisionConfig = provisionConfig - navigationController?.pushViewController(provisionVC, animated: true) + let loginAVS = storyboard?.instantiateViewController(withIdentifier: "loginWithAmazon") as! LoginWithAmazonViewController + loginAVS.provisionConfig = provisionConfig + loginAVS.transport = bleTransport + navigationController?.pushViewController(loginAVS, animated: true) } - func bleDeviceNotConfigured() { + /// + /// Show alert if ble device is not configured + /// + func bleDeviceNotConfigured(title: String, message: String) { + bleDeviceConnected = true showBusy(isBusy: false) - let alertController = UIAlertController(title: "Configure BLE device", message: "Could not configure the selected bluetooth device", preferredStyle: UIAlertController.Style.alert) - alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertAction.Style.default, handler: nil)) + let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) + alertController.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) present(alertController, animated: true, completion: nil) } + // MARK: // TableView Methods + func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { guard let peripherals = self.peripherals else { return 0 @@ -82,11 +90,10 @@ class BLELandingViewController: UIViewController, UITableViewDelegate, UITableVi return peripherals.count } - func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = UITableViewCell(style: .default, reuseIdentifier: "bleDeviceCell") + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "bleDeviceCell", for: indexPath) as! BLEDeviceListViewCell if let peripheral: CBPeripheral = self.peripherals?[indexPath.row] { - cell.textLabel?.text = peripheral.name - cell.imageView?.image = UIImage(named: "bluetooth_icon") + cell.deviceName.text = peripheral.name } return cell @@ -99,27 +106,31 @@ class BLELandingViewController: UIViewController, UITableViewDelegate, UITableVi func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if let peripheral = self.peripherals?[indexPath.row] { + showBusy(isBusy: true) bleTransport?.connect(peripheral: peripheral, withOptions: nil) - print(peripheral) + bleDeviceConnected = false + bleConnectTimer.invalidate() + bleConnectTimer = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(bleConnectionTimeout), userInfo: nil, repeats: false) } - - showBusy(isBusy: true) } - private func showBusy(isBusy: Bool) { - if isBusy { - grayView = UIView(frame: UIScreen.main.bounds) - grayView?.backgroundColor = UIColor(white: 0.5, alpha: 0.5) - view.addSubview(grayView!) - - activityView = UIActivityIndicatorView(style: .gray) - activityView?.center = view.center - activityView?.startAnimating() + private func showBusy(isBusy: Bool, message: String = "") { + DispatchQueue.main.async { + if isBusy { + let loader = MBProgressHUD.showAdded(to: self.view, animated: true) + loader.mode = MBProgressHUDMode.indeterminate + loader.label.text = message + } else { + MBProgressHUD.hide(for: self.view, animated: true) + } + } + } - view.addSubview(activityView!) - } else { - grayView?.removeFromSuperview() - activityView?.removeFromSuperview() + @objc func bleConnectionTimeout() { + if !bleDeviceConnected { + bleTransport?.disconnect() + bleConnectTimer.invalidate() + bleDeviceNotConfigured(title: "Error!", message: "Communication failed. Device may not be supported. ") } } } @@ -136,17 +147,44 @@ extension BLELandingViewController: BLETransportDelegate { } func peripheralConfigured(peripheral _: CBPeripheral) { + bleDeviceConnected = true bleDeviceConfigured() } func peripheralNotConfigured(peripheral _: CBPeripheral) { - bleDeviceNotConfigured() + bleDeviceNotConfigured(title: "Configure BLE device", message: "Could not configure the selected bluetooth device") } func peripheralDisconnected(peripheral: CBPeripheral, error _: Error?) { - let alertMessage = "Peripheral device disconnected" - let alertController = UIAlertController(title: "Provision device", message: alertMessage, preferredStyle: UIAlertController.Style.alert) - alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertAction.Style.default, handler: nil)) - present(alertController, animated: true, completion: nil) + showBusy(isBusy: false) + + if delegate == nil { + let alertMessage = "Peripheral device disconnected" + let alertController = UIAlertController(title: "Provision device", message: alertMessage, preferredStyle: UIAlertController.Style.alert) + alertController.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) + present(alertController, animated: true, completion: nil) + } else { + delegate?.peripheralDisconnected() + } + } + + func bluetoothUnavailable() { + DispatchQueue.main.async { + let alertMessage = "Turn on your Phone's Bluetooth to allow search for discoverable device's" + let alertController = UIAlertController(title: "Error", message: alertMessage, preferredStyle: UIAlertController.Style.alert) + alertController.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + MBProgressHUD.hide(for: self.view, animated: true) + } + } +} + +// MARK: UITextFieldDelegate + +extension BLELandingViewController: UITextFieldDelegate { + func textFieldShouldReturn(_: UITextField) -> Bool { + view.endEditing(true) + rescanBLEDevices(self) + return false } } diff --git a/EspressifProvision/EspressifProvision/ui/LoginWithAmazonViewController.swift b/EspressifProvision/EspressifProvision/ui/LoginWithAmazonViewController.swift index 10201bf..1c045a0 100644 --- a/EspressifProvision/EspressifProvision/ui/LoginWithAmazonViewController.swift +++ b/EspressifProvision/EspressifProvision/ui/LoginWithAmazonViewController.swift @@ -22,43 +22,39 @@ import UIKit class LoginWithAmazonViewController: UIViewController { var provisionConfig: [String: String] = [:] var transport: Transport? - var secu: Security? - var newSession: Session? - var bleTransport: BLETransport? + var security: Security? + var session: Session? var configureAvs: ConfigureAVS? var waiter: Bool? var deviceDetails: [String] = ["", "", ""] + var deviceName: String? + var capabilities: [String]? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. + navigationItem.title = deviceName ?? "" + + // Add skip button + let rightBarButton = UIBarButtonItem(title: "Skip", style: .plain, target: self, action: #selector(skipAction)) + navigationItem.rightBarButtonItem = rightBarButton + navigationItem.rightBarButtonItem?.tintColor = UIColor.white + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + + // Initiate session to pass data with device + Constants.showLoader(message: "", view: view) + initialiseSession() + } + + @objc func skipAction() { + navigateToProvisionVC(result: nil) } @IBAction func onAmazonLoginClicked(_: Any) { - let bleConfigUuid = provisionConfig[Provision.CONFIG_BLE_CONFIG_UUID] - var configUUIDMap: [String: String] = [Provision.PROVISIONING_CONFIG_PATH: bleConfigUuid!] - let avsconfigUuid = provisionConfig[ConfigureAVS.AVS_CONFIG_UUID_KEY] - configUUIDMap[ConfigureAVS.AVS_CONFIG_PATH] = avsconfigUuid - - bleTransport = BLETransport( - serviceUUIDString: provisionConfig[Provision.CONFIG_BLE_SERVICE_UUID], - sessionUUIDString: provisionConfig[Provision.CONFIG_BLE_SESSION_UUID]!, - - configUUIDMap: configUUIDMap, - deviceNamePrefix: provisionConfig[Provision.CONFIG_BLE_DEVICE_NAME_PREFIX]!, - scanTimeout: 5.0 - ) - - let securityVersion = provisionConfig[Provision.CONFIG_SECURITY_KEY] - let pop = provisionConfig[Provision.CONFIG_PROOF_OF_POSSESSION_KEY] - if securityVersion == Provision.CONFIG_SECURITY_SECURITY1 { - secu = Security1(proofOfPossession: pop!) - } else { - secu = Security0() - } + Constants.showLoader(message: "Signing in", view: view) do { - getDeviceDetails(tras: bleTransport!, secu: secu as! Security1) { _ in + getDeviceDetails(tras: transport!, secu: security as! Security1) { _ in self.callLWA() } } @@ -67,59 +63,97 @@ class LoginWithAmazonViewController: UIViewController { private func getDeviceDetails(tras _: Transport, secu _: Security1, completionHandler: @escaping (String) -> Swift.Void) { - let newSession = Session(transport: transport!, - security: secu!) + let prov = Provision(session: session!) + deviceDetails = prov.getAVSDeviceDetails(completionHandler: { _, error in + guard error == nil else { + print(error!) - newSession.initialize(response: nil) { error in + return + } + + completionHandler("nil") + }) + return + } + + private func initialiseSession() { + DispatchQueue.main.async { + let input = UIAlertController(title: "Proof of Possession", message: nil, preferredStyle: .alert) + + input.addTextField { textField in + textField.text = "abcd1234" + } + input.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: { _ in + self.transport?.disconnect() + self.navigationController?.popViewController(animated: true) + })) + input.addAction(UIAlertAction(title: "Done", style: .default, handler: { [weak input] _ in + let textField = input?.textFields![0] + self.provisionConfig[Provision.CONFIG_PROOF_OF_POSSESSION_KEY] = textField?.text ?? "" + self.security = Security1(proofOfPossession: self.provisionConfig[Provision.CONFIG_PROOF_OF_POSSESSION_KEY]!) + Constants.hideLoader(view: self.view) + self.initSession() + })) + self.present(input, animated: true, completion: nil) + } + } + + func initSession() { + Constants.showLoader(message: "Initiating Session", view: view) + session = Session(transport: transport!, + security: security!) + session!.initialize(response: nil) { error in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + } guard error == nil else { print("Error in establishing session \(error.debugDescription)") + self.showStatusScreen() return } - if newSession.isEstablished { - var prov: Provision - prov = Provision(session: newSession) - self.deviceDetails = prov.getAVSDeviceDetails(completionHandler: { _, error in - guard error == nil else { - print(error!) - - return - } - - completionHandler("nil") - // self.callLWA() - }) + } + } + + func showStatusScreen() { + DispatchQueue.main.async { + let statusVC = self.storyboard?.instantiateViewController(withIdentifier: "successViewController") as! StatusViewController + statusVC.statusText = "Error establishing session.\n Check if Proof of Possession(POP) is correct!" + self.navigationController?.present(statusVC, animated: true, completion: nil) + } + } + + private func navigateToProvisionVC(result: [String: String]?) { + var config = provisionConfig + if let results = result { + results.forEach { config[$0] = $1 } + } + DispatchQueue.main.async { + let transportVersion = config[Provision.CONFIG_TRANSPORT_KEY] + if let transportVersion = transportVersion, transportVersion == Provision.CONFIG_TRANSPORT_BLE { + let provisionVC = self.storyboard?.instantiateViewController(withIdentifier: "provision") as! ProvisionViewController + provisionVC.provisionConfig = config + provisionVC.avsDetails = result + provisionVC.transport = self.transport + provisionVC.security = self.security! + provisionVC.session = self.session! + self.navigationController?.pushViewController(provisionVC, animated: true) + } else { + let provisionLandingVC = self.storyboard?.instantiateViewController(withIdentifier: "provisionLanding") as! ProvisionLandingViewController + provisionLandingVC.provisionConfig = config + self.navigationController?.pushViewController(provisionLandingVC, animated: true) } } - return } public func callLWA() { DispatchQueue.main.async { ConfigureAVS.loginWithAmazon { results, error in + Constants.hideLoader(view: self.view) if error != nil { print(error.debugDescription) } else if let results = results { - // Write AVS details to device -// print(results) -// self.putAVSDetails(results: results) self.waiter = true - var config = self.provisionConfig - results.forEach { config[$0] = $1 } - DispatchQueue.main.async { - let transportVersion = config[Provision.CONFIG_TRANSPORT_KEY] - if let transportVersion = transportVersion, transportVersion == Provision.CONFIG_TRANSPORT_BLE { - let provisionVC = self.storyboard?.instantiateViewController(withIdentifier: "provision") as! ProvisionViewController - provisionVC.provisionConfig = config - provisionVC.avsDetails = results - provisionVC.transport = self.transport - provisionVC.security = self.secu - self.navigationController?.pushViewController(provisionVC, animated: true) - } else { - let provisionLandingVC = self.storyboard?.instantiateViewController(withIdentifier: "provisionLanding") as! ProvisionLandingViewController - provisionLandingVC.provisionConfig = config - self.navigationController?.pushViewController(provisionLandingVC, animated: true) - } - } + self.navigateToProvisionVC(result: results) } } } diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/AboutViewController.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/AboutViewController.swift new file mode 100644 index 0000000..b0b1054 --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/AboutViewController.swift @@ -0,0 +1,32 @@ +// +// AboutViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 19/11/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import UIKit + +class AboutViewController: UIViewController { + @IBOutlet var deviceNameLabel: UILabel! + @IBOutlet var wifiLabel: UILabel! + @IBOutlet var ipAddressLabel: UILabel! + @IBOutlet var macLabel: UILabel! + @IBOutlet var serialNumberLabel: UILabel! + @IBOutlet var firmwareVersionLabel: UILabel! + + var device: AlexaDevice! + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + deviceNameLabel.text = device.deviceName ?? "" + wifiLabel.text = device.connectedWifi ?? "" + ipAddressLabel.text = device.hostAddress ?? "" + macLabel.text = device.mac ?? "" + serialNumberLabel.text = device.serialNumber ?? "" + firmwareVersionLabel.text = device.fwVersion ?? "" + } +} diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceDetailViewController.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceDetailViewController.swift index a9a6065..0f36321 100644 --- a/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceDetailViewController.swift +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceDetailViewController.swift @@ -14,13 +14,13 @@ class DeviceDetailViewController: UIViewController { var loginStatus = false var device: AlexaDevice? var avsConfig: ConfigureAVS? + var session: Session! @IBOutlet var signedInViewContainer: UIView! @IBOutlet var signedOutViewContainer: UIView! + @IBOutlet var learnMoreTextView: UITextView! override func viewDidLoad() { let label = UILabel(frame: CGRect(x: 10, y: 0, width: 50, height: 40)) -// label.backgroundColor = .red -// label.font = UIFont.boldSystemFont(ofSize: 14) label.text = device?.friendlyname label.numberOfLines = 2 @@ -28,7 +28,26 @@ class DeviceDetailViewController: UIViewController { label.sizeToFit() label.textAlignment = .center - navigationItem.titleView = label + navigationItem.title = device?.friendlyname + + let attributedString = NSMutableAttributedString(string: "To learn more and access additional features, download the Alexa App") + let url = URL(string: "alexa://")! + var redirectURL = url + if !UIApplication.shared.canOpenURL(url) { + redirectURL = URL(string: "https://apps.apple.com/in/app/amazon-alexa/id944011620")! + } + + attributedString.setAttributes([.link: redirectURL], range: NSRange(location: attributedString.length - 9, length: 9)) + learnMoreTextView.attributedText = attributedString + learnMoreTextView.isUserInteractionEnabled = true + learnMoreTextView.isEditable = false + + // Set how links should appear: blue and underlined + learnMoreTextView.linkTextAttributes = [ + .foregroundColor: UIColor.blue, + .underlineStyle: NSUnderlineStyle.single.rawValue, + ] + learnMoreTextView.textAlignment = .center } override func viewWillAppear(_: Bool) { @@ -38,43 +57,33 @@ class DeviceDetailViewController: UIViewController { @IBAction func signInAmazon(_: Any) { Constants.showLoader(message: "Signing In", view: view) - let transport = SoftAPTransport(baseUrl: device!.hostAddress! + ":80") - let security = Security0() - let session = Session(transport: transport, security: security) - session.initialize(response: nil) { error in - guard error == nil else { - print("Error in establishing session \(error.debugDescription)") - MBProgressHUD.hide(for: self.view, animated: true) - return - } - if session.isEstablished { - let prov = Provision(session: session) - _ = prov.getAVSDeviceDetails(completionHandler: { _, error in - guard error == nil else { - print(error!) - DispatchQueue.main.async { - MBProgressHUD.hide(for: self.view, animated: true) - } - return - } + if session.isEstablished { + let prov = Provision(session: session) + _ = prov.getAVSDeviceDetails(completionHandler: { _, error in + guard error == nil else { + print(error!) DispatchQueue.main.async { - ConfigureAVS.loginWithAmazon(completionHandler: { avsDetails, error in - if error == nil { - prov.putAVSDeviceDetails(config: avsDetails!, completionHandler: { - DispatchQueue.main.async { - self.loginStatus = true - self.updateUIView() - } - }) - } - MBProgressHUD.hide(for: self.view, animated: true) - }) + MBProgressHUD.hide(for: self.view, animated: true) } - }) - } else { + return + } DispatchQueue.main.async { - MBProgressHUD.hide(for: self.view, animated: true) + ConfigureAVS.loginWithAmazon(completionHandler: { avsDetails, error in + if error == nil { + prov.putAVSDeviceDetails(config: avsDetails!, completionHandler: { + DispatchQueue.main.async { + self.loginStatus = true + self.updateUIView() + } + }) + } + MBProgressHUD.hide(for: self.view, animated: true) + }) } + }) + } else { + DispatchQueue.main.async { + MBProgressHUD.hide(for: self.view, animated: true) } } } @@ -97,6 +106,7 @@ class DeviceDetailViewController: UIViewController { func updateUIView() { if loginStatus { navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Sign Out", style: .plain, target: self, action: #selector(signOut)) + navigationItem.rightBarButtonItem?.tintColor = UIColor.white signedInViewContainer.isHidden = false signedOutViewContainer.isHidden = true } else { diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceSettingViewController.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceSettingViewController.swift new file mode 100644 index 0000000..35f0dfa --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/DeviceSettingViewController.swift @@ -0,0 +1,139 @@ +// +// DeviceSettingViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 12/11/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import PickerView +import UIKit + +class DeviceSettingViewController: UIViewController { + @IBOutlet var deviceNameLabel: UILabel! + @IBOutlet var languageLabel: UILabel! + @IBOutlet var volumeSlider: UISlider! + @IBOutlet var volumeLabel: UILabel! + @IBOutlet var pickerView: UIPickerView! + + var configureDevice: ConfigureDevice! + var session: Session! + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + deviceNameLabel.text = configureDevice.alexaDevice.deviceName ?? "" + volumeSlider.value = Float(configureDevice.alexaDevice.volume ?? 0) + volumeLabel.text = "\(configureDevice.alexaDevice.volume ?? 0)" + languageLabel.text = configureDevice.languages[configureDevice.alexaDevice.language?.rawValue ?? 0] + } + + func showDeviceDetails(device: AlexaDevice, avsConfig: ConfigureAVS, loginStatus: Bool = false) { + DispatchQueue.main.async { + let deviceDetailVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.deviceDetailVCIndentifier) as! DeviceDetailViewController + deviceDetailVC.avsConfig = avsConfig + deviceDetailVC.loginStatus = loginStatus + deviceDetailVC.session = self.session + deviceDetailVC.device = device + self.navigationController?.pushViewController(deviceDetailVC, animated: true) + } + } + + private func addHeightConstraint(textField: UITextField) { + let heightConstraint = NSLayoutConstraint(item: textField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30) + textField.addConstraint(heightConstraint) + textField.font = UIFont(name: textField.font!.fontName, size: 18) + } + + // MARK: IBAction Methods + + @IBAction func setDeviceName(_: Any) { + let input = UIAlertController(title: "Device name", message: nil, preferredStyle: .alert) + + input.addTextField { textField in + textField.text = self.configureDevice.alexaDevice.deviceName ?? "" + textField.delegate = self + self.addHeightConstraint(textField: textField) + } + input.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: { _ in + + })) + input.addAction(UIAlertAction(title: "Done", style: .default, handler: { [weak input] _ in + let textField = input?.textFields![0] + if let name = textField?.text { + if name != self.configureDevice.alexaDevice.deviceName, name != "" { + Constants.showLoader(message: "Setting device name", view: self.view) + self.configureDevice.setDeviceName(withName: name) { result in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if result { + self.deviceNameLabel.text = name + self.configureDevice.alexaDevice.deviceName = name + } + } + } + } + } + })) + present(input, animated: true, completion: nil) + } + + @IBAction func setDeviceVolume(_ sender: UISlider) { + Constants.showLoader(message: "Setting device volume", view: view) + let volume = UInt32(sender.value) + configureDevice.setDeviceVolume(volume: volume) { result in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if result { + self.volumeLabel.text = "\(volume)" + self.configureDevice.alexaDevice.volume = volume + } + self.volumeSlider.value = Float(self.configureDevice.alexaDevice.volume ?? 0) + } + } + } + + @IBAction func setSoundClicked(_: Any) { + let soundSettingVC = storyboard?.instantiateViewController(withIdentifier: Constants.soundSettingVCIdentifier) as! SoundSettingViewController + soundSettingVC.configureDevice = configureDevice + navigationController?.pushViewController(soundSettingVC, animated: true) + } + + @IBAction func setLanguageClicked(_: Any) { + let languageListVC = storyboard?.instantiateViewController(withIdentifier: Constants.languageListVCIdentifier) as! LanguageListTableViewController + languageListVC.configureDevice = configureDevice + navigationController?.pushViewController(languageListVC, animated: true) + } + + @IBAction func aboutBtnClicked(_: Any) { + let aboutVC = storyboard?.instantiateViewController(withIdentifier: Constants.aboutVCIdentifier) as! AboutViewController + aboutVC.device = configureDevice.alexaDevice + navigationController?.pushViewController(aboutVC, animated: true) + } + + @IBAction func manageAccountClicked(_: Any) { + let avsConfig = ConfigureAVS(session: session) + avsConfig.isLoggedIn(completionHandler: { status in + self.showDeviceDetails(device: self.configureDevice.alexaDevice, avsConfig: avsConfig, loginStatus: status) + }) + } +} + +// MARK: UITextFieldDelegate + +/* + Over riding UITextField method in order to restrict the textfield to 22 characters only. + */ +extension DeviceSettingViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let maxLength = 22 + let currentString: NSString = textField.text! as NSString + let newString: NSString = + currentString.replacingCharacters(in: range, with: string) as NSString + return newString.length <= maxLength + } +} diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/LanguageListTableViewController.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/LanguageListTableViewController.swift new file mode 100644 index 0000000..3ae5151 --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/LanguageListTableViewController.swift @@ -0,0 +1,66 @@ +// +// LanguageListTableViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 20/11/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import UIKit + +class LanguageListTableViewController: UITableViewController { + var configureDevice: ConfigureDevice! + + override func viewDidLoad() { + super.viewDidLoad() + } + + // MARK: - Table view data source + + override func numberOfSections(in _: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 1 + } + + override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return configureDevice.languages.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "languageListCell", for: indexPath) + cell.textLabel?.text = configureDevice.languages[indexPath.row] + if configureDevice.alexaDevice.language?.rawValue == indexPath.row { + cell.accessoryType = .checkmark + } else { + cell.accessoryType = .none + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + if indexPath.row != configureDevice.alexaDevice.language?.rawValue { + Constants.showLoader(message: "Setting language", view: view) + configureDevice.setDeviceLanguage(value: indexPath.row) { result in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if result { + tableView.cellForRow(at: IndexPath(row: self.configureDevice.alexaDevice.language?.rawValue ?? 0, section: 0))?.accessoryType = .none + self.configureDevice.alexaDevice.language = Avs_Locale(rawValue: indexPath.row) + tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark + } + } + } + } + } + + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + tableView.cellForRow(at: indexPath)?.accessoryType = .none + } + + override func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat { + return 60.0 + } +} diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/ScannedLocalDevicesVC.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/ScannedLocalDevicesVC.swift index db4cd16..8418717 100644 --- a/EspressifProvision/EspressifProvision/ui/ManageDevice/ScannedLocalDevicesVC.swift +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/ScannedLocalDevicesVC.swift @@ -18,36 +18,45 @@ class ScannedLocalDevicesVC: UIViewController { var retry = 3 var searchTarget = "urn:schemas-espressif-com:service:Alexa:1" var alexaDevices: [AlexaDevice] = [] + var configureDevice: ConfigureDevice! + var session: Session! override func viewDidLoad() { + // Clearing navigation bar back button text + navigationItem.title = "Devices" + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + + // Assigning delegates ssdpDiscovery.delegate = self tableView.tableFooterView = UIView() tableView.isHidden = true + + // Start SSDP discovery for local devices Constants.showLoader(message: "Scanning devices", view: view) searchLocalDevices() } + /* + Method to refresh local devices list + */ @IBAction func scanDevicesAgain(_: Any) { retry = 3 Constants.showLoader(message: "Scanning devices", view: view) searchLocalDevices() } + /* + Search for local devices using ssdp class + */ @objc func searchLocalDevices() { ssdpDiscovery.discoverService(forDuration: timeInterval, searchTarget: searchTarget) } - func parseResponse(header: String) -> [String: String] { - var dictionary: [String: String] = [:] - for item in header.components(separatedBy: "::") { - let value = item.components(separatedBy: ":") - dictionary.updateValue(value[1], forKey: value[0]) - } - return dictionary - } - + /* + Check if search for devices should be retried based on number of devices returned + */ @objc func checkNeedForReDiscovery() { - if retry != 0 { + if retry > 0 { if alexaDevices.filter({ $0.friendlyname == nil }).count > 0 { retry -= 1 searchLocalDevices() @@ -66,17 +75,33 @@ class ScannedLocalDevicesVC: UIViewController { } } + func parseResponse(header: String) -> [String: String] { + var dictionary: [String: String] = [:] + for item in header.components(separatedBy: "::") { + let value = item.components(separatedBy: ":") + dictionary.updateValue(value[1], forKey: value[0]) + } + return dictionary + } + + /* + On click of any device, device info will fetched. This will present us with a new screen where all the details of the device will + be displayed + */ func showDeviceDetails(device: AlexaDevice, avsConfig: ConfigureAVS, loginStatus: Bool = false) { DispatchQueue.main.async { let deviceDetailVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.deviceDetailVCIndentifier) as! DeviceDetailViewController deviceDetailVC.avsConfig = avsConfig deviceDetailVC.loginStatus = loginStatus deviceDetailVC.device = device + deviceDetailVC.session = self.session self.navigationController?.pushViewController(deviceDetailVC, animated: true) } } } +// MARK: SSDPDiscoveryDelegate + extension ScannedLocalDevicesVC: SSDPDiscoveryDelegate { func ssdpDiscovery(_: SSDPDiscovery, didDiscoverService service: SSDPService) { let dictionary = parseResponse(header: service.uniqueServiceName ?? "") @@ -100,6 +125,7 @@ extension ScannedLocalDevicesVC: SSDPDiscoveryDelegate { } func ssdpDiscovery(_: SSDPDiscovery, didFinishWithError error: Error) { + retry = -1 print(error) } @@ -110,33 +136,39 @@ extension ScannedLocalDevicesVC: SSDPDiscoveryDelegate { } } +// MARK: UITableViewDelegate + extension ScannedLocalDevicesVC: UITableViewDelegate { func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat { return 60.0 } func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { + Constants.showLoader(message: "Getting device info", view: view) let alexaDevice = alexaDevices[indexPath.row] let transport = SoftAPTransport(baseUrl: alexaDevice.hostAddress! + ":80") let security = Security0() - let session = Session(transport: transport, security: security) + session = Session(transport: transport, security: security) session.initialize(response: nil) { error in guard error == nil else { + Constants.hideLoader(view: self.view) print("Error in establishing session \(error.debugDescription)") return } - let avsConfig = ConfigureAVS(session: session) - avsConfig.isLoggedIn(completionHandler: { status in - self.showDeviceDetails(device: alexaDevice, avsConfig: avsConfig, loginStatus: status) - }) + self.configureDevice = ConfigureDevice(session: self.session, device: alexaDevice) + self.configureDevice.delegate = self + self.configureDevice.getDeviceInfo() } + tableView.deselectRow(at: indexPath, animated: true) } } +// MARK: UITableViewDataSource + extension ScannedLocalDevicesVC: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: Constants.deviceListCellReuseIdentifier, for: indexPath) as! DeviceListTableViewCell - cell.deviceNameLabel.text = "\(alexaDevices[indexPath.row].friendlyname ?? "") | \(alexaDevices[indexPath.row].hostAddress ?? "")" + let cell = tableView.dequeueReusableCell(withIdentifier: Constants.deviceListCellReuseIdentifier, for: indexPath) as! BLEDeviceListViewCell + cell.deviceName.text = alexaDevices[indexPath.row].friendlyname ?? "" return cell } @@ -144,3 +176,24 @@ extension ScannedLocalDevicesVC: UITableViewDataSource { return alexaDevices.count } } + +// MARK: GetDeviceInfoDelegate + +extension ScannedLocalDevicesVC: GetDeviceInfoDelegate { + func deviceInfoFetched(alexaDevice: AlexaDevice?) { + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if alexaDevice != nil { + let deviceSettingVC = self.storyboard?.instantiateViewController(withIdentifier: Constants.deviceSettingVCIndentifier) as! DeviceSettingViewController + deviceSettingVC.configureDevice = self.configureDevice + deviceSettingVC.session = self.session + self.navigationController?.pushViewController(deviceSettingVC, animated: true) + } else { + let avsConfig = ConfigureAVS(session: self.session) + avsConfig.isLoggedIn(completionHandler: { status in + self.showDeviceDetails(device: self.configureDevice.alexaDevice, avsConfig: avsConfig, loginStatus: status) + }) + } + } + } +} diff --git a/EspressifProvision/EspressifProvision/ui/ManageDevice/SoundSettingViewController.swift b/EspressifProvision/EspressifProvision/ui/ManageDevice/SoundSettingViewController.swift new file mode 100644 index 0000000..0879a6c --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/ManageDevice/SoundSettingViewController.swift @@ -0,0 +1,58 @@ +// +// SoundSettingViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 18/11/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import UIKit + +class SoundSettingViewController: UIViewController { + var configureDevice: ConfigureDevice! + + @IBOutlet var endOfRequestSwitch: UISwitch! + @IBOutlet var startOfRequestSwitch: UISwitch! + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + startOfRequestSwitch.setOn(configureDevice.alexaDevice.startToneEnabled ?? false, animated: false) + endOfRequestSwitch.setOn(configureDevice.alexaDevice.endToneEnabled ?? false, animated: false) + } + + @IBAction func setDeviceStartTone(_ sender: UISwitch) { + DispatchQueue.main.async { + Constants.showLoader(message: "Applying configuration", view: self.view) + } + configureDevice.setDeviceStartTone(value: sender.isOn) { result in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if result { + self.configureDevice.alexaDevice.startToneEnabled = sender.isOn + } else { + sender.setOn(!sender.isOn, animated: true) + } + } + } + } + + @IBAction func setDeviceEndTone(_ sender: UISwitch) { + DispatchQueue.main.async { + Constants.showLoader(message: "Applying configuration", view: self.view) + } + configureDevice.setDeviceEndTone(value: sender.isOn) { result in + DispatchQueue.main.async { + Constants.hideLoader(view: self.view) + if result { + self.configureDevice.alexaDevice.endToneEnabled = sender.isOn + } else { + sender.setOn(!sender.isOn, animated: true) + } + } + } + } +} diff --git a/EspressifProvision/EspressifProvision/ui/ProvisionViewController.swift b/EspressifProvision/EspressifProvision/ui/ProvisionViewController.swift index cb04c62..b4c86a3 100644 --- a/EspressifProvision/EspressifProvision/ui/ProvisionViewController.swift +++ b/EspressifProvision/EspressifProvision/ui/ProvisionViewController.swift @@ -33,22 +33,47 @@ class ProvisionViewController: UIViewController { var bleTransport: BLETransport? var activityView: UIActivityIndicatorView? var grayView: UIView? - var avsDetails: [String: String]? var provision: Provision! var ssidList: [String] = [] - var wifiDetailList: [String: Int32] = [:] + var avsDetails: [String: String]? + var wifiDetailList: [String: Espressif_WiFiScanResult] = [:] + var versionInfo: String? + var session: Session? + var capabilities: [String]? + var alertTextField: UITextField? + var showPasswordImageView: UIImageView! + var forceAuthentication = false + @IBOutlet var headerView: UIView! override func viewDidLoad() { super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) + showPasswordImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) + let tap = UITapGestureRecognizer(target: self, action: #selector(showPassword)) + tap.numberOfTapsRequired = 1 + showPasswordImageView.isUserInteractionEnabled = true + showPasswordImageView.addGestureRecognizer(tap) + showPasswordImageView.contentMode = .scaleAspectFit + + configurePassphraseTextField() + passphraseTextfield.addTarget(self, action: #selector(passphraseEntered), for: .editingDidEndOnExit) ssidTextfield.addTarget(self, action: #selector(ssidEntered), for: .editingDidEndOnExit) provisionButton.isUserInteractionEnabled = false if let bleTransport = transport as? BLETransport { print("Inside PVC", bleTransport.currentPeripheral!) } + tableView.tableFooterView = UIView() - scanDeviceForWiFiList() + + navigationItem.backBarButtonItem?.title = "" + + if transport == nil { + transport = SoftAPTransport(baseUrl: Utility.baseUrl) + } + getDeviceVersionInfo() } private func showBusy(isBusy: Bool) { @@ -71,81 +96,97 @@ class ProvisionViewController: UIViewController { } private func provisionDevice(ssid: String, passphrase: String) { - showBusy(isBusy: true) + showLoader(message: "Device connecting to Wi-Fi") - let pop = provisionConfig[Provision.CONFIG_PROOF_OF_POSSESSION_KEY] let baseUrl = provisionConfig[Provision.CONFIG_BASE_URL_KEY] let transportVersion = provisionConfig[Provision.CONFIG_TRANSPORT_KEY] - let securityVersion = provisionConfig[Provision.CONFIG_SECURITY_KEY] - let bleDeviceNamePrefix = provisionConfig[Provision.CONFIG_BLE_DEVICE_NAME_PREFIX] - let bleServiceUuid = provisionConfig[Provision.CONFIG_BLE_SERVICE_UUID] - let bleSessionUuid = provisionConfig[Provision.CONFIG_BLE_SESSION_UUID] - let bleConfigUuid = provisionConfig[Provision.CONFIG_BLE_CONFIG_UUID] - - if securityVersion == Provision.CONFIG_SECURITY_SECURITY1 { - security = Security1(proofOfPossession: pop!) - } else { - security = Security0() - } - if transport != nil { // transport is BLETransport set from BLELandingVC if let bleTransport = transport as? BLETransport { bleTransport.delegate = self } - initialiseSessionAndConfigure(transport: transport!, - security: security!, ssid: ssid, passPhrase: passphrase) + initialiseSessionAndConfigure(ssid: ssid, passPhrase: passphrase) } else if transportVersion == Provision.CONFIG_TRANSPORT_WIFI { transport = SoftAPTransport(baseUrl: baseUrl!) - initialiseSessionAndConfigure(transport: transport!, - security: security!, ssid: ssid, passPhrase: passphrase) + initialiseSessionAndConfigure(ssid: ssid, passPhrase: passphrase) } else if transport == nil { - let configUUIDMap: [String: String] = [Provision.PROVISIONING_CONFIG_PATH: bleConfigUuid!] - - bleTransport = BLETransport(serviceUUIDString: bleServiceUuid!, - sessionUUIDString: bleSessionUuid!, - configUUIDMap: configUUIDMap, - deviceNamePrefix: bleDeviceNamePrefix!, - scanTimeout: 5.0) + bleTransport = BLETransport(scanTimeout: 2.0) bleTransport?.scan(delegate: self) transport = bleTransport } } func scanDeviceForWiFiList() { - showLoader(message: "Scanning for Wifi") - let newSession = Session(transport: transport!, security: security!) - let scanWifiManager: ScanWifiList = ScanWifiList(session: newSession) - scanWifiManager.delegate = self - scanWifiManager.startWifiScan() + if session!.isEstablished { + DispatchQueue.main.async { + self.showLoader(message: "Scanning for Wi-Fi") + let scanWifiManager: ScanWifiList = ScanWifiList(session: self.session!) + scanWifiManager.delegate = self + scanWifiManager.startWifiScan() + } + } } - func initialiseSessionAndConfigure(transport: Transport, - security: Security, ssid: String, passPhrase: String) { - let newSession = Session(transport: transport, - security: security) + @IBAction func rescanWiFiList(_: Any) { + scanDeviceForWiFiList() + } - newSession.initialize(response: nil) { error in + func initialiseSessionAndConfigure(ssid: String, passPhrase: String) { + if transport!.isDeviceConfigured() { + if session!.isEstablished { + let provision = Provision(session: session!) + + provision.configureWifiAvs(ssid: ssid, + passphrase: passPhrase, + avs: avsDetails) { status, error in + guard error == nil else { + print("Error in configuring wifi : \(error.debugDescription)") + return + } + if status == Espressif_Status.success { + self.applyConfigurations(provision: provision) + } + } + } else { + print("Session is not established") + } + } else { + showError(errorMessage: "Peripheral device could not be configured.") + } + } + + func getDeviceVersionInfo() { + forceAuthentication = false + showLoader(message: "Connecting Device") + transport?.SendConfigData(path: (transport?.utility.versionPath)!, data: Data("V0.2".utf8), completionHandler: { response, error in + DispatchQueue.main.async { + MBProgressHUD.hide(for: self.view, animated: true) + } guard error == nil else { - print("Error in establishing session \(error.debugDescription)") + print("Error reading device version info") + self.showConnectionFailure() return } - - let provision = Provision(session: newSession) - - provision.configureWifiAvs(ssid: ssid, - passphrase: passPhrase, - avs: self.avsDetails!) { status, error in - guard error == nil else { - print("Error in configuring wifi : \(error.debugDescription)") - return + do { + if let result = try JSONSerialization.jsonObject(with: response!, options: .mutableContainers) as? NSDictionary { + self.transport?.utility.deviceVersionInfo = result + if let prov = result[Constants.provKey] as? NSDictionary, let capabilities = prov[Constants.capabilitiesKey] as? [String] { + self.capabilities = capabilities + self.scanDeviceForWiFiList() + } } - if status == Espressif_Status.success { - self.applyConfigurations(provision: provision) + } catch { + let responseString = String(decoding: response!, as: UTF8.self) + if responseString.lowercased() == "success" { + self.forceAuthentication = false + } else { + self.forceAuthentication = true } + self.scanDeviceForWiFiList() + print(error) } - } + }) } @objc func passphraseEntered() { @@ -188,39 +229,27 @@ class ProvisionViewController: UIViewController { wifiStatusUpdatedHandler: { wifiState, failReason, error in DispatchQueue.main.async { self.showBusy(isBusy: false) - let successVC = self.storyboard?.instantiateViewController(withIdentifier: "successViewController") as? SuccessViewController - if let successVC = successVC { - if error != nil { - successVC.statusText = "Error in getting wifi state : \(error.debugDescription)" - } else if wifiState == Espressif_WifiStationState.connected { - successVC.statusText = "Device has been successfully provisioned!" - } else if wifiState == Espressif_WifiStationState.disconnected { - successVC.statusText = "Please check the device indicators for Provisioning status." - } else { - successVC.statusText = "Device provisioning failed.\nReason : \(failReason).\nPlease try again" - } - self.navigationController?.present(successVC, animated: true, completion: nil) - self.provisionButton.isUserInteractionEnabled = true + let statusVC = self.storyboard?.instantiateViewController(withIdentifier: "statusViewController") as! StatusViewController + if error != nil { + statusVC.statusText = "Error in getting wifi state : \(error.debugDescription)" + self.navigationController?.present(statusVC, animated: true, completion: nil) + } else if wifiState == Espressif_WifiStationState.connected { + let successVC = self.storyboard?.instantiateViewController(withIdentifier: "successViewController") as! SuccessViewController + self.navigationController?.show(successVC, sender: nil) + } else if wifiState == Espressif_WifiStationState.disconnected { + statusVC.statusText = "Please check the device indicators for Provisioning status." + self.navigationController?.present(statusVC, animated: true, completion: nil) + } else { + statusVC.statusText = "Device provisioning failed.\nReason : \(failReason).\nPlease try again" + self.navigationController?.present(statusVC, animated: true, completion: nil) } + self.provisionButton.isUserInteractionEnabled = true } }) } - func showError(errorMessage: String) { - let alertMessage = errorMessage - let alertController = UIAlertController(title: "Provision device", message: alertMessage, preferredStyle: UIAlertController.Style.alert) - alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertAction.Style.default, handler: nil)) - present(alertController, animated: true, completion: nil) - } - - func showLoader(message: String) { - let loader = MBProgressHUD.showAdded(to: view, animated: true) - loader.mode = MBProgressHUDMode.indeterminate - loader.label.text = message - } - func setWifiIconImageFor(cell: WifiListTableViewCell, ssid: String) { - let rssi = wifiDetailList[ssid] ?? -70 + let rssi = wifiDetailList[ssid]?.rssi ?? -70 if rssi > Int32(-50) { cell.signalImageView.image = UIImage(named: "wifi_symbol_strong") } else if rssi > Int32(-60) { @@ -230,9 +259,130 @@ class ProvisionViewController: UIViewController { } else { cell.signalImageView?.image = UIImage(named: "wifi_symbol_weak") } + if wifiDetailList[ssid]?.auth != Espressif_WifiAuthMode.open { + cell.authenticationImageView.image = UIImage(named: "wifi_security") + cell.authenticationImageView.isHidden = false + } else { + cell.authenticationImageView.isHidden = true + } + } + + func showTextFieldUI() { + DispatchQueue.main.async { + self.tableView.isHidden = true + self.ssidTextfield.isHidden = false + self.passphraseTextfield.isHidden = false + self.provisionButton.isHidden = false + self.headerView.isHidden = true + } + } + + private func joinOtherNetwork() { + let input = UIAlertController(title: "", message: nil, preferredStyle: .alert) + + input.addTextField { textField in + textField.placeholder = "Network Name" + self.addHeightConstraint(textField: textField) + } + + input.addTextField { textField in + self.configurePasswordTextfield(textField: textField) + self.addHeightConstraint(textField: textField) + } + input.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: { _ in + })) + input.addAction(UIAlertAction(title: "Connect", style: .default, handler: { [weak input] _ in + let ssidTextField = input?.textFields![0] + let passphrase = input?.textFields![1] + + if let ssid = ssidTextField?.text, ssid.count > 0 { + self.provisionDevice(ssid: ssid, passphrase: passphrase?.text ?? "") + } + })) + DispatchQueue.main.async { + self.present(input, animated: true, completion: nil) + } + } + + func showStatusScreen() { + DispatchQueue.main.async { + let statusVC = self.storyboard?.instantiateViewController(withIdentifier: "successViewController") as! StatusViewController + statusVC.statusText = "Error establishing session.\n Check if Proof of Possession(POP) is correct!" + self.navigationController?.present(statusVC, animated: true, completion: nil) + } + } + + @objc func showPassword() { + if let secureEntry = self.alertTextField?.isSecureTextEntry { + alertTextField?.togglePasswordVisibility() + if secureEntry { + showPasswordImageView.image = UIImage(named: "hide_password") + } else { + showPasswordImageView.image = UIImage(named: "show_password") + } + } + } + + private func addHeightConstraint(textField: UITextField) { + let heightConstraint = NSLayoutConstraint(item: textField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 30) + textField.addConstraint(heightConstraint) + textField.font = UIFont(name: textField.font!.fontName, size: 18) + } + + private func configurePasswordTextfield(textField: UITextField) { + alertTextField = textField + textField.placeholder = "Password" + textField.isSecureTextEntry = true + showPasswordImageView.image = UIImage(named: "show_password") + let rightView = UIView(frame: CGRect(x: 0, y: 0, width: showPasswordImageView.frame.width + 10, height: showPasswordImageView.frame.height)) + rightView.addSubview(showPasswordImageView) + textField.rightView = rightView + textField.rightViewMode = .always + } + + private func configurePassphraseTextField() { + alertTextField = passphraseTextfield + passphraseTextfield.placeholder = "Password" + passphraseTextfield.isSecureTextEntry = true + showPasswordImageView.image = UIImage(named: "show_password") + let rightView = UIView(frame: CGRect(x: 0, y: 0, width: showPasswordImageView.frame.width + 10, height: showPasswordImageView.frame.height)) + rightView.addSubview(showPasswordImageView) + passphraseTextfield.rightView = rightView + passphraseTextfield.rightViewMode = .always + } + + // MARK: Activity Indicators + + private func showConnectionFailure() { + DispatchQueue.main.async { + let alert = UIAlertController(title: "Failure", message: "Connection to device failed.\n Please make sure you are connected to the Wi-Fi network of device.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: { _ in + self.navigationController?.popViewController(animated: true) + })) + self.present(alert, animated: true, completion: nil) + } + } + + func showError(errorMessage: String) { + let alertMessage = errorMessage + let alertController = UIAlertController(title: "Provision device", message: alertMessage, preferredStyle: UIAlertController.Style.alert) + alertController.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)) + present(alertController, animated: true, completion: nil) + } + + func showLoader(message: String) { + DispatchQueue.main.async { + let loader = MBProgressHUD.showAdded(to: self.view, animated: true) + loader.mode = MBProgressHUDMode.indeterminate + loader.label.text = message + loader.backgroundView.blurEffectStyle = .dark + loader.bezelView.backgroundColor = UIColor.white + } } } +// MARK: BLETransportDelegate + extension ProvisionViewController: BLETransportDelegate { func peripheralsFound(peripherals: [CBPeripheral]) { bleTransport?.connect(peripheral: peripherals[0], withOptions: nil) @@ -248,14 +398,15 @@ extension ProvisionViewController: BLETransportDelegate { showError(errorMessage: "Peripheral device could not be configured.") } - func peripheralDisconnected(peripheral _: CBPeripheral, error: Error?) { - print(error?.localizedDescription ?? "") - showError(errorMessage: "Peripheral device disconnected") - } + func peripheralDisconnected(peripheral: CBPeripheral, error _: Error?) {} + + func bluetoothUnavailable() {} } +// MARK: ScanWifiListProtocol + extension ProvisionViewController: ScanWifiListProtocol { - func wifiScanFinished(wifiList: [String: Int32]?, error _: Error?) { + func wifiScanFinished(wifiList: [String: Espressif_WiFiScanResult]?, error: Error?) { if wifiList?.count != 0, wifiList != nil { wifiDetailList = wifiList! ssidList = Array(wifiList!.keys) @@ -263,13 +414,13 @@ extension ProvisionViewController: ScanWifiListProtocol { self.tableView.isHidden = false self.ssidTextfield.isHidden = true self.passphraseTextfield.isHidden = true + self.headerView.isHidden = false self.tableView.reloadData() } } else { - DispatchQueue.main.async { - self.tableView.isHidden = true - self.ssidTextfield.isHidden = false - self.passphraseTextfield.isHidden = false + showTextFieldUI() + if error != nil { + print("Unable to fetch wifi list :\(String(describing: error))") } } DispatchQueue.main.async { @@ -278,33 +429,40 @@ extension ProvisionViewController: ScanWifiListProtocol { } } +// MARK: UITableViewDelegate + extension ProvisionViewController: UITableViewDelegate { func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + if indexPath.row >= ssidList.count { + joinOtherNetwork() + } else { + let ssid = ssidList[indexPath.row] - let ssid = ssidList[indexPath.row] - - let input = UIAlertController(title: ssid, message: nil, preferredStyle: .alert) + if wifiDetailList[ssid]?.auth != Espressif_WifiAuthMode.open || forceAuthentication { + let input = UIAlertController(title: ssid, message: nil, preferredStyle: .alert) - input.addTextField { textField in - textField.placeholder = "Password" - textField.isSecureTextEntry = true - } + input.addTextField { textField in + self.configurePasswordTextfield(textField: textField) + self.addHeightConstraint(textField: textField) + } + input.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: { _ in - input.addAction(UIAlertAction(title: "Done", style: .default, handler: { [weak input] _ in - let textField = input?.textFields![0] - guard let passphrase = textField?.text else { - return - } - if passphrase.count > 0 { - self.provisionDevice(ssid: ssid, passphrase: passphrase) + })) + input.addAction(UIAlertAction(title: "Provision", style: .default, handler: { [weak input] _ in + let textField = input?.textFields![0] + guard let passphrase = textField?.text else { + return + } + if passphrase.count > 0 || self.forceAuthentication { + self.provisionDevice(ssid: ssid, passphrase: passphrase) + } + })) + present(input, animated: true, completion: nil) + } else { + provisionDevice(ssid: ssid, passphrase: "") } - })) - input.addAction(UIAlertAction(title: "Cancel", style: .destructive, handler: { _ in - - })) - - present(input, animated: true, completion: nil) + } } func tableView(_: UITableView, heightForRowAt _: IndexPath) -> CGFloat { @@ -312,15 +470,59 @@ extension ProvisionViewController: UITableViewDelegate { } } +// MARK: UITableViewDataSource + extension ProvisionViewController: UITableViewDataSource { func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - return ssidList.count + return ssidList.count + 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "wifiListCell", for: indexPath) as! WifiListTableViewCell - cell.ssidLabel.text = ssidList[indexPath.row] - setWifiIconImageFor(cell: cell, ssid: ssidList[indexPath.row]) + if indexPath.row >= ssidList.count { + cell.ssidLabel.text = "Join Other Network" + cell.signalImageView.image = UIImage(named: "add_icon") + } else { + cell.ssidLabel.text = ssidList[indexPath.row] + setWifiIconImageFor(cell: cell, ssid: ssidList[indexPath.row]) + } return cell } } + +// MARK: BLEStatusProtocol + +extension ProvisionViewController: BLEStatusProtocol { + func peripheralDisconnected() { + MBProgressHUD.hide(for: view, animated: true) + if !(session?.isEstablished ?? false) { + showStatusScreen() + } + } +} + +// MARK: UITextField Extension + +extension UITextField { + func togglePasswordVisibility() { + isSecureTextEntry = !isSecureTextEntry + + if let existingText = text, isSecureTextEntry { + /* When toggling to secure text, all text will be purged if the user + continues typing unless we intervene. This is prevented by first + deleting the existing text and then recovering the original text. */ + deleteBackward() + + if let textRange = textRange(from: beginningOfDocument, to: endOfDocument) { + replace(textRange, withText: existingText) + } + } + + /* Reset the selected text range since the cursor can end up in the wrong + position after a toggle because the text might vary in width */ + if let existingSelectedTextRange = selectedTextRange { + selectedTextRange = nil + selectedTextRange = existingSelectedTextRange + } + } +} diff --git a/EspressifProvision/EspressifProvision/ui/StatusViewController.swift b/EspressifProvision/EspressifProvision/ui/StatusViewController.swift new file mode 100644 index 0000000..b05396d --- /dev/null +++ b/EspressifProvision/EspressifProvision/ui/StatusViewController.swift @@ -0,0 +1,20 @@ +// +// DoneViewController.swift +// EspressifProvision +// +// Created by Vikas Chandra on 16/07/19. +// Copyright © 2019 Espressif. All rights reserved. +// + +import Foundation +import UIKit + +class StatusViewController: UIViewController { + var statusText: String? + + @IBOutlet var successLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + } +} diff --git a/EspressifProvision/EspressifProvision/ui/SuccessViewController.swift b/EspressifProvision/EspressifProvision/ui/SuccessViewController.swift index 33672fd..9242775 100644 --- a/EspressifProvision/EspressifProvision/ui/SuccessViewController.swift +++ b/EspressifProvision/EspressifProvision/ui/SuccessViewController.swift @@ -20,15 +20,45 @@ import Foundation import UIKit class SuccessViewController: UIViewController { - var statusText: String? - - @IBOutlet var successLabel: UILabel! + @IBOutlet var learnMoreTextView: UITextView! override func viewDidLoad() { super.viewDidLoad() - if let statusText = statusText { - successLabel.text = statusText - } // Do any additional setup after loading the view, typically from a nib. + let attributedString = NSMutableAttributedString(string: "To learn more and access additional features, download the Alexa App") + let url = URL(string: "alexa://")! + var redirectURL = url + if !UIApplication.shared.canOpenURL(url) { + redirectURL = URL(string: "https://apps.apple.com/in/app/amazon-alexa/id944011620")! + } + + attributedString.setAttributes([.link: redirectURL], range: NSRange(location: attributedString.length - 9, length: 9)) + + learnMoreTextView.attributedText = attributedString + learnMoreTextView.isUserInteractionEnabled = true + learnMoreTextView.isEditable = false + + // Set how links should appear: blue and underlined + learnMoreTextView.linkTextAttributes = [ + .foregroundColor: UIColor.blue, + .underlineStyle: NSUnderlineStyle.single.rawValue, + ] + learnMoreTextView.textAlignment = .center + + let righButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(presentVC)) + + navigationItem.rightBarButtonItem = righButtonItem + navigationItem.rightBarButtonItem?.tintColor = UIColor.white + + navigationItem.setHidesBackButton(true, animated: true) + + navigationItem.title = "Things to try" + } + + /// + /// Go to intial View Controller. + /// + @objc func presentVC() { + performSegue(withIdentifier: "presentFirstVC", sender: nil) } } diff --git a/EspressifProvision/Podfile b/EspressifProvision/Podfile index 5f4a006..7a0e638 100644 --- a/EspressifProvision/Podfile +++ b/EspressifProvision/Podfile @@ -11,6 +11,7 @@ abstract_target 'EspressifProvisionAll' do pod 'Curve25519', '~> 1.1.0' pod 'MBProgressHUD', '~> 1.1.0' pod 'BlueSocket' + pod "PickerView" target 'EspressifProvision' target 'EspressifProvisionTests' do diff --git a/proto/avsconfig.proto b/proto/avsconfig.proto index ea53343..3cc910f 100644 --- a/proto/avsconfig.proto +++ b/proto/avsconfig.proto @@ -13,7 +13,8 @@ enum AVSConfigStatus { message CmdGetDetails { uint32 Dummy = 1; } -message RespGetDetails{ + +message RespGetDetails { AVSConfigStatus Status = 1; string Version = 2; string CodeChallenge = 3; @@ -27,7 +28,7 @@ message CmdSetConfig { string RedirectURI = 3; } -message RespSetConfig{ +message RespSetConfig { AVSConfigStatus Status = 1; uint32 Dummy = 2; } @@ -48,15 +49,110 @@ message RespSignOut { AVSConfigStatus Status = 1; } +enum Locale { + de_DE = 0; + en_AU = 1; + en_CA = 2; + en_GB = 3; + en_IN = 4; + en_US = 5; + es_ES = 6; + es_MX = 7; + es_US = 8; + fr_CA = 9; + fr_FR = 10; + hi_IN = 11; + it_IT = 12; + ja_JP = 13; + pt_BR = 14; +} + +message CmdSetAssistantLang { + Locale AssistantLang = 1; +} + +message RespSetAssistantLang { + AVSConfigStatus Status = 1; +} + +message CmdSetSORAudioCue { + bool AudioCue = 1; +} + +message CmdSetEORAudioCue { + bool AudioCue = 1; +} + +message RespSetSORAudioCue { + AVSConfigStatus Status = 1; +} + +message RespSetEORAudioCue { + AVSConfigStatus Status = 1; +} + +message CmdSetUserVisibleName { + string Name = 1; +} + +message RespSetUserVisibleName { + AVSConfigStatus Status = 1; +} + +message CmdGetDeviceInfo { + uint32 Dummy = 1; +} + +message CmdSetVolume { + uint32 Level = 1; +} + +message RespSetVolume { + AVSConfigStatus Status = 1; +} + +message AVSGenericDeviceInfo { + string FwVersion = 1; + string MAC = 2; + string SerialNum = 3; + string UserVisibleName = 4; + string WiFi = 5; +} + +message AVSSpecificDeviceInfo { + Locale AssistantLang = 1; + bool SORAudioCue = 2; + bool EORAudioCue = 3; + uint32 Volume = 4; +} + +message RespGetDeviceInfo { + AVSConfigStatus Status = 1; + AVSGenericDeviceInfo GenericInfo = 2; + AVSSpecificDeviceInfo AVSSpecificInfo = 3; +} + enum AVSConfigMsgType { TypeCmdGetDetails = 0; TypeRespGetDetails = 1; TypeCmdSetConfig = 2; - TypeRespSetConfig =3; + TypeRespSetConfig = 3; TypeCmdSignInStatus = 4; TypeRespSignInStatus = 5; TypeCmdSignOut = 6; TypeRespSignOut = 7; + TypeCmdGetDeviceInfo = 8; + TypeRespGetDeviceInfo = 9; + TypeCmdSetSORAudioCue = 10; + TypeRespSetSORAudioCue = 12; + TypeCmdSetEORAudioCue = 13; + TypeRespSetEORAudioCue = 14; + TypeCmdSetAssistantLang = 15; + TypeRespSetAssistantLang = 16; + TypeCmdSetUserVisibleName = 17; + TypeRespSetUserVisibleName = 18; + TypeCmdSetVolume = 19; + TypeRespSetVolume = 20; } message AVSConfigPayload { @@ -66,9 +162,21 @@ message AVSConfigPayload { RespGetDetails resp_get_details = 11; CmdSetConfig cmd_set_config = 12; RespSetConfig resp_set_config = 13; - CmdSignInStatus cmd_signin_status = 14; - RespSignInStatus resp_signin_status = 15; - CmdSignOut cmd_sign_out = 16; - RespSignOut resp_sign_out = 17; + CmdSignInStatus cmd_signin_status = 14; + RespSignInStatus resp_signin_status = 15; + CmdSignOut cmd_sign_out = 16; + RespSignOut resp_sign_out = 17; + CmdGetDeviceInfo cmd_get_device_info = 18; + RespGetDeviceInfo resp_get_device_info = 19; + CmdSetAssistantLang cmd_assistant_lang = 20; + RespSetAssistantLang resp_assistant_lang = 21; + CmdSetSORAudioCue cmd_sor_audio_cue = 22; + RespSetSORAudioCue resp_sor_audio_cue = 23; + CmdSetEORAudioCue cmd_eor_audio_cue = 24; + RespSetEORAudioCue resp_eor_audio_cue = 25; + CmdSetUserVisibleName cmd_user_visible_name = 26; + RespSetUserVisibleName resp_user_visible_name = 27; + CmdSetVolume cmd_set_volume = 28; + RespSetVolume resp_set_volume = 29; } -} \ No newline at end of file +} diff --git a/proto/wifi_scan.proto b/proto/wifi_scan.proto index 3a61131..d42b1a0 100644 --- a/proto/wifi_scan.proto +++ b/proto/wifi_scan.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package espressif; import "constants.proto"; +import "wifi_constants.proto"; message CmdScanStart { bool blocking = 1; @@ -30,6 +31,8 @@ message WiFiScanResult { bytes ssid = 1; uint32 channel = 2; int32 rssi = 3; + bytes bssid = 4; + WifiAuthMode auth = 5; } message RespScanResult { @@ -57,4 +60,3 @@ message WiFiScanPayload { RespScanResult resp_scan_result = 15; } } -