From 4ef5153b1e42aeb0f9c4736144e8ded9102d874d Mon Sep 17 00:00:00 2001 From: Mantosh Kumar <> Date: Wed, 24 Jul 2024 12:00:06 +0530 Subject: [PATCH] Release 24Q2-Patch-1 - BLE IOP test enhancements. - Wi-Fi Dev kit sensor. - Bug fixes. --- Podfile | 1 + Podfile.lock | 8 +- SiliconLabsApp.xcodeproj/project.pbxproj | 164 +- .../BluetoothControllers/SILCentralManager.m | 24 +- SiliconLabsApp/Categories/UIImage+SILImages.h | 2 + SiliconLabsApp/Categories/UIImage+SILImages.m | 3 + SiliconLabsApp/Models/SILApp.h | 3 +- SiliconLabsApp/Models/SILApp.m | 12 +- SiliconLabsApp/SILAppDelegate.swift | 14 + .../WifiSensoreColor/Contents.json | 6 + .../Contents.json | 20 + .../Images.xcassets/WifiSensore/Contents.json | 6 + .../WiFi_led_icon.imageset/Contents.json | 23 + .../WiFi_led_icon.imageset/Group 12923.png | Bin 0 -> 984 bytes .../WiFi_led_icon.imageset/Group 12923@2x.png | Bin 0 -> 1686 bytes .../WiFi_led_icon.imageset/Group 12923@3x.png | Bin 0 -> 2455 bytes .../WiFi_motion_icon.imageset/Contents.json | 23 + .../WiFi_motion_icon.imageset/Group 12922.png | Bin 0 -> 951 bytes .../Group 12922@2x.png | Bin 0 -> 1794 bytes .../Group 12922@3x.png | Bin 0 -> 2500 bytes .../WiFi_sensore_icon.imageset/Contents.json | 23 + .../wifi_icon 1.png | Bin 0 -> 8108 bytes .../wifi_icon 2.png | Bin 0 -> 8108 bytes .../WiFi_sensore_icon.imageset/wifi_icon.png | Bin 0 -> 8108 bytes .../blub_off_tint.imageset/Contents.json | 26 + .../graphic - light_demo - light - off.png | Bin 0 -> 8071 bytes .../graphic - light_demo - light - off@2x.png | Bin 0 -> 19105 bytes .../graphic - light_demo - light - off@3x.png | Bin 0 -> 33040 bytes .../bulb_blue.imageset/Contents.json | 23 + .../bulb_blue.imageset/lightbulb 1.png | Bin 0 -> 2360 bytes .../bulb_blue.imageset/lightbulb 2.png | Bin 0 -> 2360 bytes .../bulb_blue.imageset/lightbulb.png | Bin 0 -> 2360 bytes .../bulb_cyan.imageset/Contents.json | 23 + .../bulb_cyan.imageset/lightbulb (1) 1.png | Bin 0 -> 2055 bytes .../bulb_cyan.imageset/lightbulb (1) 2.png | Bin 0 -> 2055 bytes .../bulb_cyan.imageset/lightbulb (1).png | Bin 0 -> 2055 bytes .../bulb_green.imageset/Contents.json | 23 + .../bulb_green.imageset/lightbulb 1.png | Bin 0 -> 2409 bytes .../bulb_green.imageset/lightbulb 2.png | Bin 0 -> 2409 bytes .../bulb_green.imageset/lightbulb.png | Bin 0 -> 2409 bytes .../bulb_magenta.imageset/Contents.json | 23 + .../bulb_magenta.imageset/lightbulb 1.png | Bin 0 -> 1562 bytes .../bulb_magenta.imageset/lightbulb 2.png | Bin 0 -> 1562 bytes .../bulb_magenta.imageset/lightbulb.png | Bin 0 -> 1562 bytes .../bulb_off.imageset/Contents.json | 26 + .../bulb_off.imageset/lightbulb (3) 1.png | Bin 0 -> 2510 bytes .../bulb_off.imageset/lightbulb (3) 2.png | Bin 0 -> 2510 bytes .../bulb_off.imageset/lightbulb (3).png | Bin 0 -> 2510 bytes .../bulb_on.imageset/Contents.json | 23 + .../bulb_on.imageset/lightbulb (2) 1.png | Bin 0 -> 2448 bytes .../bulb_on.imageset/lightbulb (2) 2.png | Bin 0 -> 2448 bytes .../bulb_on.imageset/lightbulb (2).png | Bin 0 -> 2448 bytes .../bulb_red.imageset/Contents.json | 23 + .../bulb_red.imageset/lightbulb 1.png | Bin 0 -> 1749 bytes .../bulb_red.imageset/lightbulb 2.png | Bin 0 -> 1749 bytes .../bulb_red.imageset/lightbulb.png | Bin 0 -> 1749 bytes .../bulb_yellow.imageset/Contents.json | 23 + .../bulb_yellow.imageset/lightbulb 1.png | Bin 0 -> 1561 bytes .../bulb_yellow.imageset/lightbulb 2.png | Bin 0 -> 1561 bytes .../bulb_yellow.imageset/lightbulb.png | Bin 0 -> 1561 bytes .../SILAppSelectionCollectionViewCell.swift | 8 +- .../SILAppSelectionViewController.swift | 7 + .../Helpers/SILIOPTestReconnectManager.swift | 2 + .../Helpers/SILIOPTesterCentralManager.swift | 36 + .../Helpers/SILIopTestOTAUpdateManger.swift | 15 + .../IOP Test App/Log/DDLogFileInfo+Ext.swift | 55 + .../IOP Test App/Log/IOPLog.swift | 18 + .../IOP Test App/Log/IOPLogFilePrinter.swift | 174 +++ .../IOP Test App/Log/LogFile.swift | 17 + .../Test_1/TestCases/SILScanTestCase.swift | 5 + .../TestCases/SILConnectDeviceTestCase.swift | 8 + .../TestCases/SILDiscoverGATTTestCase.swift | 2 + .../Test_4/SILIOPTester_Test4.swift | 8 + .../TestHelpers/SILDiscoverFirmwareInfo.swift | 2 + .../SILDiscoverTestConnectionParameters.swift | 2 + .../SILIOPConstCharacteristicTestHelper.swift | 1 + .../SILIOPGATTNotificationTestHelper.swift | 4 + .../SILIOPGATTOperationsTestHelper.swift | 7 + .../SILIOPLengthVariableTestHelper.swift | 2 + ...ILIOPUserLenCharacteristicTestHelper.swift | 2 + .../Test_5/TestCases/SILOTAAckTestCase.swift | 29 +- .../TestCases/SILOTANonAckTestCase.swift | 38 +- .../TestCases/SILThroughputTestCase.swift | 15 + .../Test_8/SILIOPSecurityTestHelper.swift | 49 + .../Test_8/SILIOPTester_Test8.swift | 2 +- .../Test_9/SILIOPLEPrivacyHealper.swift | 48 + ...OPDeviceResetInfoPopupViewController.swift | 22 + ...LIOPDeviceResetInfoPopupViewController.xib | 120 ++ .../UI/SILIOPTesterViewController.swift | 83 +- .../ViewModel/SILIOPTesterViewModel.swift | 76 +- .../WiFi_Sensor/APIService/APIRequest.swift | 79 + .../Controller/SILWiFiLEDViewController.swift | 441 ++++++ .../Controller/SILWiFiMotionVC.swift | 311 ++++ .../Controller/SILWiFiMotionVcCh.swift | 101 ++ .../SILWiFiMotionViewController.swift | 102 ++ .../Controller/SILWifiSensorsHomeView.swift | 256 ++++ .../Controller/WiFiMotionDemoConnection.swift | 145 ++ .../Model/SILAllWiFiSensorModel.swift | 40 + .../WiFi_Sensor/Model/SILWiFiLEDModel.swift | 44 + .../WiFi_Sensor/View/Cell/SILSensorCell.swift | 69 + .../WiFi_Sensor/View/Cell/SILSensorCell.xib | 93 ++ .../View/SILWifiSensors.storyboard | 1317 +++++++++++++++++ .../SILWiFiLedSensorsViewModel.swift | 97 ++ .../SILWiFiMotionSensorsViewModel.swift | 121 ++ .../ViewModel/SILWiFiSensorsViewModel.swift | 311 ++++ 105 files changed, 4881 insertions(+), 48 deletions(-) create mode 100644 SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/sil_regularGreenColor.colorset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Group 12923.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Group 12923@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Group 12923@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_sensore_icon.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_sensore_icon.imageset/wifi_icon 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_sensore_icon.imageset/wifi_icon 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_sensore_icon.imageset/wifi_icon.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/graphic - light_demo - light - off.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/graphic - light_demo - light - off@2x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/graphic - light_demo - light - off@3x.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/lightbulb 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/lightbulb 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/lightbulb.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1) 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1) 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1).png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/lightbulb (3) 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/lightbulb (3) 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/lightbulb (3).png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_on.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_on.imageset/lightbulb (2) 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_on.imageset/lightbulb (2) 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_on.imageset/lightbulb (2).png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_red.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_red.imageset/lightbulb 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_red.imageset/lightbulb 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_red.imageset/lightbulb.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/Contents.json create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb 1.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb 2.png create mode 100644 SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb.png create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/Log/DDLogFileInfo+Ext.swift create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLog.swift create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLogFilePrinter.swift create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/Log/LogFile.swift create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.xib create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/APIService/APIRequest.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiLEDViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVC.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVcCh.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionViewController.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWifiSensorsHomeView.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/WiFiMotionDemoConnection.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILAllWiFiSensorModel.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILWiFiLEDModel.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.xib create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/View/SILWifiSensors.storyboard create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiLedSensorsViewModel.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiMotionSensorsViewModel.swift create mode 100644 SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiSensorsViewModel.swift diff --git a/Podfile b/Podfile index f327d8f7..5e35b845 100644 --- a/Podfile +++ b/Podfile @@ -24,6 +24,7 @@ def shared_pods pod 'RxSwift', '~> 6.2.0' pod 'RxCocoa', '~> 6.2.0' pod 'Introspect' + pod 'CocoaLumberjack/Swift' end def test_pods diff --git a/Podfile.lock b/Podfile.lock index b66d5c9b..d64664de 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -8,6 +8,9 @@ PODS: - Charts/Core (= 4.1.0) - Charts/Core (4.1.0): - SwiftAlgorithms (~> 1.0) + - CocoaLumberjack/Core (3.8.2) + - CocoaLumberjack/Swift (3.8.2): + - CocoaLumberjack/Core - Crashlytics (3.12.0): - Fabric (~> 1.9.0) - Fabric (1.9.0) @@ -41,6 +44,7 @@ DEPENDENCIES: - AEXML - ChameleonFramework (~> 2.1.0) - Charts (~> 4.1.0) + - CocoaLumberjack/Swift - Crashlytics (~> 3.12.0) - Fabric - Introspect @@ -66,6 +70,7 @@ SPEC REPOS: - AEXML - ChameleonFramework - Charts + - CocoaLumberjack - Crashlytics - Fabric - Introspect @@ -92,6 +97,7 @@ SPEC CHECKSUMS: AEXML: 1e255ecc6597212f97a7454a69ebd3ede64ac1cf ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64 Charts: ce0768268078eee0336f122c3c4ca248e4e204c5 + CocoaLumberjack: f8d89a516e7710fdb2e9b8f1560b16ec6040eef0 Crashlytics: a33af323773f73904037dc2e684cd2f0d29f4fe2 Fabric: 09ef2d9b99b104702bede1acaf469fb8f20a9146 Introspect: b62c4dd2063072327c21d618ef2bedc3c87bc366 @@ -113,6 +119,6 @@ SPEC CHECKSUMS: UICircularProgressRing: 19927375b2b21b5fa5fd9582f15ccdef9659da16 XMLDictionary: fa07b6ff422b3a91d47a5de9bc82e3fc04fbd167 -PODFILE CHECKSUM: 90b577a05167576a7f2ac952330395b6f30605e3 +PODFILE CHECKSUM: 99c5e34410dda7fcf73bc0c4eccd032787e4da96 COCOAPODS: 1.12.1 diff --git a/SiliconLabsApp.xcodeproj/project.pbxproj b/SiliconLabsApp.xcodeproj/project.pbxproj index aced54de..5b704523 100644 --- a/SiliconLabsApp.xcodeproj/project.pbxproj +++ b/SiliconLabsApp.xcodeproj/project.pbxproj @@ -873,6 +873,12 @@ 2051328B270365A800C27B92 /* DispatchBlockHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205131DB270365A800C27B92 /* DispatchBlockHelpers.swift */; }; 2051328C270365A800C27B92 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 205131DC270365A800C27B92 /* SettingsViewController.swift */; }; 2B257CB817ADC2635883B007 /* Pods_WirelessGecko.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E7E1967C516A49F37A0A7B8 /* Pods_WirelessGecko.framework */; }; + 2E4854CE2C4146050096699B /* DDLogFileInfo+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4854C92C4146050096699B /* DDLogFileInfo+Ext.swift */; }; + 2E4854CF2C4146050096699B /* IOPLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4854CA2C4146050096699B /* IOPLog.swift */; }; + 2E4854D02C4146050096699B /* IOPLogFilePrinter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4854CB2C4146050096699B /* IOPLogFilePrinter.swift */; }; + 2E4854D12C4146050096699B /* LogFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4854CC2C4146050096699B /* LogFile.swift */; }; + 2E4854D32C414B5D0096699B /* SILIOPDeviceResetInfoPopupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2E4854D22C414B5D0096699B /* SILIOPDeviceResetInfoPopupViewController.xib */; }; + 2E4854D52C414B640096699B /* SILIOPDeviceResetInfoPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4854D42C414B640096699B /* SILIOPDeviceResetInfoPopupViewController.swift */; }; 2E8E6E782B8C6CEA00906697 /* SILWifiOTAConfigViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2E8E6E762B8C6CEA00906697 /* SILWifiOTAConfigViewController.xib */; }; 2E8E6E792B8C6CEA00906697 /* SILWifiOTAConfigViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8E6E772B8C6CEA00906697 /* SILWifiOTAConfigViewController.swift */; }; 2E8E6E7D2B8C9B5700906697 /* TextField_Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8E6E7C2B8C9B5700906697 /* TextField_Util.swift */; }; @@ -895,6 +901,21 @@ 4320C6BF2ADD210D00A6D67A /* OccupancySensorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4320C6BE2ADD210D00A6D67A /* OccupancySensorViewController.m */; }; 4320C6C22ADD214800A6D67A /* ContactSensorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4320C6C12ADD214800A6D67A /* ContactSensorViewController.m */; }; 4320C6C82ADD381800A6D67A /* TemperatureSensorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4320C6C72ADD381800A6D67A /* TemperatureSensorController.m */; }; + 4388B2C72C2DA13E0068C950 /* SILWiFiLEDViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2C62C2DA13E0068C950 /* SILWiFiLEDViewController.swift */; }; + 4388B2C92C2DBC160068C950 /* SILWiFiLedSensorsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2C82C2DBC160068C950 /* SILWiFiLedSensorsViewModel.swift */; }; + 4388B2CB2C2DC14B0068C950 /* SILWiFiMotionSensorsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2CA2C2DC14B0068C950 /* SILWiFiMotionSensorsViewModel.swift */; }; + 4388B2CD2C2DD9FE0068C950 /* SILWiFiLEDModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2CC2C2DD9FE0068C950 /* SILWiFiLEDModel.swift */; }; + 4388B2CF2C2E8A3C0068C950 /* SILWiFiMotionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2CE2C2E8A3C0068C950 /* SILWiFiMotionViewController.swift */; }; + 4388B2D12C30476E0068C950 /* SILWiFiMotionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2D02C30476E0068C950 /* SILWiFiMotionVC.swift */; }; + 4388B2D32C3047910068C950 /* SILWiFiMotionVcCh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2D22C3047910068C950 /* SILWiFiMotionVcCh.swift */; }; + 4388B2D52C305B0C0068C950 /* WiFiMotionDemoConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2D42C305B0C0068C950 /* WiFiMotionDemoConnection.swift */; }; + 4388B2E22C3FC9370068C950 /* SILAllWiFiSensorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4388B2E12C3FC9370068C950 /* SILAllWiFiSensorModel.swift */; }; + 439F2C792C2C18BF00504A12 /* APIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439F2C782C2C18BF00504A12 /* APIRequest.swift */; }; + 439F2C7F2C2C242E00504A12 /* SILWifiSensorsHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439F2C7D2C2C242E00504A12 /* SILWifiSensorsHomeView.swift */; }; + 439F2C852C2C243600504A12 /* SILSensorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439F2C802C2C243600504A12 /* SILSensorCell.swift */; }; + 439F2C862C2C243600504A12 /* SILSensorCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 439F2C812C2C243600504A12 /* SILSensorCell.xib */; }; + 439F2C872C2C243600504A12 /* SILWifiSensors.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 439F2C832C2C243600504A12 /* SILWifiSensors.storyboard */; }; + 439F2C892C2C2B2C00504A12 /* SILWiFiSensorsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439F2C882C2C2B2C00504A12 /* SILWiFiSensorsViewModel.swift */; }; 43D819C92B8CC64C006EB860 /* SILSecurity_7_5TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D819C82B8CC64C006EB860 /* SILSecurity_7_5TestCase.swift */; }; 43D819CC2B91C4A3006EB860 /* SILIOPTester_Test9.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D819CB2B91C4A3006EB860 /* SILIOPTester_Test9.swift */; }; 43D819CF2B91C610006EB860 /* SILLEPrivacy_7_6TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D819CE2B91C610006EB860 /* SILLEPrivacy_7_6TestCase.swift */; }; @@ -1870,6 +1891,12 @@ 205131DB270365A800C27B92 /* DispatchBlockHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchBlockHelpers.swift; sourceTree = ""; }; 205131DC270365A800C27B92 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 257E756EE6E82EC7E361629D /* Pods-SiliconLabsApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsApp/Pods-SiliconLabsApp.debug.xcconfig"; sourceTree = ""; }; + 2E4854C92C4146050096699B /* DDLogFileInfo+Ext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DDLogFileInfo+Ext.swift"; sourceTree = ""; }; + 2E4854CA2C4146050096699B /* IOPLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOPLog.swift; sourceTree = ""; }; + 2E4854CB2C4146050096699B /* IOPLogFilePrinter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IOPLogFilePrinter.swift; sourceTree = ""; }; + 2E4854CC2C4146050096699B /* LogFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFile.swift; sourceTree = ""; }; + 2E4854D22C414B5D0096699B /* SILIOPDeviceResetInfoPopupViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILIOPDeviceResetInfoPopupViewController.xib; sourceTree = ""; }; + 2E4854D42C414B640096699B /* SILIOPDeviceResetInfoPopupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILIOPDeviceResetInfoPopupViewController.swift; sourceTree = ""; }; 2E8E6E762B8C6CEA00906697 /* SILWifiOTAConfigViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILWifiOTAConfigViewController.xib; sourceTree = ""; }; 2E8E6E772B8C6CEA00906697 /* SILWifiOTAConfigViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILWifiOTAConfigViewController.swift; sourceTree = ""; }; 2E8E6E7C2B8C9B5700906697 /* TextField_Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField_Util.swift; sourceTree = ""; }; @@ -1903,6 +1930,21 @@ 4320C6C12ADD214800A6D67A /* ContactSensorViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContactSensorViewController.m; sourceTree = ""; }; 4320C6C62ADD381800A6D67A /* TemperatureSensorController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TemperatureSensorController.h; sourceTree = ""; }; 4320C6C72ADD381800A6D67A /* TemperatureSensorController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TemperatureSensorController.m; sourceTree = ""; }; + 4388B2C62C2DA13E0068C950 /* SILWiFiLEDViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiLEDViewController.swift; sourceTree = ""; }; + 4388B2C82C2DBC160068C950 /* SILWiFiLedSensorsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiLedSensorsViewModel.swift; sourceTree = ""; }; + 4388B2CA2C2DC14B0068C950 /* SILWiFiMotionSensorsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiMotionSensorsViewModel.swift; sourceTree = ""; }; + 4388B2CC2C2DD9FE0068C950 /* SILWiFiLEDModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiLEDModel.swift; sourceTree = ""; }; + 4388B2CE2C2E8A3C0068C950 /* SILWiFiMotionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiMotionViewController.swift; sourceTree = ""; }; + 4388B2D02C30476E0068C950 /* SILWiFiMotionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiMotionVC.swift; sourceTree = ""; }; + 4388B2D22C3047910068C950 /* SILWiFiMotionVcCh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiMotionVcCh.swift; sourceTree = ""; }; + 4388B2D42C305B0C0068C950 /* WiFiMotionDemoConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiFiMotionDemoConnection.swift; sourceTree = ""; }; + 4388B2E12C3FC9370068C950 /* SILAllWiFiSensorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILAllWiFiSensorModel.swift; sourceTree = ""; }; + 439F2C782C2C18BF00504A12 /* APIRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIRequest.swift; sourceTree = ""; }; + 439F2C7D2C2C242E00504A12 /* SILWifiSensorsHomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILWifiSensorsHomeView.swift; sourceTree = ""; }; + 439F2C802C2C243600504A12 /* SILSensorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILSensorCell.swift; sourceTree = ""; }; + 439F2C812C2C243600504A12 /* SILSensorCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILSensorCell.xib; sourceTree = ""; }; + 439F2C832C2C243600504A12 /* SILWifiSensors.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SILWifiSensors.storyboard; sourceTree = ""; }; + 439F2C882C2C2B2C00504A12 /* SILWiFiSensorsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILWiFiSensorsViewModel.swift; sourceTree = ""; }; 43D819C82B8CC64C006EB860 /* SILSecurity_7_5TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILSecurity_7_5TestCase.swift; sourceTree = ""; }; 43D819CB2B91C4A3006EB860 /* SILIOPTester_Test9.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILIOPTester_Test9.swift; sourceTree = ""; }; 43D819CE2B91C610006EB860 /* SILLEPrivacy_7_6TestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILLEPrivacy_7_6TestCase.swift; sourceTree = ""; }; @@ -3314,6 +3356,7 @@ 1E4D8F4326035FE000924430 /* IOP Test App */ = { isa = PBXGroup; children = ( + 2E4854CD2C4146050096699B /* Log */, 1E5FEAA4261F37BD00D00E5F /* UI */, 1E5FEAAA261F3A2800D00E5F /* ViewModel */, 1EC1F199260CE1EE00508552 /* TestScenario */, @@ -3547,6 +3590,8 @@ isa = PBXGroup; children = ( 80DBE36E282E4B0D00F7C579 /* SILIOPInfoPopup.swift */, + 2E4854D42C414B640096699B /* SILIOPDeviceResetInfoPopupViewController.swift */, + 2E4854D22C414B5D0096699B /* SILIOPDeviceResetInfoPopupViewController.xib */, 80DBE36D282E4B0D00F7C579 /* SILIOPInfoPopup.xib */, 1E4D8F4726035FE000924430 /* SILIOPTesterViewController.swift */, 1E4D8F4826035FE000924430 /* SILIOPTestScenarioCellView.swift */, @@ -4351,6 +4396,17 @@ path = "React-Sense"; sourceTree = ""; }; + 2E4854CD2C4146050096699B /* Log */ = { + isa = PBXGroup; + children = ( + 2E4854C92C4146050096699B /* DDLogFileInfo+Ext.swift */, + 2E4854CA2C4146050096699B /* IOPLog.swift */, + 2E4854CB2C4146050096699B /* IOPLogFilePrinter.swift */, + 2E4854CC2C4146050096699B /* LogFile.swift */, + ); + path = Log; + sourceTree = ""; + }; 2E8E6E7A2B8C7FFD00906697 /* WiFi_OTA */ = { isa = PBXGroup; children = ( @@ -4410,6 +4466,76 @@ path = stylekit; sourceTree = ""; }; + 439F2C742C2C178000504A12 /* APIService */ = { + isa = PBXGroup; + children = ( + 439F2C782C2C18BF00504A12 /* APIRequest.swift */, + ); + path = APIService; + sourceTree = ""; + }; + 439F2C762C2C17D800504A12 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 439F2C882C2C2B2C00504A12 /* SILWiFiSensorsViewModel.swift */, + 4388B2C82C2DBC160068C950 /* SILWiFiLedSensorsViewModel.swift */, + 4388B2CA2C2DC14B0068C950 /* SILWiFiMotionSensorsViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 439F2C772C2C17EC00504A12 /* Model */ = { + isa = PBXGroup; + children = ( + 4388B2CC2C2DD9FE0068C950 /* SILWiFiLEDModel.swift */, + 4388B2E12C3FC9370068C950 /* SILAllWiFiSensorModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 439F2C7C2C2C23C100504A12 /* WiFi_Sensor */ = { + isa = PBXGroup; + children = ( + 439F2C842C2C243600504A12 /* View */, + 439F2C7E2C2C242E00504A12 /* Controller */, + 439F2C772C2C17EC00504A12 /* Model */, + 439F2C762C2C17D800504A12 /* ViewModel */, + 439F2C742C2C178000504A12 /* APIService */, + ); + path = WiFi_Sensor; + sourceTree = ""; + }; + 439F2C7E2C2C242E00504A12 /* Controller */ = { + isa = PBXGroup; + children = ( + 439F2C7D2C2C242E00504A12 /* SILWifiSensorsHomeView.swift */, + 4388B2C62C2DA13E0068C950 /* SILWiFiLEDViewController.swift */, + 4388B2CE2C2E8A3C0068C950 /* SILWiFiMotionViewController.swift */, + 4388B2D02C30476E0068C950 /* SILWiFiMotionVC.swift */, + 4388B2D22C3047910068C950 /* SILWiFiMotionVcCh.swift */, + 4388B2D42C305B0C0068C950 /* WiFiMotionDemoConnection.swift */, + ); + path = Controller; + sourceTree = ""; + }; + 439F2C822C2C243600504A12 /* Cell */ = { + isa = PBXGroup; + children = ( + 439F2C802C2C243600504A12 /* SILSensorCell.swift */, + 439F2C812C2C243600504A12 /* SILSensorCell.xib */, + ); + path = Cell; + sourceTree = ""; + }; + 439F2C842C2C243600504A12 /* View */ = { + isa = PBXGroup; + children = ( + 439F2C822C2C243600504A12 /* Cell */, + 439F2C832C2C243600504A12 /* SILWifiSensors.storyboard */, + ); + path = View; + sourceTree = ""; + }; 43D819CA2B91C379006EB860 /* Test_9 */ = { isa = PBXGroup; children = ( @@ -4949,6 +5075,7 @@ E621B33D1A65562400223C5A /* ViewControllers */ = { isa = PBXGroup; children = ( + 439F2C7C2C2C23C100504A12 /* WiFi_Sensor */, 2E8E6E7A2B8C7FFD00906697 /* WiFi_OTA */, 72E78A482A97819A003F05F7 /* MatterDemo */, 1E0C126F298BE6E500BCADFC /* ESLDemo */, @@ -5484,6 +5611,7 @@ 0C2FCB9F1F9A542300F4F259 /* SILBeaconRegistryEntryCell.xib in Resources */, 20513202270365A800C27B92 /* SettingsViewController.storyboard in Resources */, 1E26EC45254B1BFC002FFAAB /* SILDebugCharacteristicEncodingFieldTableViewCell.xib in Resources */, + 439F2C872C2C243600504A12 /* SILWifiSensors.storyboard in Resources */, 1EEFB215252218DD00DD2DD7 /* SILCharacteristicWriteFieldTableViewCell.xib in Resources */, 20513228270365A800C27B92 /* TBSense_Rev_Lowpoly_2.mtl in Resources */, 0C2FCBA01F9A542300F4F259 /* SILDebugDeviceTableViewCell~iphone.xib in Resources */, @@ -5505,6 +5633,7 @@ 0C2FCBA61F9A542300F4F259 /* SILOTAHUDView.xib in Resources */, 80DBE36F282E4B0D00F7C579 /* SILIOPInfoPopup.xib in Resources */, 80671C38271EE2A4009B6284 /* BRD4184A_LowPoly.obj in Resources */, + 2E4854D32C414B5D0096699B /* SILIOPDeviceResetInfoPopupViewController.xib in Resources */, 0C2FCBA71F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */, 0F34409620AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */, 0C2FCBA81F9A542300F4F259 /* SILActivityBarViewController.xib in Resources */, @@ -5525,6 +5654,7 @@ 4C9EAB5924042FA90023FFB1 /* Roboto-Black.ttf in Resources */, 1EEE315723F57CED0076E731 /* SILBeaconTypeTableViewCell.xib in Resources */, 1E145AEF266E5EA1009792C2 /* SILWarningViewController.xib in Resources */, + 439F2C862C2C243600504A12 /* SILSensorCell.xib in Resources */, 1E4DB3AE266A720600405BD2 /* SILCreateGattCharacteristicViewController.xib in Resources */, 80671C3F271EE2A4009B6284 /* BRD4184A_LowPoly.dae in Resources */, 807B139B2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib in Resources */, @@ -5722,6 +5852,7 @@ "${BUILT_PRODUCTS_DIR}/ActionSheetPicker-3.0/ActionSheetPicker_3_0.framework", "${BUILT_PRODUCTS_DIR}/ChameleonFramework/ChameleonFramework.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/IP-UIKit-Wisdom/IP_UIKit_Wisdom.framework", "${BUILT_PRODUCTS_DIR}/Introspect/Introspect.framework", "${BUILT_PRODUCTS_DIR}/KVOController/KVOController.framework", @@ -5747,6 +5878,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionSheetPicker_3_0.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChameleonFramework.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IP_UIKit_Wisdom.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Introspect.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KVOController.framework", @@ -5800,6 +5932,7 @@ "${BUILT_PRODUCTS_DIR}/ActionSheetPicker-3.0/ActionSheetPicker_3_0.framework", "${BUILT_PRODUCTS_DIR}/ChameleonFramework/ChameleonFramework.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/IP-UIKit-Wisdom/IP_UIKit_Wisdom.framework", "${BUILT_PRODUCTS_DIR}/Introspect/Introspect.framework", "${BUILT_PRODUCTS_DIR}/KVOController/KVOController.framework", @@ -5821,6 +5954,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionSheetPicker_3_0.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChameleonFramework.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IP_UIKit_Wisdom.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Introspect.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KVOController.framework", @@ -5883,6 +6017,7 @@ "${BUILT_PRODUCTS_DIR}/ActionSheetPicker-3.0/ActionSheetPicker_3_0.framework", "${BUILT_PRODUCTS_DIR}/ChameleonFramework/ChameleonFramework.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/IP-UIKit-Wisdom/IP_UIKit_Wisdom.framework", "${BUILT_PRODUCTS_DIR}/Introspect/Introspect.framework", "${BUILT_PRODUCTS_DIR}/KVOController/KVOController.framework", @@ -5904,6 +6039,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionSheetPicker_3_0.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChameleonFramework.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IP_UIKit_Wisdom.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Introspect.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KVOController.framework", @@ -5935,6 +6071,7 @@ "${BUILT_PRODUCTS_DIR}/ActionSheetPicker-3.0/ActionSheetPicker_3_0.framework", "${BUILT_PRODUCTS_DIR}/ChameleonFramework/ChameleonFramework.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/IP-UIKit-Wisdom/IP_UIKit_Wisdom.framework", "${BUILT_PRODUCTS_DIR}/Introspect/Introspect.framework", "${BUILT_PRODUCTS_DIR}/KVOController/KVOController.framework", @@ -5956,6 +6093,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ActionSheetPicker_3_0.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChameleonFramework.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IP_UIKit_Wisdom.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Introspect.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KVOController.framework", @@ -6043,6 +6181,7 @@ 205131EA270365A800C27B92 /* ApplicationConfig.swift in Sources */, 1EC1F2D6260CF44900508552 /* SILIOPTester_Test3.swift in Sources */, 1E56B233263702C7004F3EA6 /* SILIOPGATTOperationsTestHelper.swift in Sources */, + 4388B2E22C3FC9370068C950 /* SILAllWiFiSensorModel.swift in Sources */, 1EEE315D23F58C210076E731 /* SILBrowserFilterBeaconTypeViewController.m in Sources */, 2051326B270365A800C27B92 /* BleMotionDemoConnection.swift in Sources */, 1E1907B123FD850700DD014C /* SILSavedSearchesRealmModel.m in Sources */, @@ -6053,6 +6192,7 @@ 2FD579DD2579352C001D7E9E /* UIViewController+SILContext.swift in Sources */, 8044307D270F008200DD3EA6 /* SILIOPTestReconnectManager.swift in Sources */, 1E26EC99255019E0002FFAAB /* SILAdvertiserDetailsWireframe.swift in Sources */, + 4388B2CB2C2DC14B0068C950 /* SILWiFiMotionSensorsViewModel.swift in Sources */, 1E973CF22672693400B5FC71 /* SILAppTypeBlinkyViewController.swift in Sources */, 20513288270365A800C27B92 /* EnvironmentDemoCollectionViewDataSource.swift in Sources */, 0C2FCB031F9A542300F4F259 /* SILBluetoothFieldModel.m in Sources */, @@ -6149,6 +6289,7 @@ FE7B8FDF292E4FBD0075D894 /* SILIOPTestDeviceSelectorController.swift in Sources */, 1EC1F282260CECAF00508552 /* SILThroughputTestCase.swift in Sources */, 80DBE370282E4B0D00F7C579 /* SILIOPInfoPopup.swift in Sources */, + 2E4854D02C4146050096699B /* IOPLogFilePrinter.swift in Sources */, 0C2FCB1B1F9A542300F4F259 /* DebugDeviceFilterViewController.swift in Sources */, FEDB768A286D9CC3004FC2DC /* ViewWithFloatingButton.swift in Sources */, 1E51563D26B81B9300CCB2CA /* SILGattProjectMarker.swift in Sources */, @@ -6291,6 +6432,7 @@ 1EFC766226AEDF770035594E /* SILGattConfigurationCharacteristicEntity+SILGattXMLExportable.swift in Sources */, 1E4D8F6026035FE000924430 /* SILIopTestOTAUpdateManger.swift in Sources */, 1E26EC54255019A1002FFAAB /* SILAdvertiserRemoveSetting.swift in Sources */, + 4388B2CD2C2DD9FE0068C950 /* SILWiFiLEDModel.swift in Sources */, 1E26ECC025501A0A002FFAAB /* SILAdvertisingDataViewModelBuilder.swift in Sources */, 2E8E6EC62B8E297100906697 /* ProgressStyleKit.swift in Sources */, 2051326A270365A800C27B92 /* SwitchView.swift in Sources */, @@ -6301,12 +6443,14 @@ 0C2FCB3C1F9A542300F4F259 /* SILHeartRateMeasurement.m in Sources */, 1E4DB384266A71E000405BD2 /* SILBrowserDetailsTabBar.swift in Sources */, 1EFC76BE26AEE02A0035594E /* SILThroughputViewModel.swift in Sources */, + 4388B2C72C2DA13E0068C950 /* SILWiFiLEDViewController.swift in Sources */, 0C2FCB3D1F9A542300F4F259 /* SILApp+AttributedProfiles.m in Sources */, 0C2FCB3E1F9A542300F4F259 /* SILOTAFirmwareFile.m in Sources */, 1E4DB342266A711700405BD2 /* SILBluetoothServiceDescriptorModel.swift in Sources */, 1E90F5E42473E73E0013AABD /* SILDebugServicesMenuViewController.swift in Sources */, 1EC1F1DB260CE95900508552 /* SILDiscoverFirmwareInfo.swift in Sources */, 72CA90002ABCBBD700FE70BA /* MatterHomeViewController.m in Sources */, + 4388B2D52C305B0C0068C950 /* WiFiMotionDemoConnection.swift in Sources */, 2F0DE91D258B7F4D00BEFF76 /* SILContextMenu.swift in Sources */, FE417EB22893E4A0003FDDD0 /* CharacteristicFieldValueConverterError.swift in Sources */, 1EFC76AA26AEDFA40035594E /* SILCreateGattDescriptorViewModel.swift in Sources */, @@ -6331,6 +6475,7 @@ 0C2FCB471F9A542300F4F259 /* CBService+Categories.m in Sources */, 80B5FF9F275F912F008F08A8 /* SILWifiCommissioningConnectedAPCellView.swift in Sources */, 43D819CF2B91C610006EB860 /* SILLEPrivacy_7_6TestCase.swift in Sources */, + 2E4854CF2C4146050096699B /* IOPLog.swift in Sources */, 1E26EC85255019E0002FFAAB /* SILAdvertiserCellView.swift in Sources */, 13ACBA87256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */, 1E26EC9C255019E0002FFAAB /* SILAdvertiserAdd16BitServiceDialogViewController.swift in Sources */, @@ -6374,6 +6519,7 @@ 0C2FCB4D1F9A542300F4F259 /* SILKeyValueViewModel.m in Sources */, 80C67059255169950083D20C /* SILRadioButton.swift in Sources */, 1E4DB343266A711700405BD2 /* SILBluetoothServiceCharacteristicModel.swift in Sources */, + 439F2C7F2C2C242E00504A12 /* SILWifiSensorsHomeView.swift in Sources */, 2E8E6E9B2B8CAAD500906697 /* GCDAsyncUdpSocket.m in Sources */, 1EE26CB829CDBCE1006417D1 /* SILESLCommandDelete.swift in Sources */, 1EFC770D26AEE1D60035594E /* SILCharacteristicWriteCellViewModels.swift in Sources */, @@ -6422,6 +6568,7 @@ 20513273270365A800C27B92 /* DeviceTransportDelegate.swift in Sources */, 1E48A1E92484E27300C188C0 /* SILAnimatedUIButton.swift in Sources */, 72CA8FCC2ABCBB7B00FE70BA /* FabricKeys.m in Sources */, + 439F2C892C2C2B2C00504A12 /* SILWiFiSensorsViewModel.swift in Sources */, 80B5FF88275F911E008F08A8 /* SILTimeoutTimer.swift in Sources */, 0C08FD5820CB1CA90016CABC /* SILConnectedLightingViewController.m in Sources */, 1EFC770C26AEE1D60035594E /* SILCharacteristicWriteViewModel.swift in Sources */, @@ -6438,6 +6585,7 @@ 205131EC270365A800C27B92 /* DeviceConnection.swift in Sources */, 1E1457F5266E0555009792C2 /* SILThroughputPeripheral.swift in Sources */, 2051326C270365A800C27B92 /* DemoConnection.swift in Sources */, + 4388B2D12C30476E0068C950 /* SILWiFiMotionVC.swift in Sources */, 4C4F2DE124080E50005D43BB /* SILRoundedButton.swift in Sources */, 1E36E8B729D082C00011BA0D /* SILESLDemoTagCell.swift in Sources */, 20513254270365A800C27B92 /* ButtonSpinner.swift in Sources */, @@ -6467,6 +6615,7 @@ 0C2FCB5C1F9A542300F4F259 /* SILOTASetupViewController.m in Sources */, 1E7CD33A260DDE740022FE80 /* SILIOPTesterCentralManager.swift in Sources */, 80AF191E2677A068002A58DB /* SILGattConfiguratorCheckBoxCellView.swift in Sources */, + 439F2C852C2C243600504A12 /* SILSensorCell.swift in Sources */, 1EFC766126AEDF770035594E /* SILGattConfigurationProperty+SILGattXMLExportable.swift in Sources */, 1EEFB22A2524B45000DD2DD7 /* SILCharacteristicWriteEnumOptionsTableViewCell.swift in Sources */, 1EFC766E26AEDF770035594E /* SILGattXmlParser.swift in Sources */, @@ -6524,6 +6673,7 @@ 1E56E9EB2416974500D5B92A /* SILStoryboard+Constants.m in Sources */, 2051323D270365A800C27B92 /* NSDataExtensions.swift in Sources */, 0C2FCB671F9A542300F4F259 /* SILConstants.m in Sources */, + 4388B2D32C3047910068C950 /* SILWiFiMotionVcCh.swift in Sources */, 1EABF868251B7511006358C8 /* SILCharacteristicWriteViewController.swift in Sources */, 4C3BA289240D58DF00CF3268 /* SILKeychainViewController.swift in Sources */, 1EFC766D26AEDF770035594E /* SILGattXmlMarkerType.swift in Sources */, @@ -6547,6 +6697,7 @@ 2E8E6ECA2B8E297100906697 /* AnimationHelper.swift in Sources */, 0C2FCB6C1F9A542300F4F259 /* SILBluetoothXMLParser.m in Sources */, 4C9EAB4924042A7B0023FFB1 /* SILServiceCell.swift in Sources */, + 4388B2CF2C2E8A3C0068C950 /* SILWiFiMotionViewController.swift in Sources */, 0C2FCB701F9A542300F4F259 /* SILDebugHeaderView.m in Sources */, 4D9E26262212CA7000617DBA /* SILRangeTestBoardInfo.swift in Sources */, FE10E1A82965BBEC00D4D03B /* ScannerTabSettings.swift in Sources */, @@ -6575,6 +6726,7 @@ 1EE26CF529CF3F59006417D1 /* SILAddress.swift in Sources */, 205131F4270365A800C27B92 /* Logging.swift in Sources */, 1E2D9CAC23BA48D600816EC0 /* SILBluetoothBrowserViewController.m in Sources */, + 4388B2C92C2DBC160068C950 /* SILWiFiLedSensorsViewModel.swift in Sources */, 1E4DB3B0266A720600405BD2 /* SILGattConfiguratorCharacteristicCellView.swift in Sources */, 1E4D8F6A26035FE000924430 /* DateExtension.swift in Sources */, 0C2FCB791F9A542300F4F259 /* SILOTAHUDView.m in Sources */, @@ -6583,6 +6735,7 @@ 1EFC767026AEDF770035594E /* SILGattAssignedNumberEntity.swift in Sources */, 20513245270365A800C27B92 /* MotionDemoConnection.swift in Sources */, 0C2FCB7A1F9A542300F4F259 /* SILServiceTableModel.m in Sources */, + 2E4854CE2C4146050096699B /* DDLogFileInfo+Ext.swift in Sources */, 0C2FCB7B1F9A542300F4F259 /* SILBluetoothSearch.m in Sources */, 0C2FCB7D1F9A542300F4F259 /* SILBluetoothCharacteristicModel.m in Sources */, 1EC1F27B260CEC5D00508552 /* SILOTANonAckTestCase.swift in Sources */, @@ -6611,12 +6764,14 @@ 1E90F5EA2473EA650013AABD /* SILDebugServicesMenuViewControllerDelegate.swift in Sources */, 1EC1F28B260CED2400508552 /* SILSecurity_7_2TestCase.swift in Sources */, 1E4DB3B7266A720600405BD2 /* SILGattConfiguratorCharacteristicShadowCellView.swift in Sources */, + 2E4854D52C414B640096699B /* SILIOPDeviceResetInfoPopupViewController.swift in Sources */, 4C2CB435240E725C0079040D /* SILMap.swift in Sources */, FE36FC77286486F700A9CB41 /* TestView.swift in Sources */, 1E4DB3AA266A720600405BD2 /* SILGattConfiguratorHomeViewController.swift in Sources */, 80B5FFA2275F912F008F08A8 /* SILWifiCommissioningPasswordPopup.swift in Sources */, 1E26ECC125501A0A002FFAAB /* SILAdvertiserAdd16BitServiceDialogViewModel.swift in Sources */, 72EEF90E2BF883C0008E7EB4 /* NetworkMonitor.swift in Sources */, + 439F2C792C2C18BF00504A12 /* APIRequest.swift in Sources */, 0C2FCB891F9A542300F4F259 /* SILBluetoothBitModel.m in Sources */, 1E4DB377266A71AE00405BD2 /* SILAppSelectionViewController.swift in Sources */, 20513240270365A800C27B92 /* WYPopoverController+SILHelpers.m in Sources */, @@ -6638,6 +6793,7 @@ 0C2FCB8C1F9A542300F4F259 /* GradientView.swift in Sources */, 20513260270365A800C27B92 /* AsyncOperation.swift in Sources */, 80B5FF87275F911E008F08A8 /* SILWifiCommissioningPeripheralGATTDatabase.swift in Sources */, + 2E4854D12C4146050096699B /* LogFile.swift in Sources */, 0C2FCB8E1F9A542300F4F259 /* KZBehaviour.m in Sources */, FEA6B648295C6E3100D96645 /* SILBrowserPresenter.swift in Sources */, 0C2FCB8F1F9A542300F4F259 /* SILDescriptorTableModel.m in Sources */, @@ -7065,7 +7221,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 5; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 52444FG85C; DISPLAY_NAME = "Si Connect"; @@ -7083,7 +7239,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.9.0; + MARKETING_VERSION = 2.9.1; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -7107,7 +7263,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 5; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 52444FG85C; @@ -7125,7 +7281,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.9.0; + MARKETING_VERSION = 2.9.1; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "BlueGeckoDemoApp Distribution"; diff --git a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m index a7b8cb41..0470df5a 100644 --- a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m +++ b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m @@ -16,6 +16,7 @@ #import "SILWeakNotificationPair.h" #import "SILConstants.h" #import "NSString+SILBrowserNotifications.h" +#import "BlueGecko-Swift.h" #if ENABLE_HOMEKIT #import #endif @@ -58,7 +59,7 @@ @interface SILCentralManager () @property (nonatomic, strong) NSArray *regions; @property (nonatomic, strong) CLLocationManager *locationManager; - +@property (nonatomic, strong) IOPLog *logObj; @end @implementation SILCentralManager @@ -73,6 +74,7 @@ - (instancetype)initWithServiceUUIDs:(NSArray *)serviceUUIDs { [self setupNotifications]; [self setupBeaconMonitoring]; self.connectionsViewModel = [SILBrowserConnectionsViewModel sharedInstance]; + _logObj = [[IOPLog alloc] init]; } return self; } @@ -124,9 +126,14 @@ - (void)setupNotifications { - (void)applicationWillTerminateNotification:(NSNotification *)notification { if (self.connectedPeripheral) { NSLog(@"Disconnected from connected peripheral"); + + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"Disconnected from connected peripheral: %@", self.connectedPeripheral]]; + [self disconnectFromPeripheral:self.connectedPeripheral]; } else if (self.connectingPeripheral) { NSLog(@"Disconnect from connecting peripheral"); + + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"Disconnect from connecting peripheral: %@", self.connectingPeripheral]]; [self disconnectFromPeripheral:self.connectingPeripheral]; } } @@ -383,6 +390,8 @@ - (double)getTimestampWithAdvertisementData:(NSDictionary *)advertisementData { - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@"didConnectPeripheral: %@", peripheral); + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"didConnectPeripheral: %@", peripheral]]; + [self removeUnfiredConnectionTimeoutTimer]; self.connectingPeripheral = nil; self.connectedPeripheral = peripheral; @@ -398,6 +407,11 @@ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPerip - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"didFailToConnectPeripheral: %@", peripheral.name); NSLog(@"error: %@", error); + + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"didDisconnectPeripheral: %@", peripheral]]; + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"didDisconnectPeripheral: %@", peripheral.name]]; + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"error: %@", error]]; + [self removeUnfiredConnectionTimeoutTimer]; [self handleConnectionFailureWithError:error]; [self postRegisterLogNotification:[SILLogDataModel prepareLogDescription:@"didFailToConnectPeripheral: " andPeripheral:peripheral andError:error]]; @@ -408,6 +422,10 @@ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPe NSLog(@"didDisconnectPeripheral: %@", peripheral.name); NSLog(@"error: %@", error); + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"didDisconnectPeripheral: %@", peripheral]]; + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"didDisconnectPeripheral: %@", peripheral.name]]; + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"error: %@", error]]; + BOOL wasConnected = [self.connectionsViewModel isConnectedPeripheral:peripheral]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[SILCentralManagerPeripheralKey] = peripheral; @@ -415,7 +433,9 @@ - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPe if (error) { userInfo[SILCentralManagerErrorKey] = error; } - + + [_logObj iopLogSwiftFunctionWithMessage:[NSString stringWithFormat:@"UserInfo: %@", userInfo]]; + [[NSNotificationCenter defaultCenter] postNotificationName:SILCentralManagerDidDisconnectPeripheralNotification object:self userInfo:userInfo]; diff --git a/SiliconLabsApp/Categories/UIImage+SILImages.h b/SiliconLabsApp/Categories/UIImage+SILImages.h index b6bc7252..f80c6efb 100644 --- a/SiliconLabsApp/Categories/UIImage+SILImages.h +++ b/SiliconLabsApp/Categories/UIImage+SILImages.h @@ -35,6 +35,8 @@ extern NSString * const SILImageNameHomeWifiCommissioning; extern NSString * const SILImageNameHomeESLDemo; extern NSString * const SILImageNameHomeMatterDemo; extern NSString * const SILImageNameHomeWifiOtaDemo; +//extern NSString * const SILImageNameHomeWifiSensor; +extern NSString * const SILImageNameHomeWiFiSensor; extern NSString * const SILImageNameKeyboard; extern NSString * const SILImageNameKeyboardCheckmark; diff --git a/SiliconLabsApp/Categories/UIImage+SILImages.m b/SiliconLabsApp/Categories/UIImage+SILImages.m index 0f93b963..84e49dd5 100644 --- a/SiliconLabsApp/Categories/UIImage+SILImages.m +++ b/SiliconLabsApp/Categories/UIImage+SILImages.m @@ -35,6 +35,9 @@ NSString * const SILImageNameHomeESLDemo = @"esl_icon"; NSString * const SILImageNameHomeMatterDemo = @"matter_icon"; NSString * const SILImageNameHomeWifiOtaDemo = @"wifi_ota_icon"; +//NSString * const SILImageNameHomeWifiSensor = @"icon - wifi commissioning"; +NSString * const SILImageNameHomeWiFiSensor = @"WiFi_sensore_icon"; + NSString * const SILImageNameKeyboard = @"Keyboard"; NSString * const SILImageNameKeyboardCheckmark = @"KeyboardCheckmark"; diff --git a/SiliconLabsApp/Models/SILApp.h b/SiliconLabsApp/Models/SILApp.h index 25acaf7e..610a8a4f 100644 --- a/SiliconLabsApp/Models/SILApp.h +++ b/SiliconLabsApp/Models/SILApp.h @@ -20,7 +20,8 @@ typedef NS_ENUM(NSInteger, SILAppType) { SILAppTypeWifiCommissioning, SILAppTypeESLDemo, SILAppTypeMatterDemo, - SILAppTypeWifiOTA + SILAppTypeWifiOTA, + SILAppTypeWifiSensor }; @interface SILApp : NSObject diff --git a/SiliconLabsApp/Models/SILApp.m b/SiliconLabsApp/Models/SILApp.m index 3758557b..268ebebb 100644 --- a/SiliconLabsApp/Models/SILApp.m +++ b/SiliconLabsApp/Models/SILApp.m @@ -23,7 +23,8 @@ + (NSArray *)demoApps { [self wifiCommissioningApp], [self eslDemoApp], [self matterDemoApp], - [self WifiOTADemoApp] + [self WifiOTADemoApp], + [self WifiSensorDemoApp] ]; } @@ -117,11 +118,18 @@ + (SILApp *)matterDemoApp { + (SILApp *)WifiOTADemoApp { return [[SILApp alloc] initWithAppType:SILAppTypeWifiOTA - title:@"Wi-fi OTA Demo" + title:@"Wi-Fi OTA Demo" description:@"Control OTA Firmware update over Wi-Fi." showcasedProfiles:@{} imageName:SILImageNameHomeWifiOtaDemo]; } ++ (SILApp *)WifiSensorDemoApp { + return [[SILApp alloc] initWithAppType:SILAppTypeWifiSensor + title:@"Wi-Fi Sensors" + description:@"Read and display sensor data from the dev kit sensors." + showcasedProfiles:@{} + imageName:SILImageNameHomeWiFiSensor]; +} - (instancetype)initWithAppType:(SILAppType)appType title:(NSString *)title diff --git a/SiliconLabsApp/SILAppDelegate.swift b/SiliconLabsApp/SILAppDelegate.swift index b1667b89..7c382271 100644 --- a/SiliconLabsApp/SILAppDelegate.swift +++ b/SiliconLabsApp/SILAppDelegate.swift @@ -12,6 +12,7 @@ import SwiftUI import Fabric import Crashlytics import CoreHaptics +import CocoaLumberjack class SILAppDelegate : UIResponder, UIApplicationDelegate { var window: UIWindow? @@ -33,6 +34,19 @@ class SILAppDelegate : UIResponder, UIApplicationDelegate { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = UIHostingController(rootView: MainNavigationView()) window?.makeKeyAndVisible() + + setupLogs() + return true } + private func setupLogs() { + DDLog.add(DDOSLogger.sharedInstance) + + let fileLogger: DDFileLogger = DDFileLogger() // File Logger + fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours + fileLogger.logFileManager.maximumNumberOfLogFiles = 30 + fileLogger.maximumFileSize = 1024 * 1024 * 10 // 10 MiB + DDLog.add(fileLogger) + //SBMLogger.sharedInstance().delegate = self; + } } diff --git a/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/Contents.json b/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/sil_regularGreenColor.colorset/Contents.json b/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/sil_regularGreenColor.colorset/Contents.json new file mode 100644 index 00000000..cc980d26 --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Colours.xcassets/WifiSensoreColor/sil_regularGreenColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x59", + "green" : "0xA3", + "red" : "0x14" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Contents.json new file mode 100644 index 00000000..e827a2ce --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Group 12923.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 12923@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 12923@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Group 12923.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_led_icon.imageset/Group 12923.png new file mode 100644 index 0000000000000000000000000000000000000000..09f0fe23a069fedc76b002ce68c3be43368babba GIT binary patch literal 984 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 VfW%L7Y5f2I002ovPDHLkV1nzMn?L{n literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Contents.json new file mode 100644 index 00000000..48868508 --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Group 12922.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 12922@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 12922@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922.png new file mode 100644 index 0000000000000000000000000000000000000000..d4b47da77b804dbbd504e8dbfaf75d13ba115a17 GIT binary patch literal 951 zcmV;o14#UdP)3-`(6C(`21GAR z#30NtOcA!82{TXi9941<#vD2Vv(N`}em*ZeTaL}&fe<4w%^3WVrINzL&>Jtt?t-2y z#9$axRMXHJIVwpvVS5YGm%`ZIOi+(J#C{^gBsvO7=<&ScW5<`+PqvI1szq@y+jMbh$)jrXe(Yle6r>-`^@2qVWQaXOjO_4?IE7lz!Vfn zTt91Flke7`gruPxAINYuSp8inFnr-8^lP@Dxi7zwS^Wdw*kL7NX|rzFy`#Y=+KEoQ zyrETo&)hx_E!+tOw33FBAYqO|f#{iTOfP;%{@;#OQuFJG_H;uDUIqA4n&zu$fz?O- zpfwDU7mH{KLq?>%P__xe9G28oSJ5sA(JN`K(!AKR1M`gX3%}#t(Tx~6wb0zmVv%E5 zdeE!nu>4N?{zb2i2@9HTW-|*8dgVA{&!%F7O}Phh`9kA=I1->@hdcfKnrrY3!Ua(; zx0(E=O>_7boCFEbwiDsK47gR<^$jZka?Ii1^)#FI+`FSgJ-GRH#Y+^=2T2UCwBcq} zv!8z=`)$Jtgnr@1^u1bk98L#vBuLCrUuv$FzY13I3pd6GUnQiGMY~{%j6{)Am3F}t zvjKd3oyV1XF)#6`9*!-yE}cT`+;JS~kmw3ZYl^S@*n|REJRN*4OUkC906RL*AQr)8 zr2n+##EPW&Pt}g`6Rr5(mXT^WigP4pcxwA};u4hUP7GQbwB5ici+5@HPs1l1TguT_ zO$=iuE7EH2kA0XVWlEd17T`3yl8n}79ibhaBpzZIprHyK)oboF*gU2kq%_ki{^VPX zA>w#en<&Z#J2qfLe&OK6WSzt~3o45W75Zqyl*0a#La5784C8(rW2Gk|(fSZlkMm3( zznp1m1+Q2KRv4w-fxi}3@FRIwkM5F)!002ovPDHLkV1hLYvH<`9 literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@2x.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4d7873642e84c6d42836f20fd04124c552aa6690 GIT binary patch literal 1794 zcmV+d2mSboP)4>m8MW1 zr7hG)&0=lyq}f6xieM%bnkRKzX!Et1>p3&okJ;TxW_M=p?xy>Lu(LZKoB7W<=iYPf zy(^#+b3Tdn4IN_L0JPU2p#!paYp-5*v_*ee2$7}N3}7!obYyV;cp3^3h#MAViyqb(E}poF5Y#V_6K7T? z$^S>u%m|S}=h_s^{w(UCK7q_R9qaZin$K$du&BlBuO~KXs1$qjyMcI(0P1u zZvZt?n?MuyMkwCQHbEe+ha`l&K_b-@5`qgfes>bKOrg~gavI}vlYtKpbd+N`yFi>$ z+!k)fd)iqb_FFd1-H1kkdxzX^dx5yM;(*+mF((LFYTf#_Yu)@~kkdPE*?=4r_a4nz z-+S~Ih&zF;)s)u@c}bUV(5~)c6BKuB^>JtNgTqMf?ZDU%7L?-?kE&acO--RgBV-(}DqM|_KcZZ?Sdidr`&-F^xsc#_WQpI`?9N>P>Kc`fq|2!xdOQbFawIQvTKFy>{`xu zpK^F4hTV*+kgkh0K%Ay$%gtPY*uC0#aouc7ASn#A@giwRoZL*q#2o`|szH6CZe1hm z=|!9B$eh6f>4*XDyQoSPV(TXfNfspJF+>daerL}eRU(j^%N>oY$F1`nL{>3$lFT!2 zs_ywZm`(+t8)LT@qwGT+)1oS=a(QC@leZfpPM(1yFFV$xC%X_8f^sm4kPN-^DpHh{ zm%CJY@k!uou)z8Av#U8|mY)SWkIyNu9r5{7KRrb1?lQs>TA=CEJyZp3Km{-WCW$ol z<3og@_Obag9Y`2f%5j{a{S1Uiquck$DA3-7J})-d?p zLf{y-16F7kD_n{GvHov<$IXR*&?s2gzOU~`B@RR74vps^M}zb9uKyW2FxCTqCtF=m zmkxvw**&nagoxp|lv#cn*uBD#UD+hdeNa=EA}~_k=TyT&4BTA!7b;+jiA5Yn3W`CU zB8@iH=@1zT((4wrWp&K}-SyB|z2;7jm5~MNU4w8vE8ooDLK`o<{sVb|IyYv}#*3|` z`Nb88-Au?d6vAl*mnHIlo_+f|eD^7V-RqJ&Ew40rVmC9O2rf}@dQp8}Hx@rDrVp|8 zbNtwHFQ%Aku$w7t&G?3c^tkyLrQ$$XrEAZM0tVdf zY9g0X-6!JQty)ULdEMA;3u+W;Yl)io8yl92@GUCm%MYP zE6W{hMXaPkxI9rBk9b?XI8N*_D?Y7yEDuN9+AI{4`mo2b9`$htoR|()Tb<}XV`}^k zS&}w_=YuO`Jyn-U`BxMZIa9T(H3quJ#D3FvFSg_JIUODLJY;mEuT2HF*qd5kd0;MO z`wr7Et1 zs2IOv(m>Ks!-e8l!xf3d3n`d=p-VT>u3m9sQ*O8+LPac0@v?Ej%d6ZKLElMf>1B}LymP{)Vb^rhX07*qoM6N<$f-yZ@i2wiq literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@3x.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/WiFi_motion_icon.imageset/Group 12922@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb74cf1801fe2493cbab0d1d030ff3f1ff03d76 GIT binary patch literal 2500 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 X)fF3mJ$C8sHYvE literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/graphic - light_demo - light - off@2x.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/blub_off_tint.imageset/graphic - light_demo - light - off@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a62c13d1d3ae7b7df2ae8ba763b2c7715ee8dc8f GIT binary patch literal 19105 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 literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/Contents.json new file mode 100644 index 00000000..13c4569f --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lightbulb.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lightbulb 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lightbulb 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/lightbulb 1.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_blue.imageset/lightbulb 1.png new file mode 100644 index 0000000000000000000000000000000000000000..4144d6738f52981b5b9f9806942ce569fc5de4d3 GIT binary patch literal 2360 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 literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1) 2.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1) 2.png new file mode 100644 index 0000000000000000000000000000000000000000..d753a9ca6e352684419007775bc704c715bce506 GIT binary patch literal 2055 zcmV+i2>ADjP)h+_@GgO34V|msu*H0CKXfVgGe+{i<+tdLu_g+1;hrev>+)4+4`r_ z?an**yqTSucl_|~wBvTWJ2UUy*?#CR+1xiX_xyh6op;~;bM9SW1raB5z_9#oC4E>q zs8$V&8kTG&-3V4v{CvQL02DE0L#5 zhJq@^obE9;{b!sc7Y|EQC;&?I)4as}VVYi1glkYlBA-^mM*uL#u^pxM|G}_*BJa)9 zy#pB*7zM&Ky~41aGb|ce`@uo&OGPbB*Bcf#EE*?r+aR`v^F)#Nu^&X%e%5KttU7gSwNI`7p;|F4&$*}ah%cG*s_8F;VZPR|%o~<} z_|uRF&#R`t5RtXlPu80oeeuUhvc<67dC>`-EQ@ocCNR}nqp_~i=}aq8kOjePqgI;% zQESw$j4S|QUA6}SJ_g_>0Gk1fFR0Q6=p`lH7i6@Da3Pu-zGo+yK?HXP~N;P-L57 z`MP0wxySB>$l7-euHW1gC$i_veWc&%-&)ngiM_8o!7(iDI7xR$)?U_+x#4t&VX^se zC4U=u!K4~Ej#r(2Gft$h^+qVNE>6+j<>XRo|#YuB>oX9PP<%fo)?rx;xT2KW5 z45xRT`M&Y~vxrnf!*Wk3a$}*J915!Ac_$|tC-y5vsNy8K*sy%-%xjnKg{lDn*Fmab znOXCOPL<+&889qgbdDV?D?1b?>3a>!f80p42dY8dIm)5a{mXfQyy^YR4K?p8liDy} z(|si(n||4S_W>6Go~+j|DpPMLxZz3-+b4bSN7jD0+p%`DPKIT_3&Q7%@|TbmR@dPL zKPt|3HpG};D&cVewV;#T2LPnn)fy-PQ$F&vRz066`AdXM)|(q$D-fufP-LC^+@7AD zoi4&%s0GA4e-(g90Ec;U@PG3I%{PSd*7HUWBI~CQxXe-yIg?WX6P$&RFM()P_&&ndHJwuA01N0~WkYZI{YEp!^ zTEv)?W|R~FtuMmU4^FHl2Jq4DXH~2x0!Z;%5#ECHfYyru6u{O+lN`RFS|!q}0f4mf z;>dItgW>eBVbM_J-J{ZUt5P*A$BV8#%9shd;R$yX=jsns!R@&w?FEsJN_SDjNpq81 z3(QT;&8_jRH#Il6#&CMdwYqJ-_4JOpF<%Z?&Zd2sjMfnWN z*Sd+&TAF&Fah3wtDbBl2ap)%&S$nBVwYPnBdS0flV$Ab^J_eu~WVB;g9#XmuzyyGw zq*Kv>(j4r8xA}XP(QbD+bN~Q+JTK^bMg18)@*Msfh9z5l^5m)^>?6a{c6q>a?k^UF zzZG15xZw~0YK?_km#e-IVVYiz{Jg+npC?B=FJbj{08qlfy?3RP+mPb*vc2j>p@hit zpc4Q;tkL$oCG0 z!Mu__RiZ-|un=C4$<-Q-D=Tf?1?Y9?IRVxuIsiSJRd8n-hJ_NJOTlXZ49h_PMRSGw zZ5)-l?cNK20Z<(`Hf;dVYCE_JXq+6xyfrrV7E*c+K--$I2BMA+H~n>jMDt*I0l>BG zYWB+62Sb-J*VE-6ece3Zc_Ktw*#Y1g9-i002ovPDHLkV1lHH-tqtd literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1).png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_cyan.imageset/lightbulb (1).png new file mode 100644 index 0000000000000000000000000000000000000000..d753a9ca6e352684419007775bc704c715bce506 GIT binary patch literal 2055 zcmV+i2>ADjP)h+_@GgO34V|msu*H0CKXfVgGe+{i<+tdLu_g+1;hrev>+)4+4`r_ z?an**yqTSucl_|~wBvTWJ2UUy*?#CR+1xiX_xyh6op;~;bM9SW1raB5z_9#oC4E>q zs8$V&8kTG&-3V4v{CvQL02DE0L#5 zhJq@^obE9;{b!sc7Y|EQC;&?I)4as}VVYi1glkYlBA-^mM*uL#u^pxM|G}_*BJa)9 zy#pB*7zM&Ky~41aGb|ce`@uo&OGPbB*Bcf#EE*?r+aR`v^F)#Nu^&X%e%5KttU7gSwNI`7p;|F4&$*}ah%cG*s_8F;VZPR|%o~<} z_|uRF&#R`t5RtXlPu80oeeuUhvc<67dC>`-EQ@ocCNR}nqp_~i=}aq8kOjePqgI;% zQESw$j4S|QUA6}SJ_g_>0Gk1fFR0Q6=p`lH7i6@Da3Pu-zGo+yK?HXP~N;P-L57 z`MP0wxySB>$l7-euHW1gC$i_veWc&%-&)ngiM_8o!7(iDI7xR$)?U_+x#4t&VX^se zC4U=u!K4~Ej#r(2Gft$h^+qVNE>6+j<>XRo|#YuB>oX9PP<%fo)?rx;xT2KW5 z45xRT`M&Y~vxrnf!*Wk3a$}*J915!Ac_$|tC-y5vsNy8K*sy%-%xjnKg{lDn*Fmab znOXCOPL<+&889qgbdDV?D?1b?>3a>!f80p42dY8dIm)5a{mXfQyy^YR4K?p8liDy} z(|si(n||4S_W>6Go~+j|DpPMLxZz3-+b4bSN7jD0+p%`DPKIT_3&Q7%@|TbmR@dPL zKPt|3HpG};D&cVewV;#T2LPnn)fy-PQ$F&vRz066`AdXM)|(q$D-fufP-LC^+@7AD zoi4&%s0GA4e-(g90Ec;U@PG3I%{PSd*7HUWBI~CQxXe-yIg?WX6P$&RFM()P_&&ndHJwuA01N0~WkYZI{YEp!^ zTEv)?W|R~FtuMmU4^FHl2Jq4DXH~2x0!Z;%5#ECHfYyru6u{O+lN`RFS|!q}0f4mf z;>dItgW>eBVbM_J-J{ZUt5P*A$BV8#%9shd;R$yX=jsns!R@&w?FEsJN_SDjNpq81 z3(QT;&8_jRH#Il6#&CMdwYqJ-_4JOpF<%Z?&Zd2sjMfnWN z*Sd+&TAF&Fah3wtDbBl2ap)%&S$nBVwYPnBdS0flV$Ab^J_eu~WVB;g9#XmuzyyGw zq*Kv>(j4r8xA}XP(QbD+bN~Q+JTK^bMg18)@*Msfh9z5l^5m)^>?6a{c6q>a?k^UF zzZG15xZw~0YK?_km#e-IVVYiz{Jg+npC?B=FJbj{08qlfy?3RP+mPb*vc2j>p@hit zpc4Q;tkL$oCG0 z!Mu__RiZ-|un=C4$<-Q-D=Tf?1?Y9?IRVxuIsiSJRd8n-hJ_NJOTlXZ49h_PMRSGw zZ5)-l?cNK20Z<(`Hf;dVYCE_JXq+6xyfrrV7E*c+K--$I2BMA+H~n>jMDt*I0l>BG zYWB+62Sb-J*VE-6ece3Zc_Ktw*#Y1g9-i002ovPDHLkV1lHH-tqtd literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/Contents.json new file mode 100644 index 00000000..13c4569f --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lightbulb.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lightbulb 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lightbulb 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 1.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 1.png new file mode 100644 index 0000000000000000000000000000000000000000..896b51c1692afae442b42d5db1cbc09fb2165a22 GIT binary patch literal 2409 zcmV-v36}PWP)C$G7+nQt_2YEW{SyK(X?vA=95w(GE zu_B6AO%#)}P<~cE(Q7zuAFC<-Fk)%EOfI=dlskZTosr||DD=glJ91v_*NVzYV7s%NK&tqDB)vF{@^8<) z3wRzl)Ox)w4$ToMCTEGt?d21_Dr(^&u5rL6Yn)MkHBbW1ibZn-f)Y0n)2siXW*i~_ zrLLYAnlkNeCH&5~G>2nU@9|;=!@%DXm*&A4z+}<^e(ioapLLr7H4D?aC#-9tsoDH;o(xM@ce#H_koLTaM-rF zsJQ^)B852s3V*Tqt4isf=W5BHT*q-%{msCI@x`)k`<1Ay01_%J_MOH323t0h{1U<+DMy$da_W}S z)w^}HqSe$31i6kjuiEZ+Px=MtJvK1(T2rR|G%r#F5~y6^x(fGKCHlmfQy4dJl=a$z z$_0pi8JG-sfjh!?W-j59HO^*I3lLomcoBbY*x9pwADy1o38rLkuwHYeuI^{5EmJ(* z;O3S@&^PTxg=0lo9(EO%)vc9J%?P~Sr0{JZKzP*cX}*+m*6#{3$y-&p5!mj=myYA~ z4Ie}`J(i)cd1vYk3dbUB2-C&ok;FO*ePOz|P{f=8^r4(@$L(H?V#0KBiNXuOk-?s( zrLou^S)r$Of{1({K%CrIbxS`AVP`&X%TCwK(t0MlMbwe?*Yxu$jpWXLdxCn7GRGXXr4i^W2&|G4Zu4M z4y7ympL(UEy#dx8158$Uxsg#Ix_Y-NYyu|OME_Zw(YsCH6~G2k`g|1Wh+%R682~}H zMbW7SgFFf#qA37{R{>OHPBh&pcr(3nFzWmuDk}OKfWpkmU+-4Vr$y0?+6V>!BBo(c zpuLFO`)cgSel_`jVvfm7^79u%7 z#BZxC3(k(BQx#p=oo?^k9zb+9fEfN2MK)3u2*c7A%CoPN&@o(T&ZGn21OVrKO@W%> zL^Jx#XaB9R1(@EHNq!-cRt*Hk&!rq5e~WbQt5LKfKQ#l70(dJ6qH0x(dVfXb@J~_a z9+4doiuo-7s$Zw4b$V32QR!)&j<5hg@b^TOi(YR#^REDp0fz(wcI0qXj0A&bF)#%v zgz28%sCx0l13W(hyb24O@~OFTXpTfvK6Q#B-vhjeSsaIEEDk8mD{d6@=Rn}St|gN` zA`abs$fss_uWKoH-M@%lJ0ThUD} z;TFXu9bezw*4yt~cp>n*z~@ZKtYh8T6C>+xZb{f6@rb}Fp#Sh+~MaIziWe`{39;N5A19SB5BM_l!OC=D6DmgW^VR`&h`#{2p8>KM;uz&Kz{BMJ`<+$kzze^Wl7ZU+cl z4j^7u#bnkQ4WY3P5SK;Ge9Ft+^)RTSPsM4{ z#|)#f+vEz#qpfEJ%Bk$T1FRYx$g?ic>qr(O1wKEDhE@UVxfkV&^(*LkHq_M)I^(ly zUmEuYY;H-|zG-^^bRsoYuZW1^-qRcZc zU2KiTpb=>3K*$@JkFbIA&d^#ZAw1=5{d0+rse-ZDES>8(&Z=7payt8Ok{-?=La)PI zSBW?`lrdS(3e>L!>0tE210+y7jrs=~Z5NEi8<0s)wGzG$3^*}!Fr$1>;muU5{;xpb z2AU^v&icAPiK~V&sRBVZxmDq_wWLva$)}4aHp-^Gj7b$RVlD=rp}c{4G(w=qi@CJ1 bR+af5rynVBD>KFO00000NkvXXu0mjf;SGH; literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 2.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb 2.png new file mode 100644 index 0000000000000000000000000000000000000000..896b51c1692afae442b42d5db1cbc09fb2165a22 GIT binary patch literal 2409 zcmV-v36}PWP)C$G7+nQt_2YEW{SyK(X?vA=95w(GE zu_B6AO%#)}P<~cE(Q7zuAFC<-Fk)%EOfI=dlskZTosr||DD=glJ91v_*NVzYV7s%NK&tqDB)vF{@^8<) z3wRzl)Ox)w4$ToMCTEGt?d21_Dr(^&u5rL6Yn)MkHBbW1ibZn-f)Y0n)2siXW*i~_ zrLLYAnlkNeCH&5~G>2nU@9|;=!@%DXm*&A4z+}<^e(ioapLLr7H4D?aC#-9tsoDH;o(xM@ce#H_koLTaM-rF zsJQ^)B852s3V*Tqt4isf=W5BHT*q-%{msCI@x`)k`<1Ay01_%J_MOH323t0h{1U<+DMy$da_W}S z)w^}HqSe$31i6kjuiEZ+Px=MtJvK1(T2rR|G%r#F5~y6^x(fGKCHlmfQy4dJl=a$z z$_0pi8JG-sfjh!?W-j59HO^*I3lLomcoBbY*x9pwADy1o38rLkuwHYeuI^{5EmJ(* z;O3S@&^PTxg=0lo9(EO%)vc9J%?P~Sr0{JZKzP*cX}*+m*6#{3$y-&p5!mj=myYA~ z4Ie}`J(i)cd1vYk3dbUB2-C&ok;FO*ePOz|P{f=8^r4(@$L(H?V#0KBiNXuOk-?s( zrLou^S)r$Of{1({K%CrIbxS`AVP`&X%TCwK(t0MlMbwe?*Yxu$jpWXLdxCn7GRGXXr4i^W2&|G4Zu4M z4y7ympL(UEy#dx8158$Uxsg#Ix_Y-NYyu|OME_Zw(YsCH6~G2k`g|1Wh+%R682~}H zMbW7SgFFf#qA37{R{>OHPBh&pcr(3nFzWmuDk}OKfWpkmU+-4Vr$y0?+6V>!BBo(c zpuLFO`)cgSel_`jVvfm7^79u%7 z#BZxC3(k(BQx#p=oo?^k9zb+9fEfN2MK)3u2*c7A%CoPN&@o(T&ZGn21OVrKO@W%> zL^Jx#XaB9R1(@EHNq!-cRt*Hk&!rq5e~WbQt5LKfKQ#l70(dJ6qH0x(dVfXb@J~_a z9+4doiuo-7s$Zw4b$V32QR!)&j<5hg@b^TOi(YR#^REDp0fz(wcI0qXj0A&bF)#%v zgz28%sCx0l13W(hyb24O@~OFTXpTfvK6Q#B-vhjeSsaIEEDk8mD{d6@=Rn}St|gN` zA`abs$fss_uWKoH-M@%lJ0ThUD} z;TFXu9bezw*4yt~cp>n*z~@ZKtYh8T6C>+xZb{f6@rb}Fp#Sh+~MaIziWe`{39;N5A19SB5BM_l!OC=D6DmgW^VR`&h`#{2p8>KM;uz&Kz{BMJ`<+$kzze^Wl7ZU+cl z4j^7u#bnkQ4WY3P5SK;Ge9Ft+^)RTSPsM4{ z#|)#f+vEz#qpfEJ%Bk$T1FRYx$g?ic>qr(O1wKEDhE@UVxfkV&^(*LkHq_M)I^(ly zUmEuYY;H-|zG-^^bRsoYuZW1^-qRcZc zU2KiTpb=>3K*$@JkFbIA&d^#ZAw1=5{d0+rse-ZDES>8(&Z=7payt8Ok{-?=La)PI zSBW?`lrdS(3e>L!>0tE210+y7jrs=~Z5NEi8<0s)wGzG$3^*}!Fr$1>;muU5{;xpb z2AU^v&icAPiK~V&sRBVZxmDq_wWLva$)}4aHp-^Gj7b$RVlD=rp}c{4G(w=qi@CJ1 bR+af5rynVBD>KFO00000NkvXXu0mjf;SGH; literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_green.imageset/lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..896b51c1692afae442b42d5db1cbc09fb2165a22 GIT binary patch literal 2409 zcmV-v36}PWP)C$G7+nQt_2YEW{SyK(X?vA=95w(GE zu_B6AO%#)}P<~cE(Q7zuAFC<-Fk)%EOfI=dlskZTosr||DD=glJ91v_*NVzYV7s%NK&tqDB)vF{@^8<) z3wRzl)Ox)w4$ToMCTEGt?d21_Dr(^&u5rL6Yn)MkHBbW1ibZn-f)Y0n)2siXW*i~_ zrLLYAnlkNeCH&5~G>2nU@9|;=!@%DXm*&A4z+}<^e(ioapLLr7H4D?aC#-9tsoDH;o(xM@ce#H_koLTaM-rF zsJQ^)B852s3V*Tqt4isf=W5BHT*q-%{msCI@x`)k`<1Ay01_%J_MOH323t0h{1U<+DMy$da_W}S z)w^}HqSe$31i6kjuiEZ+Px=MtJvK1(T2rR|G%r#F5~y6^x(fGKCHlmfQy4dJl=a$z z$_0pi8JG-sfjh!?W-j59HO^*I3lLomcoBbY*x9pwADy1o38rLkuwHYeuI^{5EmJ(* z;O3S@&^PTxg=0lo9(EO%)vc9J%?P~Sr0{JZKzP*cX}*+m*6#{3$y-&p5!mj=myYA~ z4Ie}`J(i)cd1vYk3dbUB2-C&ok;FO*ePOz|P{f=8^r4(@$L(H?V#0KBiNXuOk-?s( zrLou^S)r$Of{1({K%CrIbxS`AVP`&X%TCwK(t0MlMbwe?*Yxu$jpWXLdxCn7GRGXXr4i^W2&|G4Zu4M z4y7ympL(UEy#dx8158$Uxsg#Ix_Y-NYyu|OME_Zw(YsCH6~G2k`g|1Wh+%R682~}H zMbW7SgFFf#qA37{R{>OHPBh&pcr(3nFzWmuDk}OKfWpkmU+-4Vr$y0?+6V>!BBo(c zpuLFO`)cgSel_`jVvfm7^79u%7 z#BZxC3(k(BQx#p=oo?^k9zb+9fEfN2MK)3u2*c7A%CoPN&@o(T&ZGn21OVrKO@W%> zL^Jx#XaB9R1(@EHNq!-cRt*Hk&!rq5e~WbQt5LKfKQ#l70(dJ6qH0x(dVfXb@J~_a z9+4doiuo-7s$Zw4b$V32QR!)&j<5hg@b^TOi(YR#^REDp0fz(wcI0qXj0A&bF)#%v zgz28%sCx0l13W(hyb24O@~OFTXpTfvK6Q#B-vhjeSsaIEEDk8mD{d6@=Rn}St|gN` zA`abs$fss_uWKoH-M@%lJ0ThUD} z;TFXu9bezw*4yt~cp>n*z~@ZKtYh8T6C>+xZb{f6@rb}Fp#Sh+~MaIziWe`{39;N5A19SB5BM_l!OC=D6DmgW^VR`&h`#{2p8>KM;uz&Kz{BMJ`<+$kzze^Wl7ZU+cl z4j^7u#bnkQ4WY3P5SK;Ge9Ft+^)RTSPsM4{ z#|)#f+vEz#qpfEJ%Bk$T1FRYx$g?ic>qr(O1wKEDhE@UVxfkV&^(*LkHq_M)I^(ly zUmEuYY;H-|zG-^^bRsoYuZW1^-qRcZc zU2KiTpb=>3K*$@JkFbIA&d^#ZAw1=5{d0+rse-ZDES>8(&Z=7payt8Ok{-?=La)PI zSBW?`lrdS(3e>L!>0tE210+y7jrs=~Z5NEi8<0s)wGzG$3^*}!Fr$1>;muU5{;xpb z2AU^v&icAPiK~V&sRBVZxmDq_wWLva$)}4aHp-^Gj7b$RVlD=rp}c{4G(w=qi@CJ1 bR+af5rynVBD>KFO00000NkvXXu0mjf;SGH; literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/Contents.json new file mode 100644 index 00000000..13c4569f --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "lightbulb.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lightbulb 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lightbulb 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 1.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 1.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c49dc18ce00f3481f45d95e243b51ed9b147ce GIT binary patch literal 1562 zcmV+#2IcvQP)fBgB9*d?y0d*n9j?0b8drz#jfx#8JeEA{@>@%(I912QfCMt!o9`Z}$5U zgNkr?0pcSQvNoq(;R-;EMf`2{H|MqY3L6L7Ow3(5ZQnt>mD46dtT%seW14KX1g|765s z#96*L%UZxROByX{sN%)gQAr0QMV)2nX$TNA5zixbAlj`fr&A|l7vg2a^@#G8>4;f~ zm56H`dHNyd+JmJZw#!EG?T9ZBhwNUu%+I#iX?D~w!=~HWd5m6730Z@fRKW3ML~Dv? zx4pm9tF^ApBr_085PMQ~-yp6m;C~`wb&BVeJjR0&Po)+(U5M8a7i6@bgE(fkKaz99 zWIbmNVy|(uBOc6XJKD5%n(Z;fGld3D#7OH6X|ksgXVkEH7|~2zTZ~7HUr*()a zsdD#G88{c6g6kIhy$7+15YsX7NWKu!zshgZ5GPDP<~;#o2x1cA4#eBUJ4+DV)`Oa? z+}zato2u{(L@YvF?e>%fVBK%G34GF#YXsuO)N8jEG1z`vLS-Ltwiod}fOyn|ZOJp( z)#1Dr@w@S^wt=x9@j3#;QXhVtVO{Sx6Lp}LVUw&SzHWf8FU{9&cKw}Q&vM~)-Em!P`9VjXEcn3kWP|pD<>RZA%?#HH z7-sn)N1iPB$?{Q_|6#dPT+p=wlC5f+BTp72z3hC;li*2<3#rpyTa^cI#Hwtor1vDP zvFjn0|La2PS^-I`jC16vi%3zMq}x-s5}*4*`#vQQ!)-b`LIXosHvJ~y@nAvNtYVUI z*HsPlIn#U|a@~9QXtiW|n(KipEAF;@ZKQ|>`t1$k*c;X*ht;=m_BRaim-(C#+FvFh zJ~Tng8sO_W^R+3ougpM9wMb1TVtPP7SD24(#LQ5B3W4~e2Z()$kpcKeAbv3)%YE}Z z!vZ7QOyEw$c^y59 zH~7@n5D>@kkJx5uNPUZ5_JgeJAE(CmMOOsd!9|LCB<)aq3S~;2;?;1eq$WwbLY^E6 zy@fqsd6CgOu{lt)lp|aNBK2_C^6`iZbJ`}s&ciiJIU;lgBwhD$OGR+Pla{IoT>%>; z&5=~}SX+mrPra!*jot`)PSD=_<&vf<9?|olThca3^CLy&1wLIsd|sg15tuw_V@Qp` zdH|wD@c@RTOC_=tPl0Ah9f(f~ ztRGIJ&w)5}zLDyOq)$%mhz*E|W$X3^Du)^Jq{7?iJP7`O0pcSpMuHFGb2UE!YXV4h|dD&?EyQ>*6j_%=lxo#oc^qpI*Fse4H*Oe2j2hC#IiZ%`Tzg` M07*qoM6N<$f-Up!j{pDw literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 2.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb 2.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c49dc18ce00f3481f45d95e243b51ed9b147ce GIT binary patch literal 1562 zcmV+#2IcvQP)fBgB9*d?y0d*n9j?0b8drz#jfx#8JeEA{@>@%(I912QfCMt!o9`Z}$5U zgNkr?0pcSQvNoq(;R-;EMf`2{H|MqY3L6L7Ow3(5ZQnt>mD46dtT%seW14KX1g|765s z#96*L%UZxROByX{sN%)gQAr0QMV)2nX$TNA5zixbAlj`fr&A|l7vg2a^@#G8>4;f~ zm56H`dHNyd+JmJZw#!EG?T9ZBhwNUu%+I#iX?D~w!=~HWd5m6730Z@fRKW3ML~Dv? zx4pm9tF^ApBr_085PMQ~-yp6m;C~`wb&BVeJjR0&Po)+(U5M8a7i6@bgE(fkKaz99 zWIbmNVy|(uBOc6XJKD5%n(Z;fGld3D#7OH6X|ksgXVkEH7|~2zTZ~7HUr*()a zsdD#G88{c6g6kIhy$7+15YsX7NWKu!zshgZ5GPDP<~;#o2x1cA4#eBUJ4+DV)`Oa? z+}zato2u{(L@YvF?e>%fVBK%G34GF#YXsuO)N8jEG1z`vLS-Ltwiod}fOyn|ZOJp( z)#1Dr@w@S^wt=x9@j3#;QXhVtVO{Sx6Lp}LVUw&SzHWf8FU{9&cKw}Q&vM~)-Em!P`9VjXEcn3kWP|pD<>RZA%?#HH z7-sn)N1iPB$?{Q_|6#dPT+p=wlC5f+BTp72z3hC;li*2<3#rpyTa^cI#Hwtor1vDP zvFjn0|La2PS^-I`jC16vi%3zMq}x-s5}*4*`#vQQ!)-b`LIXosHvJ~y@nAvNtYVUI z*HsPlIn#U|a@~9QXtiW|n(KipEAF;@ZKQ|>`t1$k*c;X*ht;=m_BRaim-(C#+FvFh zJ~Tng8sO_W^R+3ougpM9wMb1TVtPP7SD24(#LQ5B3W4~e2Z()$kpcKeAbv3)%YE}Z z!vZ7QOyEw$c^y59 zH~7@n5D>@kkJx5uNPUZ5_JgeJAE(CmMOOsd!9|LCB<)aq3S~;2;?;1eq$WwbLY^E6 zy@fqsd6CgOu{lt)lp|aNBK2_C^6`iZbJ`}s&ciiJIU;lgBwhD$OGR+Pla{IoT>%>; z&5=~}SX+mrPra!*jot`)PSD=_<&vf<9?|olThca3^CLy&1wLIsd|sg15tuw_V@Qp` zdH|wD@c@RTOC_=tPl0Ah9f(f~ ztRGIJ&w)5}zLDyOq)$%mhz*E|W$X3^Du)^Jq{7?iJP7`O0pcSpMuHFGb2UE!YXV4h|dD&?EyQ>*6j_%=lxo#oc^qpI*Fse4H*Oe2j2hC#IiZ%`Tzg` M07*qoM6N<$f-Up!j{pDw literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_magenta.imageset/lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c49dc18ce00f3481f45d95e243b51ed9b147ce GIT binary patch literal 1562 zcmV+#2IcvQP)fBgB9*d?y0d*n9j?0b8drz#jfx#8JeEA{@>@%(I912QfCMt!o9`Z}$5U zgNkr?0pcSQvNoq(;R-;EMf`2{H|MqY3L6L7Ow3(5ZQnt>mD46dtT%seW14KX1g|765s z#96*L%UZxROByX{sN%)gQAr0QMV)2nX$TNA5zixbAlj`fr&A|l7vg2a^@#G8>4;f~ zm56H`dHNyd+JmJZw#!EG?T9ZBhwNUu%+I#iX?D~w!=~HWd5m6730Z@fRKW3ML~Dv? zx4pm9tF^ApBr_085PMQ~-yp6m;C~`wb&BVeJjR0&Po)+(U5M8a7i6@bgE(fkKaz99 zWIbmNVy|(uBOc6XJKD5%n(Z;fGld3D#7OH6X|ksgXVkEH7|~2zTZ~7HUr*()a zsdD#G88{c6g6kIhy$7+15YsX7NWKu!zshgZ5GPDP<~;#o2x1cA4#eBUJ4+DV)`Oa? z+}zato2u{(L@YvF?e>%fVBK%G34GF#YXsuO)N8jEG1z`vLS-Ltwiod}fOyn|ZOJp( z)#1Dr@w@S^wt=x9@j3#;QXhVtVO{Sx6Lp}LVUw&SzHWf8FU{9&cKw}Q&vM~)-Em!P`9VjXEcn3kWP|pD<>RZA%?#HH z7-sn)N1iPB$?{Q_|6#dPT+p=wlC5f+BTp72z3hC;li*2<3#rpyTa^cI#Hwtor1vDP zvFjn0|La2PS^-I`jC16vi%3zMq}x-s5}*4*`#vQQ!)-b`LIXosHvJ~y@nAvNtYVUI z*HsPlIn#U|a@~9QXtiW|n(KipEAF;@ZKQ|>`t1$k*c;X*ht;=m_BRaim-(C#+FvFh zJ~Tng8sO_W^R+3ougpM9wMb1TVtPP7SD24(#LQ5B3W4~e2Z()$kpcKeAbv3)%YE}Z z!vZ7QOyEw$c^y59 zH~7@n5D>@kkJx5uNPUZ5_JgeJAE(CmMOOsd!9|LCB<)aq3S~;2;?;1eq$WwbLY^E6 zy@fqsd6CgOu{lt)lp|aNBK2_C^6`iZbJ`}s&ciiJIU;lgBwhD$OGR+Pla{IoT>%>; z&5=~}SX+mrPra!*jot`)PSD=_<&vf<9?|olThca3^CLy&1wLIsd|sg15tuw_V@Qp` zdH|wD@c@RTOC_=tPl0Ah9f(f~ ztRGIJ&w)5}zLDyOq)$%mhz*E|W$X3^Du)^Jq{7?iJP7`O0pcSpMuHFGb2UE!YXV4h|dD&?EyQ>*6j_%=lxo#oc^qpI*Fse4H*Oe2j2hC#IiZ%`Tzg` M07*qoM6N<$f-Up!j{pDw literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/Contents.json b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/Contents.json new file mode 100644 index 00000000..bbdfab82 --- /dev/null +++ b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "lightbulb (3).png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "lightbulb (3) 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lightbulb (3) 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/lightbulb (3) 1.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_off.imageset/lightbulb (3) 1.png new file mode 100644 index 0000000000000000000000000000000000000000..7b88908035ff9404e8cf77291b002041acde34db GIT binary patch literal 2510 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 O0000Y?DiAkq?0~%mQ`NBB9CNn@v(- zM1>Z$d`(+uo7{U}|JaR&G;MbG+)e$1=bzoX_rA}0&U5cM=bm%kg_DSO*X?jzH+wQQ z#7|bi*=#ocZ@5rFPnHG%>tq)G|9}q-FbQ^Yxp|J~WmHw75>yUURW`i(>Le2mBLh_A zK2>dN@9DY8gr}miRmJhVt&HtU-?vbG=RgqbyXsp?9B`eCO6;9mm!w4mWkU=|P? zx&ekAZP`y`7asV`|iNNPmkAOwdHcRh{(M~?z`U|7`W|t z*S5QE2VeuQ1Fwndb}I}YzT&*|UIzH%%P&tnaOB8cqPiRa)jKk&)T-lXO{nj;BC?!< z@xH?S>icU>bkj=e1h=?uT>y7bn0z22UrHyFPajhu&-*s2>w%*pa(z0P+;}41cGulW zv5AbplWJKjTN)a20FLKfqpE8ubetlihQ{xncV&Xo{o+hojUbqRei1{mD*Kmogx_qoqRqK20>1s z4%Ic8RI0UNuHCz5YAkjeBHyLZ>>fX=nz=fadavDe?*wiI_6^q5%wJYpd%#qCByWNm zhBph;3A~WD?KLJ`>87UrnN(^y@I_!haOp&mjjgIyUoyE0)o$R-35O4_jzYV!0ZvCp z4XQsV9^P7NPd^qiw*9;mOgIQ!HsAABwTgtOt}I^iy-l5+Gfc}dHXxij^>Sb~@O0X? zpEKnfjBY>iM{;I-A0f)lK0%sI|dJ{n5BPP5?2862dqPjySJf%SY z_ms^BKxAlsF>+m@EST;N4^{kBL!L1ai^pHkAb42d4*;l6D}3lNGrV$OWPrk6DX5rd z!cz*;_4S7sn%e^c^Ay0c{$avv)C49b61{*Tl4?~Qx1Hu}HXB!H0EnxWwNNt>MxrmD zKmRR-Uf{HIT=!EZT;<@OGiF>sq1nChjbw6<377F*lj86sBFjv2uP^HaL$92zF*a=7|a#p3HAL{Kr!}tAH zfq3!GN7C+xqbjejY1@8D3%Nh>nCj9>d3@h*0VX2yU^OZaRm>Dsk>3I+RBQmS1RxB< z^^u4g1ywU(Pea2l;BBDZ>Fk_VZv3O2os)qk;P8RL!RMpaH-xGg&?*v2VI5C2kw}#r ze>fg*ppY-nQ_akgs!n0Gu$%*?L$R-d7^qGT1g*7RSi(fdR7X<;3*YS31^OXL%l>WhFc{r!{D>2y#HomFiD?YUeRB8`SLyE3+YVI+b^ zK~-ObBpzQX@B)R*&~h;-@J!rnF7YA8!nh<$7t>bPQOF4#Ycq2I*r_5nwj`5%)z&sH z+gYNz1-OXPc5)NIB2jg!tEzyiKLOjix~31rV($P)Ly_8WjIHzus?$~EFF^dL>QvJ; z#j!9hO`zR%`+;-H4D&L!ZC69jaEwnAfGdGrKq-0uzCeeHWU4F+fd2r#Q-#gtY*Oj~ O0000Y?DiAkq?0~%mQ`NBB9CNn@v(- zM1>Z$d`(+uo7{U}|JaR&G;MbG+)e$1=bzoX_rA}0&U5cM=bm%kg_DSO*X?jzH+wQQ z#7|bi*=#ocZ@5rFPnHG%>tq)G|9}q-FbQ^Yxp|J~WmHw75>yUURW`i(>Le2mBLh_A zK2>dN@9DY8gr}miRmJhVt&HtU-?vbG=RgqbyXsp?9B`eCO6;9mm!w4mWkU=|P? zx&ekAZP`y`7asV`|iNNPmkAOwdHcRh{(M~?z`U|7`W|t z*S5QE2VeuQ1Fwndb}I}YzT&*|UIzH%%P&tnaOB8cqPiRa)jKk&)T-lXO{nj;BC?!< z@xH?S>icU>bkj=e1h=?uT>y7bn0z22UrHyFPajhu&-*s2>w%*pa(z0P+;}41cGulW zv5AbplWJKjTN)a20FLKfqpE8ubetlihQ{xncV&Xo{o+hojUbqRei1{mD*Kmogx_qoqRqK20>1s z4%Ic8RI0UNuHCz5YAkjeBHyLZ>>fX=nz=fadavDe?*wiI_6^q5%wJYpd%#qCByWNm zhBph;3A~WD?KLJ`>87UrnN(^y@I_!haOp&mjjgIyUoyE0)o$R-35O4_jzYV!0ZvCp z4XQsV9^P7NPd^qiw*9;mOgIQ!HsAABwTgtOt}I^iy-l5+Gfc}dHXxij^>Sb~@O0X? zpEKnfjBY>iM{;I-A0f)lK0%sI|dJ{n5BPP5?2862dqPjySJf%SY z_ms^BKxAlsF>+m@EST;N4^{kBL!L1ai^pHkAb42d4*;l6D}3lNGrV$OWPrk6DX5rd z!cz*;_4S7sn%e^c^Ay0c{$avv)C49b61{*Tl4?~Qx1Hu}HXB!H0EnxWwNNt>MxrmD zKmRR-Uf{HIT=!EZT;<@OGiF>sq1nChjbw6<377F*lj86sBFjv2uP^HaL$92zF*a=7|a#p3HAL{Kr!}tAH zfq3!GN7C+xqbjejY1@8D3%Nh>nCj9>d3@h*0VX2yU^OZaRm>Dsk>3I+RBQmS1RxB< z^^u4g1ywU(Pea2l;BBDZ>Fk_VZv3O2os)qk;P8RL!RMpaH-xGg&?*v2VI5C2kw}#r ze>fg*ppY-nQ_akgs!n0Gu$%*?L$R-d7^qGT1g*7RSi(fdR7X<;3*YS31^OXL%l>WhFc{r!{D>2y#HomFiD?YUeRB8`SLyE3+YVI+b^ zK~-ObBpzQX@B)R*&~h;-@J!rnF7YA8!nh<$7t>bPQOF4#Ycq2I*r_5nwj`5%)z&sH z+gYNz1-OXPc5)NIB2jg!tEzyiKLOjix~31rV($P)Ly_8WjIHzus?$~EFF^dL>QvJ; z#j!9hO`zR%`+;-H4D&L!ZC69jaEwnAfGdGrKq-0uzCeeHWU4F+fd2r#Q-#gtY*Oj~ O0000v-X|o2Ziz>X_Kmjwt^5T6`H7N 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 LNkvXXu0mjfAcxsQ literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb 2.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb 2.png new file mode 100644 index 0000000000000000000000000000000000000000..76772222e96fa846c1691818ec6ea84d361e7bb7 GIT binary patch literal 1561 zcmV+!2Il#RP)M2u4zcQWo1)Qz`7h}*k z(4C;jm!O>fBhY{{d?y0d*n9j?0b8drz#jfx&{5EcA{@>D&9jI22Q)UPt!o9`Z}$5^ zgNkr?0q7$WvNoq(;R*na1^sRIH|MqY3L6L7Ow3(5ZQlXCmD45ytv7#f8ZwBW44KyJj|76f& z&{@7X%UZxROByX{sN%)gQAr1*NS$TqX$U|wLC=GBfZDAqr&A|r7wBcs^-$t69W)EH z5_F9tPe0IHd$9DwcG)Pt9rOk0kljm{`Pmjb&5jyo*mOHPkI}0sA!|UB3OJq&YEAL% zw)b~>wbr$nWCmynXiv)S8_<;n{7(d}PVu~w$9OR4sni0e3-mhZf{gZaK*!AXM{;hM ztmn)D?KO^e(1RImN1N78vpoiSrqI9%8fm>DP4+bCj2bo%gLcfvStn2+|qOSAIeOc<2c*5?@w(B;~xjy|B0`YVOXni35 zTT=t;Si85$d@cy#GZ#Sbo0xk7`dDQ?R@n7?yWSegXI5d&)Sw710KP-2O3%XW7vQ=$!fA$8hotMcHDSe0#+^q!`F zSYTwE3ET-f&m;Hn)Mn@_&_G|@USZqRq?LAHu{=l47SQL$xfe9Xht^;KjRE~?f?x8$ z_bin_Xz&%M7ucZKVFOm-76XkU4z+jnxk0ZUKr5`}9&_MWU>qxbYHJ8m%hhikIFdp2 z2A|p*0^%6{5!);csc+HCevozj9M9+h6N!ujNkD?L-pDrLiFHr3WOrEqcq{d)9 z0Mw#*07KFxlK#v|agn6$lDZ@fh$40SRoDPfInCjcTFZWp!XW;7yoKJUK(nL{&?g1f z52w-RKpZ;XNcBU~C#QDM2GGQ^b$bJq!wh*+;cavt1pmJP@e!7zlG+uo@9TX^A1Y~& z;#KOIQKVyG6*d6GXMywffSqOQ_6Fkfeyvnaf7VK!#8Kdei~;`x`Uud(YmuuL00000 LNkvXXu0mjfAcxsQ literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb.png b/SiliconLabsApp/SupportingFiles/Images.xcassets/WifiSensore/bulb_yellow.imageset/lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..76772222e96fa846c1691818ec6ea84d361e7bb7 GIT binary patch literal 1561 zcmV+!2Il#RP)M2u4zcQWo1)Qz`7h}*k z(4C;jm!O>fBhY{{d?y0d*n9j?0b8drz#jfx&{5EcA{@>D&9jI22Q)UPt!o9`Z}$5^ zgNkr?0q7$WvNoq(;R*na1^sRIH|MqY3L6L7Ow3(5ZQlXCmD45ytv7#f8ZwBW44KyJj|76f& z&{@7X%UZxROByX{sN%)gQAr1*NS$TqX$U|wLC=GBfZDAqr&A|r7wBcs^-$t69W)EH z5_F9tPe0IHd$9DwcG)Pt9rOk0kljm{`Pmjb&5jyo*mOHPkI}0sA!|UB3OJq&YEAL% zw)b~>wbr$nWCmynXiv)S8_<;n{7(d}PVu~w$9OR4sni0e3-mhZf{gZaK*!AXM{;hM ztmn)D?KO^e(1RImN1N78vpoiSrqI9%8fm>DP4+bCj2bo%gLcfvStn2+|qOSAIeOc<2c*5?@w(B;~xjy|B0`YVOXni35 zTT=t;Si85$d@cy#GZ#Sbo0xk7`dDQ?R@n7?yWSegXI5d&)Sw710KP-2O3%XW7vQ=$!fA$8hotMcHDSe0#+^q!`F zSYTwE3ET-f&m;Hn)Mn@_&_G|@USZqRq?LAHu{=l47SQL$xfe9Xht^;KjRE~?f?x8$ z_bin_Xz&%M7ucZKVFOm-76XkU4z+jnxk0ZUKr5`}9&_MWU>qxbYHJ8m%hhikIFdp2 z2A|p*0^%6{5!);csc+HCevozj9M9+h6N!ujNkD?L-pDrLiFHr3WOrEqcq{d)9 z0Mw#*07KFxlK#v|agn6$lDZ@fh$40SRoDPfInCjcTFZWp!XW;7yoKJUK(nL{&?g1f z52w-RKpZ;XNcBU~C#QDM2GGQ^b$bJq!wh*+;cavt1pmJP@e!7zlG+uo@9TX^A1Y~& z;#KOIQKVyG6*d6GXMywffSqOQ_6Fkfeyvnaf7VK!#8Kdei~;`x`Uud(YmuuL00000 LNkvXXu0mjfAcxsQ literal 0 HcmV?d00001 diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.swift b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.swift index 0f4be2d9..eee1ea4f 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.swift +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionCollectionViewCell.swift @@ -46,10 +46,10 @@ class SILAppSelectionCollectionViewCell: UICollectionViewCell { override func prepareForReuse() { super.prepareForReuse() - imageView = nil - iconImageView = nil - titleLabel = nil - descriptionLabel = nil +// imageView = nil +// iconImageView = nil +// titleLabel = nil +// descriptionLabel = nil } func setFieldsIn(_ appData: SILApp?) { diff --git a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.swift b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.swift index 65098646..6ddb8423 100644 --- a/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.swift +++ b/SiliconLabsApp/ViewControllers/AppSelection/SILAppSelectionViewController.swift @@ -127,6 +127,11 @@ class SILAppSelectionViewController : UIViewController, UICollectionViewDataSour let nextViewController = storyBoard.instantiateViewController(withIdentifier: "MatterHomeViewController") self.navigationController?.pushViewController(nextViewController, animated: true) } + private func moveToSILWifiSensorsDemoView() { + let storyBoard : UIStoryboard = UIStoryboard(name: "SILWifiSensors", bundle:nil) + let nextViewController = storyBoard.instantiateViewController(withIdentifier: "SILWifiSensorsHomeView") + self.navigationController?.pushViewController(nextViewController, animated: true) + } private func showWifiDisabledAlert() { let message = "Please check your Wi-Fi connection to use Wi-Fi OTA Demo" @@ -201,6 +206,8 @@ class SILAppSelectionViewController : UIViewController, UICollectionViewDataSour case .online(.wiFi): showWiFiOTAScreen() } + case .typeWifiSensor: + moveToSILWifiSensorsDemoView() default: return } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTestReconnectManager.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTestReconnectManager.swift index 95facb55..824d8261 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTestReconnectManager.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTestReconnectManager.swift @@ -41,6 +41,7 @@ class SILIOPTestReconnectManager: NSObject { } func reconnectToDevice(withName name: String) { + self.nameToReconnect = name self.setCentralManagerSubscription() @@ -51,6 +52,7 @@ class SILIOPTestReconnectManager: NSObject { selector: #selector(self.scanIntervalTimerFired), userInfo: nil, repeats: false) + } private func setCentralManagerSubscription() { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTesterCentralManager.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTesterCentralManager.swift index eae99a88..5219f836 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTesterCentralManager.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIOPTesterCentralManager.swift @@ -84,9 +84,15 @@ enum SILIOPTesterCentralManagerConnectionStatus { func connect(to discoveredPeripheral: SILDiscoveredPeripheral) { guard let peripheral = discoveredPeripheral.peripheral else { debugPrint("CENTRAL MANAGER discovered peripheral is nil!") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER discovered peripheral is nil!") + return } debugPrint("CONNECTING PERIPHERAL \(String(describing: peripheral))") + + IOPLog().iopLogSwiftFunction(message: "CONNECTING PERIPHERAL \(String(describing: peripheral))") + self.centralManager.connect(peripheral) } @@ -98,31 +104,52 @@ enum SILIOPTesterCentralManagerConnectionStatus { switch central.state { case .poweredOff: debugPrint("CENTRAL MANAGER powered off") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER powered off") + self.stopScanningActions() self.publishConnectionStatus.value = .bluetoothEnabled(enabled: false) case .poweredOn: debugPrint("CENTRAL MANAGER powered on") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER powered on") + self.publishConnectionStatus.value = .bluetoothEnabled(enabled: true) case .resetting: debugPrint("CENTRAL MANAGER resetting") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER powered resetting") + self.stopScanningActions() self.publishConnectionStatus.value = .bluetoothEnabled(enabled: false) case .unauthorized: debugPrint("CENTRAL MANAGER unauthorized") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER unauthorized") + self.stopScanningActions() self.publishConnectionStatus.value = .bluetoothEnabled(enabled: false) case .unknown: debugPrint("CENTRAL MANAGER unknown") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER unknown") + self.stopScanningActions() self.publishConnectionStatus.value = .bluetoothEnabled(enabled: false) case .unsupported: debugPrint("CENTRAL MANAGER UNSUPPORTED") + + IOPLog().iopLogSwiftFunction(message: "CENTRAL MANAGER UNSUPPORTED") + self.publishConnectionStatus.value = .bluetoothEnabled(enabled: false) } } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { debugPrint("DID DISCOVER PERIPHERAL \(peripheral)") + + IOPLog().iopLogSwiftFunction(message: "DID DISCOVER PERIPHERAL \(peripheral)") + if let discoveredPeripheral = self.discoveredPeripherals.first(where: { discoveredPeripheral in discoveredPeripheral.peripheral == peripheral }) { discoveredPeripheral.update(withAdvertisementData: advertisementData, rssi: RSSI, andDiscoveringTimestamp: Date.timeIntervalBetween1970AndReferenceDate) } else { @@ -133,16 +160,25 @@ enum SILIOPTesterCentralManagerConnectionStatus { func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { debugPrint("DID CONNECT \(peripheral)") + + IOPLog().iopLogSwiftFunction(message: "DID CONNECT \(peripheral)") + publishConnectionStatus.value = .connected(peripheral: peripheral) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { debugPrint("DID DISCONNECT \(peripheral) WITH ERROR \(error.debugDescription)") + + IOPLog().iopLogSwiftFunction(message: "DID DISCONNECT \(peripheral) WITH ERROR \(error.debugDescription)") + publishConnectionStatus.value = .disconnected(peripheral: peripheral, error: error) } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { debugPrint("DID FAIL TO CONNECT \(peripheral) WITH EROR \(error.debugDescription)") + + IOPLog().iopLogSwiftFunction(message: "DID FAIL TO CONNECT \(peripheral) WITH EROR \(error.debugDescription)") + publishConnectionStatus.value = .failToConnect(peripheral: peripheral, error: error) } } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIopTestOTAUpdateManger.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIopTestOTAUpdateManger.swift index b99ac5f7..e4345ac5 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIopTestOTAUpdateManger.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Helpers/SILIopTestOTAUpdateManger.swift @@ -77,6 +77,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate @objc private func didConnectPeripheral(notification: Notification) { debugPrint("didConnectPeripheral**********OTA") + IOPLog().iopLogSwiftFunction(message: "didConnectPeripheral**********OTA") if self.otaProgress == .unknown { self.otaProgress = .reconnected } else if self.otaProgress == .initiated || self.otaProgress == .reconnected { @@ -84,11 +85,13 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate } else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "Not allowed connection to peripheral.") + IOPLog().iopLogSwiftFunction(message: "Not allowed connection to peripheral.") } } @objc private func didDisconnectPeripheral(notification: Notification) { debugPrint("didDisconnectPeripheral**********OTA") + IOPLog().iopLogSwiftFunction(message: "didDisconnectPeripheral**********OTA") if self.otaProgress == .reconnected { self.otaProgress = .initiated } else if self.otaProgress == .finished { @@ -101,20 +104,25 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate self.unregisterNotifications() self.failureReson = "Not allowed disconnection from peripheral." self.waitForChangeTopController() + IOPLog().iopLogSwiftFunction(message: "Not allowed disconnection from peripheral.") } } @objc private func didFailToConnectPeripheral(notification: Notification) { debugPrint("didFailToConnectPeripheral**********OTA") + IOPLog().iopLogSwiftFunction(message: "didFailToConnectPeripheral**********OTA") self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "Fail to connect to peripheral.") + IOPLog().iopLogSwiftFunction(message: "Fail to connect to peripheral.") } @objc private func bluetoothDisabled() { debugPrint("bluetoothDisabled**********OTA") + IOPLog().iopLogSwiftFunction(message: "bluetoothDisabled**********OTA") self.unregisterNotifications() self.dismissPopoverWithCompletion(completion: nil) self.otaTestStatus.value = .failure(reason: "Bluetooth disabled.") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled.") } func setupOTAFirmWareModel() { @@ -170,6 +178,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate guard let fileToUpdate = self.fileToUpdate else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "File to update not found.") + IOPLog().iopLogSwiftFunction(message: "File to update not found.") return } @@ -182,10 +191,12 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate } else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "Peripheral disconnected when choosing a file.") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected when choosing a file.") } } else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "Chosen file isn't EBL or GBL file") + IOPLog().iopLogSwiftFunction(message: "Chosen file isn't EBL or GBL file") } } @@ -196,6 +207,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate } else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "No chosen file.") + IOPLog().iopLogSwiftFunction(message: "No chosen file.") } } @@ -221,6 +233,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate } else { self.unregisterNotifications() self.otaTestStatus.value = .failure(reason: "Error during a file update.") + IOPLog().iopLogSwiftFunction(message: "Error during a file update.") } } }) @@ -235,6 +248,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate self.handleFileUploadProgress(progress: fraction, uploadedBytes: bytes) }, completion: { (peripheral, error) in print("Completed Flash") + IOPLog().iopLogSwiftFunction(message: "Completed Flash") self.handleAppFileUploadCompletionForPeripheral(peripheral: peripheral, error: error) self.finishOTAError = error if self.finishOTAError == nil { @@ -244,6 +258,7 @@ class SILIopTestOTAUpdateManger: NSObject, SILOTAFirmwareUpdateManagerDelegate self.unregisterNotifications() self.dismissPopoverWithCompletion(completion: { self.otaTestStatus.value = .failure(reason: "Error during a file update.") + IOPLog().iopLogSwiftFunction(message: "Error during a file update.") }) } }) diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Log/DDLogFileInfo+Ext.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Log/DDLogFileInfo+Ext.swift new file mode 100644 index 00000000..9dc73b8f --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Log/DDLogFileInfo+Ext.swift @@ -0,0 +1,55 @@ +// +// DDLogFileInfo+Ext.swift +// BlueGecko +// +// Created by SovanDas Maity on 01/07/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +import CocoaLumberjack + +private let sizeDivisor = 1024.0 +private let suffixes = ["KiB", "MiB", "GiB", "TiB"] +private let unknown = "Unknown" +private let dateFormat = "yyyy-MM-dd HH:mm:ss" + +extension DDLogFileInfo { + var formattedFileSize: String { + get { + var size: Double = Double(fileSize) / sizeDivisor + var suffixIndex = 0 + while size >= sizeDivisor { + size = size / sizeDivisor + suffixIndex = suffixIndex + 1 + } + + let formattedSize = String(format: "%3.2f", size) + return "\(formattedSize) \(suffixes[suffixIndex])" + } + } + + var localCreationDateString: String { + get { + guard let creationDate = creationDate else { + return unknown + } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = dateFormat + dateFormatter.timeZone = TimeZone.current + return dateFormatter.string(from: creationDate) + } + } + + var localLastModificationDateString: String { + get { + guard let modificationDate = modificationDate else { + return unknown + } + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = dateFormat + dateFormatter.timeZone = TimeZone.current + return dateFormatter.string(from: modificationDate) + } + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLog.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLog.swift new file mode 100644 index 00000000..7f2bd933 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLog.swift @@ -0,0 +1,18 @@ +// +// IOPLog.swift +// BlueGecko +// +// Created by SovanDas Maity on 01/07/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +import CocoaLumberjack + + +class IOPLog: NSObject { + @objc func iopLogSwiftFunction(message: String) { + let fileName = URL(fileURLWithPath: #file).deletingPathExtension().lastPathComponent + DDLogVerbose("Application: \(fileName): \(message)") + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLogFilePrinter.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLogFilePrinter.swift new file mode 100644 index 00000000..7fdef5dd --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Log/IOPLogFilePrinter.swift @@ -0,0 +1,174 @@ +// +// IOPLogFilePrinter.swift +// BlueGecko +// +// Created by SovanDas Maity on 01/07/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit + +class IOPLogFilePrinter: NSObject { + fileprivate var fileHandle: FileHandle? + private var timestamp: Date + private var deviceName: String + + var getFileUrl: URL { + return URL(fileURLWithPath: getFilePathOfExistingFile()) + } + + var getFilePath: String { + return (IOPLogFilePrinter.logDirPath as NSString).appendingPathComponent(getLogFileName) + } + + private var getLogFileName: String { + let nowString = longStyleDateFormatter().string(from: timestamp) + return String(format: "%@_%@.txt", deviceName.replacingOccurrences(of: " ", with: "_").replacingOccurrences(of: ".", with: "_"), nowString.replacingOccurrences(of: " ", with: "_")) + } + + private static var logDirPath: String { + let documentDirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + let logDir = (documentDirPath as NSString).appendingPathComponent("TestSILOG") + return logDir + } + + init(timestamp: Date, deviceName: String) { + self.timestamp = timestamp + self.deviceName = deviceName + } + + class func clearLogDir() { + let fileManager = FileManager.default + + do { + let fileNames = try fileManager.contentsOfDirectory(atPath: logDirPath) + for fileName in fileNames { + let filePath = (logDirPath as NSString).appendingPathComponent(fileName) + try fileManager.removeItem(atPath: filePath) + } + } catch { + print("Could not clear dictionary: \(error)") + } + } + + func createEmptyFile(atPath filePath: String) -> Bool { + guard prepareDictionary() else { + return false + } + + guard clearExisitingFile(filePath: filePath) else { + return false + } + + let success = FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil) + guard success else { + debugPrint("File didn't created.") + return false + } + + addSkipBackupAttributeToItem(filePath: filePath) + + return true + } + + private func prepareDictionary() -> Bool { + if !directoryExists(atPath: IOPLogFilePrinter.logDirPath) { + do { + try FileManager.default.createDirectory(atPath: IOPLogFilePrinter.logDirPath, withIntermediateDirectories: true, attributes: nil) + } catch let error { + debugPrint("Error when creating a directory \(error.localizedDescription)") + return false + } + } + + guard directoryExists(atPath: IOPLogFilePrinter.logDirPath) else { + debugPrint("Directory \(IOPLogFilePrinter.logDirPath) doesn't exists!") + return false + } + + return true + } + + private func clearExisitingFile(filePath: String) -> Bool { + if fileExists(atPath: filePath) { + do { + debugPrint("File at path \(filePath) already exists.") + try FileManager.default.removeItem(atPath: filePath) + } + catch let error { + debugPrint("Error when removing a file \(error.localizedDescription)") + return false + } + } + + return true + } + + func openFile(filePath: String) -> Bool { + if !fileExists(atPath: filePath) && !createEmptyFile(atPath: filePath) { + return false + } + + fileHandle = FileHandle(forWritingAtPath: filePath) + return (fileHandle != nil) + } + + func append(text: String) -> Bool { + if fileHandle == nil { + return false + } + + guard let data = text.data(using: .utf8) else { + return false + } + + fileHandle?.seekToEndOfFile() + fileHandle?.write(data) + return true + } + + func closeFile() { + fileHandle?.closeFile() + } + + private func getFilePathOfExistingFile() -> String { + do { + let fileNames: [String] = try FileManager.default.contentsOfDirectory(atPath: IOPLogFilePrinter.logDirPath) + if fileNames.count == 1, let fileName = fileNames.first { + return (IOPLogFilePrinter.logDirPath as NSString).appendingPathComponent(fileName) + } else { + return self.getFilePath + } + } catch { + return "" + } + } + + private func longStyleDateFormatter() -> DateFormatter { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = DateFormatString.fileNameDateTime.formatString + return dateFormatter + } + + private func directoryExists(atPath dirPath: String) -> Bool { + var isDir: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: dirPath, isDirectory: &isDir) + return (exists && isDir.boolValue) + } + + private func fileExists(atPath dirPath: String) -> Bool { + var isDir: ObjCBool = false + let exists = FileManager.default.fileExists(atPath: dirPath, isDirectory: &isDir) + return (exists && !isDir.boolValue) + } + + private func addSkipBackupAttributeToItem(filePath: String) { + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: filePath) { + return + } + + var url = URL(fileURLWithPath: filePath) + url.setTemporaryResourceValue(true, forKey: .isExcludedFromBackupKey) + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/Log/LogFile.swift b/SiliconLabsApp/ViewControllers/IOP Test App/Log/LogFile.swift new file mode 100644 index 00000000..05ee9ddf --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/Log/LogFile.swift @@ -0,0 +1,17 @@ +// +// LogFile.swift +// ble_mesh-app +// +// Created by Kamil Czajka on 28/07/2020. +// Copyright © 2020 Silicon Labs, http://www.silabs.com. All rights reserved. +// + +import Foundation +import CocoaLumberjack + +struct LogFile { + var info: DDLogFileInfo + var url: URL { + URL(fileURLWithPath: info.filePath) + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_1/TestCases/SILScanTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_1/TestCases/SILScanTestCase.swift index 5c99a91f..abf14352 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_1/TestCases/SILScanTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_1/TestCases/SILScanTestCase.swift @@ -69,6 +69,7 @@ class SILScanTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWithRetries { case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.invalidateTestTimer() weakSelf.scanTimer?.invalidate() weakSelf.iopCentralManager.stopScanning() @@ -80,6 +81,7 @@ class SILScanTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWithRetries { default: weakSelf.publishTestResult(passed: false, description: "Unknown failure from central manager") + IOPLog().iopLogSwiftFunction(message: "Unknown failure from central manager") } }) disposeBag.add(token: centralManagerBluetoothStateSubscription) @@ -106,8 +108,10 @@ class SILScanTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWithRetries { let testTime = weakSelf.stopTestTimerWithResult() if testTime < weakSelf.timeoutMS { weakSelf.publishTestResult(passed: true, description: "(Testing time: \(testTime)ms, Acceptable Time: \(weakSelf.timeoutMS)ms).") + IOPLog().iopLogSwiftFunction(message: "(Testing time: \(testTime)ms, Acceptable Time: \(weakSelf.timeoutMS)ms).") } else { weakSelf.publishTestResult(passed: false, description: "Peripheral was discovered but not in \(weakSelf.timeoutMS)ms") + IOPLog().iopLogSwiftFunction(message: "Peripheral was discovered but not in \(weakSelf.timeoutMS)ms") } } }) @@ -131,6 +135,7 @@ class SILScanTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWithRetries { self.iopCentralManager.stopScanning() self.publishTestResult(passed: false, description: "Peripheral with name \(String(describing: self.peripheralLocalName)) wasn't found in any of 5 attempts of scanning for \(self.timeoutMS) ms") + IOPLog().iopLogSwiftFunction(message: "Peripheral with name \(String(describing: self.peripheralLocalName)) wasn't found in any of 5 attempts of scanning for \(self.timeoutMS) ms") } // Artifacts diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_2/TestCases/SILConnectDeviceTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_2/TestCases/SILConnectDeviceTestCase.swift index df28d59d..94a062dd 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_2/TestCases/SILConnectDeviceTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_2/TestCases/SILConnectDeviceTestCase.swift @@ -76,23 +76,29 @@ class SILConnectDeviceTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWith if testTime < weakSelf.timeoutMS { weakSelf.publishTestResult(passed: true, description: "(Testing time: \(testTime)ms, Acceptable Time: \(weakSelf.timeoutMS)ms).") + IOPLog().iopLogSwiftFunction(message: "(Testing time: \(testTime)ms, Acceptable Time: \(weakSelf.timeoutMS)ms).") } else { weakSelf.notifyErrorInAttempt(reason: "Peripheral was connected but not in \(weakSelf.timeoutMS)ms") + IOPLog().iopLogSwiftFunction(message: "Peripheral was connected but not in \(weakSelf.timeoutMS)ms") + } break case let .disconnected(peripheral: peripheral, error: error): weakSelf.notifyErrorInAttempt(reason: "didDisconnectPeripheral \(peripheral) with error \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "didDisconnectPeripheral \(peripheral) with error \(String(describing: error?.localizedDescription))") break case let .failToConnect(peripheral: peripheral, error: error): weakSelf.notifyErrorInAttempt(reason: "didFailToConnectPeripheral \(peripheral) with error \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "didFailToConnectPeripheral \(peripheral) with error \(String(describing: error?.localizedDescription))") break case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.invalidateTestTimer() weakSelf.connectTimer?.invalidate() weakSelf.publishTestResult(passed: false, description: "Bluetooth disabled!") @@ -120,6 +126,7 @@ class SILConnectDeviceTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWith private func notifyErrorInAttempt(reason: String) { debugPrint(reason) + IOPLog().iopLogSwiftFunction(message: "\(reason)") invalidateTestTimer() stopConnecting() } @@ -129,6 +136,7 @@ class SILConnectDeviceTestCase: SILTestCase, SILTestCaseTimeout, SILTestCaseWith self.iopCentralManager.disconnect(peripheral: peripheral) self.publishTestResult(passed: false, description: "Peripheral \(String(describing: self.cbPeripheral)) wasn't connected in any of 5 attempts of connecting for \(self.timeoutMS) ms") + IOPLog().iopLogSwiftFunction(message: "Peripheral \(String(describing: self.cbPeripheral)) wasn't connected in any of 5 attempts of connecting for \(self.timeoutMS) ms") } func getTestArtifacts() -> Dictionary { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_3/TestCases/SILDiscoverGATTTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_3/TestCases/SILDiscoverGATTTestCase.swift index db2d4b9f..9fda1b6d 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_3/TestCases/SILDiscoverGATTTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_3/TestCases/SILDiscoverGATTTestCase.swift @@ -78,11 +78,13 @@ class SILDiscoverGATTTestCase: SILTestCase, SILTestCaseTimeout { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("Peripheral disconnected with \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected with \(String(describing: error?.localizedDescription))") weakSelf.notifyError(reason: "Peripheral was disconnected with \(String(describing: error?.localizedDescription)).") case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.notifyError(reason: "Bluetooth disabled.") } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/SILIOPTester_Test4.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/SILIOPTester_Test4.swift index d8d09177..4412d293 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/SILIOPTester_Test4.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/SILIOPTester_Test4.swift @@ -88,19 +88,23 @@ class SILIOPTester_Test4 : SILTestScenario { switch state { case .initiated: debugPrint("DISCOVER FIRMWARE INFO INITIATED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER FIRMWARE INFO INITIATED") break case .running: debugPrint("DISCOVER FIRMWARE INFO RUNNING") + IOPLog().iopLogSwiftFunction(message: "DISCOVER FIRMWARE INFO RUNNING") break case .failed: debugPrint("DISCOVER FIRMWARE INFO FAILED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER FIRMWARE INFO FAILED") weakSelf.tests[0].performTestCase() break case let .completed(stackVersion: stackVersion): debugPrint("DISCOVER FIRMWARE COMPLETED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER FIRMWARE COMPLETED") weakSelf.parameters["stackVersion"] = stackVersion weakSelf.stackVersion = stackVersion weakSelf.discoverRFUFeatures.injectParameters(parameters: weakSelf.parameters) @@ -120,19 +124,23 @@ class SILIOPTester_Test4 : SILTestScenario { switch state { case .initiated: debugPrint("DISCOVER RFU INITIATED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER RFU INITIATED") break case .running: debugPrint("DISCOVER RFU RUNNING") + IOPLog().iopLogSwiftFunction(message: "DISCOVER RFU RUNNING") break case .failed: debugPrint("DISCOVER RFU FAILED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER RFU FAILED") weakSelf.tests[0].performTestCase() break case let .completed(firmwareInfo: firmwareInfo, connectionParameters: connectionParameters): debugPrint("DISCOVER RFU COMPLETED") + IOPLog().iopLogSwiftFunction(message: "DISCOVER RFU COMPLETED") weakSelf.firmwareInfo = firmwareInfo weakSelf.connectionParameters = connectionParameters weakSelf.tests[0].performTestCase() diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverFirmwareInfo.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverFirmwareInfo.swift index 2d9b01c5..d4f42637 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverFirmwareInfo.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverFirmwareInfo.swift @@ -68,12 +68,14 @@ class SILDiscoverFirmwareInfo { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("Peripheral disconnected with \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected with \(String(describing: error?.localizedDescription))") weakSelf.invalidateObservableTokens() weakSelf.state.value = .failed case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.invalidateObservableTokens() weakSelf.state.value = .failed } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverTestConnectionParameters.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverTestConnectionParameters.swift index aec58cf2..09d786ef 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverTestConnectionParameters.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILDiscoverTestConnectionParameters.swift @@ -91,11 +91,13 @@ class SILDiscoverTestConnectionParameters { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("Peripheral disconnected with \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected with \(String(describing: error?.localizedDescription))") weakSelf.setFailed() case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.setFailed() } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPConstCharacteristicTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPConstCharacteristicTestHelper.swift index 013186c5..e6cc1fa9 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPConstCharacteristicTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPConstCharacteristicTestHelper.swift @@ -97,6 +97,7 @@ class SILIOPConstCharacteristicTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == weakSelf.iopTestCharacteristicTypesRWConstLen1 { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if weakSelf.isFirstSubtest, data?.hexa() == weakSelf.ExceptedValue_Subtest1 { weakSelf.isFirstSubtest = false if let dataToWrite = weakSelf.ValueToWrite_Subtest2.data(withCount: 1) { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTNotificationTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTNotificationTestHelper.swift index aec49237..4507de12 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTNotificationTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTNotificationTestHelper.swift @@ -121,6 +121,7 @@ class SILIOPGATTNotificationTestHelper: SILTestCaseTimeout, SILTestCaseWithRetri case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == weakSelf.testedCharacteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") weakSelf.testTimeoutTimer?.invalidate() if let data = data?.hexa(), weakSelf.exceptedValue.contains(data) { weakSelf.observableTokens.append(weakSelf.centralManagerSubscription) @@ -135,6 +136,8 @@ class SILIOPGATTNotificationTestHelper: SILTestCaseTimeout, SILTestCaseWithRetri case let .updateNotificationState(characteristic: characteristic, state: state): debugPrint("Notification \(state) on characteristic \(characteristic)") + IOPLog().iopLogSwiftFunction(message: "Notification \(state) on characteristic \(characteristic)") + if characteristic.uuid == weakSelf.testedCharacteristicUUID, state == true { weakSelf.testTimeoutTimer?.invalidate() let testTime = weakSelf.stopTestTimerWithResult() @@ -183,6 +186,7 @@ class SILIOPGATTNotificationTestHelper: SILTestCaseTimeout, SILTestCaseWithRetri switch status { case let .updateNotificationState(characteristic: characteristic, state: state): debugPrint("Notification \(state) on characteristic \(characteristic)") + IOPLog().iopLogSwiftFunction(message: "Notification \(state) on characteristic \(characteristic)") if characteristic.uuid == weakSelf.testedCharacteristicUUID, state == false { weakSelf.invalidateOldSubscriptions() weakSelf.test() diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTOperationsTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTOperationsTestHelper.swift index 28613178..37766de6 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTOperationsTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPGATTOperationsTestHelper.swift @@ -42,11 +42,13 @@ class SILIOPGATTOperationsTestHelper { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("Peripheral disconnected with \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected with \(String(describing: error?.localizedDescription))") weakTestCase.publishTestResult(passed: false, description: "Peripheral was disconnected with \(String(describing: error?.localizedDescription)).") case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakTestCase.publishTestResult(passed: false, description: "Bluetooth disabled.") } @@ -84,6 +86,7 @@ class SILIOPGATTOperationsTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == characteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if data?.hexa() == exceptedValue { weakTestCase.publishTestResult(passed: true) } else { @@ -136,6 +139,7 @@ class SILIOPGATTOperationsTestHelper { case let .successWrite(characteristic): if characteristic.uuid == characteristicUUID { debugPrint("DATA \(String(describing: characteristic.value?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: characteristic.value?.hexa()))") weakTestCase.publishTestResult(passed: true) return } @@ -185,6 +189,7 @@ class SILIOPGATTOperationsTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == characteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if data?.hexa() == exceptedValue { weakTestCase.publishTestResult(passed: true) } else { @@ -232,6 +237,7 @@ class SILIOPGATTOperationsTestHelper { case let .successWrite(characteristic: characteristic): if characteristic.uuid == characteristicUUID { debugPrint("DATA \(String(describing: characteristic.value?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: characteristic.value?.hexa()))") peripheralDelegate.readCharacteristic(characteristic: characteristic) return } @@ -241,6 +247,7 @@ class SILIOPGATTOperationsTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == characteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if data?.hexa() == exceptedValue { weakTestCase.publishTestResult(passed: true) } else { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPLengthVariableTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPLengthVariableTestHelper.swift index de349a4f..4c7b77ca 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPLengthVariableTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPLengthVariableTestHelper.swift @@ -102,6 +102,7 @@ class SILIOPLengthVariableTestHelper { case let .successWrite(characteristic: characteristic): if characteristic.uuid == weakSelf.testedCharacteristicUUID { debugPrint("DATA \(String(describing: characteristic.value?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: characteristic.value?.hexa()))") weakSelf.peripheralDelegate.readCharacteristic(characteristic: characteristic) return } @@ -111,6 +112,7 @@ class SILIOPLengthVariableTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == weakSelf.testedCharacteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if weakSelf.isFirstSubtest, data?.hexa() == weakSelf.exceptedValue_Subtest1 { weakSelf.isFirstSubtest = false if let dataToWrite = weakSelf.expectedValue_Subtest2.data(withCount: 4) { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPUserLenCharacteristicTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPUserLenCharacteristicTestHelper.swift index a8d7d222..d77e066c 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPUserLenCharacteristicTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_4/TestHelpers/SILIOPUserLenCharacteristicTestHelper.swift @@ -93,6 +93,7 @@ class SILIOPUserLenCharacteristicTestHelper { case let .successWrite(characteristic: characteristic): if characteristic.uuid == weakSelf.testedCharacteristicUUID { debugPrint("DATA \(String(describing: characteristic.value?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: characteristic.value?.hexa()))") weakSelf.peripheralDelegate.readCharacteristic(characteristic: characteristic) return } @@ -102,6 +103,7 @@ class SILIOPUserLenCharacteristicTestHelper { case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == weakSelf.testedCharacteristicUUID { debugPrint("DATA \(String(describing: data?.hexa()))") + IOPLog().iopLogSwiftFunction(message: "DATA \(String(describing: data?.hexa()))") if data?.hexa() == weakSelf.exceptedValue { weakSelf.testResult.value = TestResult(passed: true, description: "") } else { diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_5/TestCases/SILOTAAckTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_5/TestCases/SILOTAAckTestCase.swift index 1aa330aa..88ce317a 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_5/TestCases/SILOTAAckTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_5/TestCases/SILOTAAckTestCase.swift @@ -47,21 +47,25 @@ class SILOTAAckTestCase: SILTestCase { func performTestCase() { guard iopCentralManager.bluetoothState else { self.publishTestResult(passed: false, description: "Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") return } guard let _ = firmwareInfo else { self.testResult.value = SILTestResult(testID: self.testID, testName: self.testName, testStatus: .unknown(reason: "Firmware Info is nil.")) + IOPLog().iopLogSwiftFunction(message: "Firmware Info is nil.") return } guard firmwareInfo!.firmware != .unknown else { self.testResult.value = SILTestResult(testID: self.testID, testName: self.testName, testStatus: .unknown(reason: "Board not supported.")) + IOPLog().iopLogSwiftFunction(message: "Board not supported.") return } guard let _ = peripheral else { self.publishTestResult(passed: false, description: "Peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral is nil.") return } @@ -82,12 +86,14 @@ class SILOTAAckTestCase: SILTestCase { case let .disconnected(peripheral: peripheral, error: error): if peripheral === weakSelf.peripheral { debugPrint("DISCONNECTED WITH \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "DISCONNECTED WITH \(String(describing: error?.localizedDescription))") weakSelf.scanUsingBrowserBluetoothManager() } case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.otaUpdateManager = nil weakSelf.publishTestResult(passed: false, description: "Bluetooth disabled.") } @@ -96,6 +102,7 @@ class SILOTAAckTestCase: SILTestCase { break default: + IOPLog().iopLogSwiftFunction(message: "Unknown failure reason from IOP Central Manager.") weakSelf.publishTestResult(passed: false, description: "Unknown failure reason from IOP Central Manager.") } }) @@ -116,6 +123,7 @@ class SILOTAAckTestCase: SILTestCase { @objc private func didReceiveScanForPeripheralChange() { debugPrint("DID RECEIVE") + IOPLog().iopLogSwiftFunction(message: "DID RECEIVE") weak var weakSelf = self let discoveredPeripheral = browserCentralManager.discoveredPeripherals().first(where: { peripheral in guard let weakSelf = weakSelf else { return false } @@ -146,12 +154,15 @@ class SILOTAAckTestCase: SILTestCase { case .success: weakSelf.otaUpdateManager = nil weakSelf.invalidateObservableTokens() - - weakSelf.reconnectToDevice() - + UserDefaults.standard.setValue("IOP_Test_2", forKey: "deviceNameAfterOtaUpdate") + // weakSelf.reconnectToDevice(passed: true) + weakSelf.publishTestResult(passed: true) case let .failure(reason: reason): weakSelf.otaUpdateManager = nil - weakSelf.publishTestResult(passed: false, description: reason) + + UserDefaults.standard.setValue("IOP_Test_1", forKey: "deviceNameAfterOtaUpdate") + weakSelf.browserCentralManager.disconnect(from: self.peripheral ) + weakSelf.reconnectToDevice(passed: false,description: reason) case .unknown: break @@ -183,6 +194,7 @@ class SILOTAAckTestCase: SILTestCase { case .unknown: self.invalidateObservableTokens() self.testResult.value = SILTestResult(testID: self.testID, testName: self.testName, testStatus: .unknown(reason: "Unsupported board.")) + IOPLog().iopLogSwiftFunction(message: "Unsupported board.") } self.otaUpdateManager.startTest(for: boardID, firmwareVersion: firmwareInfo!.originalVersion) @@ -191,6 +203,7 @@ class SILOTAAckTestCase: SILTestCase { @objc private func scanIntervalTimerFired() { stopScanning() self.publishTestResult(passed: false, description: "Peripheral didn't found.") + IOPLog().iopLogSwiftFunction(message: "Peripheral didn't found.") } func stopScanning() { @@ -201,7 +214,7 @@ class SILOTAAckTestCase: SILTestCase { } } - private func reconnectToDevice() { + private func reconnectToDevice(passed: Bool, description: String? = nil) { weak var weakSelf = self let reconnectManager = SILIOPTestReconnectManager(with: peripheral, iopCentralManager: iopCentralManager) let reconnectManagerSubscription = reconnectManager.reconnectStatus.observe { reconnectStatus in @@ -212,8 +225,12 @@ class SILOTAAckTestCase: SILTestCase { weakSelf.peripheral = discoveredPeripheral?.peripheral weakSelf.firmwareVersionAfterOtaAckUpdate = SILIOPFirmwareVersion(version: stackVersion) weakSelf.invalidateObservableTokens() - weakSelf.publishTestResult(passed: true) + if passed{ + weakSelf.publishTestResult(passed: true) + }else{ + weakSelf.publishTestResult(passed: passed, description: description) + } case let .failure(reason: reason): weakSelf.publishTestResult(passed: false, description: reason) diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_6/TestCases/SILOTANonAckTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_6/TestCases/SILOTANonAckTestCase.swift index 710ebe36..14352b11 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_6/TestCases/SILOTANonAckTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_6/TestCases/SILOTANonAckTestCase.swift @@ -28,7 +28,10 @@ class SILOTANonAckTestCase: SILTestCase { private var deviceNameAfterOtaUpdate: String { get { - return firmwareInfo!.originalVersion.isLesserThan3_3_0() ? "IOP Test" : "IOP_Test_1" + + let deviceNameAfterOtaUpdate = UserDefaults.standard.value(forKey: "deviceNameAfterOtaUpdate") + + return firmwareInfo!.originalVersion.isLesserThan3_3_0() ? "IOP Test" : deviceNameAfterOtaUpdate as! String } } @@ -43,19 +46,26 @@ class SILOTANonAckTestCase: SILTestCase { func performTestCase() { guard let _ = firmwareInfo else { self.testResult.value = SILTestResult(testID: self.testID, testName: self.testName, testStatus: .unknown(reason: "Firmware Info is nil.")) + IOPLog().iopLogSwiftFunction(message: "Firmware Info is nil.") return } guard firmwareInfo!.firmware != .unknown else { self.testResult.value = SILTestResult(testID: self.testID, testName: self.testName, testStatus: .unknown(reason: "Board not supported.")) + IOPLog().iopLogSwiftFunction(message: "Board not supported.") return } guard let _ = peripheral else { self.publishTestResult(passed: false, description: "Peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral is nil.") return } + IOPLog().iopLogSwiftFunction(message: "\(firmwareInfo?.name ?? "")") + IOPLog().iopLogSwiftFunction(message: "\(firmwareInfo?.nameTag ?? "")") + //IOPLog().iopLogSwiftFunction(message: "\(firmwareInfo!.firmware)") + IOPLog().iopLogSwiftFunction(message: "\(String(describing: peripheral))") publishStartTestEvent() self.otaUpdateManager = SILIopTestOTAUpdateManger(with: self.peripheral, @@ -67,14 +77,21 @@ class SILOTANonAckTestCase: SILTestCase { guard let weakSelf = weakSelf else { return } switch status { case .success: + IOPLog().iopLogSwiftFunction(message: "Success OTA Non Ack TestCase\(status)") weakSelf.otaUpdateManager = nil weakSelf.invalidateObservableTokens() - weakSelf.reconnectToDevice() + UserDefaults.standard.setValue("IOP_Test_1", forKey: "deviceNameAfterOtaUpdate") + weakSelf.reconnectToDevice(passed: true) + + case let .failure(reason: reason): + IOPLog().iopLogSwiftFunction(message: "Failure OTA Non Ack TestCase\(reason)") weakSelf.otaUpdateManager = nil weakSelf.invalidateObservableTokens() - weakSelf.publishTestResult(passed: false, description: reason) + + weakSelf.browserCentralManager.disconnectConnectedPeripheral() + weakSelf.reconnectToDevice(passed: false,description: reason) case .unknown: break @@ -111,7 +128,7 @@ class SILOTANonAckTestCase: SILTestCase { self.otaUpdateManager.startTest(for: boardID, firmwareVersion: firmwareInfo!.originalVersion) } - private func reconnectToDevice() { + private func reconnectToDevice(passed: Bool, description: String? = nil) { weak var weakSelf = self let reconnectManager = SILIOPTestReconnectManager(with: peripheral, iopCentralManager: iopCentralManager) let reconnectManagerSubscription = reconnectManager.reconnectStatus.observe { reconnectStatus in @@ -122,8 +139,18 @@ class SILOTANonAckTestCase: SILTestCase { weakSelf.peripheral = discoveredPeripheral?.peripheral weakSelf.firmwareVersionAfterOtaAckUpdate = SILIOPFirmwareVersion(version: stackVersion) weakSelf.invalidateObservableTokens() - weakSelf.publishTestResult(passed: true) + IOPLog().iopLogSwiftFunction(message: "\(reconnectStatus)") + IOPLog().iopLogSwiftFunction(message: "\(String(describing: discoveredPeripheral))") + IOPLog().iopLogSwiftFunction(message: "\(String(describing: discoveredPeripheral?.peripheral))") + IOPLog().iopLogSwiftFunction(message: "\(SILIOPFirmwareVersion(version: stackVersion))") + + + if passed{ + weakSelf.publishTestResult(passed: true) + }else{ + weakSelf.publishTestResult(passed: passed, description: description) + } case let .failure(reason: reason): weakSelf.publishTestResult(passed: false, description: reason) @@ -134,6 +161,7 @@ class SILOTANonAckTestCase: SILTestCase { self.disposeBag.add(token: reconnectManagerSubscription) observableTokens.append(reconnectManagerSubscription) + reconnectManager.reconnectToDevice(withName: deviceNameAfterOtaUpdate) } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_7/TestCases/SILThroughputTestCase.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_7/TestCases/SILThroughputTestCase.swift index 8d77cc4d..c7f7d306 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_7/TestCases/SILThroughputTestCase.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_7/TestCases/SILThroughputTestCase.swift @@ -56,16 +56,19 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { func performTestCase() { guard iopCentralManager.bluetoothState else { self.publishTestResult(passed: false, description: "Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "FBluetooth disabled!") return } guard let _ = discoveredPeripheral else { self.publishTestResult(passed: false, description: "Discovered peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Discovered peripheral is nil.") return } guard let _ = peripheral else { self.publishTestResult(passed: false, description: "Peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral is nil.") return } @@ -80,11 +83,15 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("Peripheral disconnected with \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Peripheral disconnected with \(String(describing: error?.localizedDescription))") + weakSelf.publishTestResult(passed: false, description: "Peripheral was disconnected with \(String(describing: error?.localizedDescription)).") case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") + weakSelf.publishTestResult(passed: false, description: "Bluetooth disabled.") } @@ -93,6 +100,8 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { default: weakSelf.publishTestResult(passed: false, description: "Unknown failure from central manager.") + IOPLog().iopLogSwiftFunction(message: "Unknown failure from central manager.") + } }) disposeBag.add(token: centralManagerSubscription) @@ -109,6 +118,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { case let .successForServices(services): guard let iopTestPhase3Service = services.first(where: { service in service.uuid == weakSelf.iopTestPhase3Service }) else { weakSelf.publishTestResult(passed: false, description: "Service Test Phase 3 didn't found.") + IOPLog().iopLogSwiftFunction(message: "Service Test Phase 3 didn't found.") return } @@ -119,6 +129,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { characteristic.uuid == weakSelf.iopTestPhase3ThroughputGATT }) else { weakSelf.publishTestResult(passed: false, description: "Throughput characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Throughput characteristic didn't found.") return } @@ -132,6 +143,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { } weakSelf.publishTestResult(passed: false, description: "Failure when writing to a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to a characteristic.") case let .successGetValue(value: data, characteristic: characteristic): if characteristic.uuid == weakSelf.iopTestPhase3ThroughputGATT, let data = data { @@ -145,6 +157,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { } weakSelf.publishTestResult(passed: false, description: "Failure when notifying a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when notifying a characteristic.") case let .updateNotificationState(characteristic: characteristic, state: state): if characteristic.uuid == weakSelf.iopTestPhase3ThroughputGATT { @@ -154,6 +167,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { } else { let throughputSpped = Double(weakSelf.countCharacteristicThroughput / 5) debugPrint("Throughput speed: \(throughputSpped) kbps") + IOPLog().iopLogSwiftFunction(message: "Throughput speed: \(throughputSpped) kbps") let acceptableThroughput = weakSelf.calculateThroughput() if acceptableThroughput == 0 { @@ -172,6 +186,7 @@ class SILThroughputTestCase: SILTestCase, SILTestCaseTimeout { default: weakSelf.publishTestResult(passed: false, description: "Unknown failure from peripheral delegate.") + IOPLog().iopLogSwiftFunction(message: "Unknown failure from peripheral delegate.") } }) disposeBag.add(token: peripheralDelegateSubscription) diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPSecurityTestHelper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPSecurityTestHelper.swift index 0a924fd2..05ebe3ad 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPSecurityTestHelper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPSecurityTestHelper.swift @@ -46,6 +46,7 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { } func injectParameters(parameters: Dictionary) { + print(parameters) self.iopCentralManager = parameters["iopCentralManager"] as? SILIOPTesterCentralManager self.discoveredPeripheral = parameters["discoveredPeripheral"] as? SILDiscoveredPeripheral self.peripheral = parameters["peripheral"] as? CBPeripheral @@ -55,21 +56,25 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { func performTestCase() { guard iopCentralManager.bluetoothState else { self.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") return } guard let _ = discoveredPeripheral else { self.testResult.value = SecurityTestResult(passed: false, description: "Discovered peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Discovered peripheral is nil.") return } guard let _ = peripheral else { self.testResult.value = SecurityTestResult(passed: false, description: "Peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral is nil.") return } guard let _ = peripheralDelegate else { self.testResult.value = SecurityTestResult(passed: false, description: "Peripheral delegate is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral delegate is nil.") return } @@ -78,6 +83,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { guard let iopTestPhase3Service = self.peripheral.services?.first(where: { service in service.uuid == iopTestPhase3Service }) else { self.testResult.value = SecurityTestResult(passed: false, description: "Service Test Phase 3 didn't found.") + IOPLog().iopLogSwiftFunction(message: "Service Test Phase 3 didn't found.") + return } @@ -91,10 +98,13 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Not allowed disconnection.") case let .bluetoothEnabled(enabled: enabled): if !enabled { + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") debugPrint("Bluetooth disabled!") weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled.") } @@ -120,6 +130,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID }) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -128,6 +140,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { if weakSelf.initialValue == weakSelf.NotifyTest { guard pairingCharacteristic.properties.contains(.notify) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Characteristic doesn't have notify property.") + IOPLog().iopLogSwiftFunction(message: "Characteristic doesn't have notify property.") + return } weakSelf.peripheralDelegate.notifyCharacteristic(characteristic: pairingCharacteristic, enabled: true) @@ -152,28 +166,36 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to a characteristic.") + //ADDED NEW... case let .updateNotificationState(characteristic, _): if(characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID){ debugPrint("DID WRITE VALUE TO CCCD of characteristic\(characteristic)") + IOPLog().iopLogSwiftFunction(message: "DID WRITE VALUE TO CCCD of characteristic\(characteristic)") return } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to CCCD of characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to CCCD of characteristic.") //END case let .successWrite(characteristic: characteristic): if characteristic.uuid == weakSelf.iopTestPhase3Control { debugPrint("DID WRITE VALUE TO \(characteristic)") + IOPLog().iopLogSwiftFunction(message: "DID WRITE VALUE TO \(characteristic)") + return } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to a characteristic.") case .unknown: break default: weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Uknown failure from peripheral delegate.") + IOPLog().iopLogSwiftFunction(message: "Uknown failure from peripheral delegate.") } }) disposeBag.add(token: peripheralDelegateSubscription) @@ -189,6 +211,7 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { switch status { case let .connected(peripheral: peripheral): debugPrint("PERIPHERAL CONNECTED") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL CONNECTED") weakSelf.peripheral = peripheral weakSelf.connectionTimeout?.invalidate() weakSelf.peripheralDelegate.updatePeripheral(peripheral: peripheral) @@ -196,6 +219,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { case let .disconnected(peripheral: _, error: error): debugPrint("PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + weakSelf.pairingTimer?.invalidate() if weakSelf.retryCount > 0 { weakSelf.retryCount = weakSelf.retryCount - 1 @@ -209,10 +234,12 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { weakSelf.pairingTimer?.invalidate() weakSelf.connectionTimeout?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Fail to connect to peripheral with error \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Fail to connect to peripheral with error \(String(describing: error?.localizedDescription))") case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") weakSelf.connectionTimeout?.invalidate() weakSelf.pairingTimer?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled.") @@ -231,6 +258,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { connectionTimeout = nil iopCentralManager.disconnect(peripheral: peripheral) testResult.value = SecurityTestResult(passed: false, description: "Peripheral wasn't reconnected in 10 seconds.") + IOPLog().iopLogSwiftFunction(message: "Peripheral wasn't reconnected in 10 seconds.") + } private func reconnectedPeripheralDelegateSubscription() { @@ -247,12 +276,16 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Service Test Phase 3 didn't found.") + IOPLog().iopLogSwiftFunction(message: "Service Test Phase 3 didn't found.") + case let .successForCharacteristics(characteristics): guard let pairingCharacteristic = characteristics.first(where: { characteristic in characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID }) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -275,6 +308,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { descriptor.uuid.uuidString == CBUUIDClientCharacteristicConfigurationString }) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested descriptor didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested descriptor didn't found.") + return } weakSelf.peripheralDelegate.readDescriptor(descriptor: pairingDescriptor) @@ -282,11 +317,15 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { case let .successGetValueDescriptor(value: data, descriptor: descriptor): guard descriptor.uuid.uuidString == CBUUIDClientCharacteristicConfigurationString else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested descriptor didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested descriptor didn't found.") + return } let valueDescriptor = (data as? NSNumber)?.stringValue if valueDescriptor != weakSelf.exceptedValue { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Wrong value in Client Characteristic Configuration Descriptor.") + IOPLog().iopLogSwiftFunction(message: "Wrong value in Client Characteristic Configuration Descriptor.") + return } weakSelf.peripheralDelegate.notifyCharacteristic(characteristic: descriptor.characteristic!, enabled: false) @@ -294,6 +333,8 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { case let .successGetValue(value: data, characteristic: characteristic): guard characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -303,22 +344,29 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { weakSelf.testResult.value = SecurityTestResult(passed: true, description: "") } else if weakSelf.retryCount == 0 { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Wrong value in a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Wrong value in a characteristic.") } //ADDED NEW case let .updateNotificationState(characteristic, _): if(characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID){ debugPrint("DID WRITE VALUE TO CCCD of characteristic\(characteristic)") + IOPLog().iopLogSwiftFunction(message: "DID WRITE VALUE TO CCCD of characteristic\(characteristic)") + weakSelf.testResult.value = SecurityTestResult(passed: true, description: "") return } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to CCCD of characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to CCCD of characteristic.") + //END case .failure(error: _): weakSelf.pairingTimer?.invalidate() if weakSelf.retryCount == 0 { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Exceeded an allowed number of attempts.") + IOPLog().iopLogSwiftFunction(message: "Exceeded an allowed number of attempts.") + } case .unknown: @@ -327,6 +375,7 @@ class SILIOPSecurityTestHelper: SILTestCaseWithRetries { default: weakSelf.pairingTimer?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Unknown failure from peripheral delegate.") + IOPLog().iopLogSwiftFunction(message: "Unknown failure from peripheral delegate.") } }) disposeBag.add(token: peripheralDelegateSubscription) diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPTester_Test8.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPTester_Test8.swift index bf6a3f2f..6f76029f 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPTester_Test8.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_8/SILIOPTester_Test8.swift @@ -24,7 +24,7 @@ class SILIOPTester_Test8: SILTestScenario { appendTestCase(testCase: SILSecurity_7_2TestCase()) appendTestCase(testCase: SILSecurity_7_3TestCase()) appendTestCase(testCase: SILSecurity_7_4TestCase()) - appendTestCase(testCase: SILSecurity_7_5TestCase()) //Added + appendTestCase(testCase: SILSecurity_7_5TestCase()) testResults.value = privTestResults } diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_9/SILIOPLEPrivacyHealper.swift b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_9/SILIOPLEPrivacyHealper.swift index 38c295e4..26a18d08 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_9/SILIOPLEPrivacyHealper.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/TestScenario/Test_9/SILIOPLEPrivacyHealper.swift @@ -54,21 +54,25 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { func performTestCase() { guard iopCentralManager.bluetoothState else { self.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") return } guard let _ = discoveredPeripheral else { self.testResult.value = SecurityTestResult(passed: false, description: "Discovered peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Discovered peripheral is nil.") return } guard let _ = peripheral else { self.testResult.value = SecurityTestResult(passed: false, description: "Peripheral is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral is nil.") return } guard let _ = peripheralDelegate else { self.testResult.value = SecurityTestResult(passed: false, description: "Peripheral delegate is nil.") + IOPLog().iopLogSwiftFunction(message: "Peripheral delegate is nil.") return } @@ -77,6 +81,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { guard let iopTestPhase3Service = self.peripheral.services?.first(where: { service in service.uuid == iopTestPhase3Service }) else { self.testResult.value = SecurityTestResult(passed: false, description: "Service Test Phase 3 didn't found.") + IOPLog().iopLogSwiftFunction(message: "Service Test Phase 3 didn't found.") + return } @@ -90,11 +96,17 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { switch status { case let .disconnected(peripheral: _, error: error): debugPrint("PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Not allowed disconnection.") + IOPLog().iopLogSwiftFunction(message: "Not allowed disconnection.") + case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") + weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled.") } @@ -119,6 +131,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID }) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -140,20 +154,28 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to a characteristic.") + case let .successWrite(characteristic: characteristic): if characteristic.uuid == weakSelf.iopTestPhase3Control { debugPrint("DID WRITE VALUE TO \(characteristic)") + IOPLog().iopLogSwiftFunction(message: "DID WRITE VALUE TO \(characteristic)") + return } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Failure when writing to a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Failure when writing to a characteristic.") + case .unknown: break default: weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Uknown failure from peripheral delegate.") + IOPLog().iopLogSwiftFunction(message: "Uknown failure from peripheral delegate.") + } }) disposeBag.add(token: peripheralDelegateSubscription) @@ -168,6 +190,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { self.iopCentralManager.connect(to: self.discoveredPeripheral) } else { self.testResult.value = SecurityTestResult(passed: false, description: "Exceeded an allowed number of attempts.") + IOPLog().iopLogSwiftFunction(message: "Exceeded an allowed number of attempts.") + } } @@ -178,6 +202,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { switch status { case let .connected(peripheral: peripheral): debugPrint("PERIPHERAL CONNECTED") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL CONNECTED") + weakSelf.peripheral = peripheral weakSelf.connectionTimeout?.invalidate() weakSelf.peripheralDelegate.updatePeripheral(peripheral: peripheral) @@ -185,6 +211,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { weakSelf.reconnectedPeripheralDelegateSubscription() case let .disconnected(peripheral: _, error: error): debugPrint("PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "PERIPHERAL DISCONNECTED WITH ERROR \(String(describing: error?.localizedDescription))") + weakSelf.pairingTimer?.invalidate() if weakSelf.retryCount > 0 { weakSelf.retryCount = weakSelf.retryCount - 1 @@ -192,16 +220,22 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { weakSelf.iopCentralManager.connect(to: weakSelf.discoveredPeripheral) } else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Exceeded an allowed number of attempts.") + IOPLog().iopLogSwiftFunction(message: "Exceeded an allowed number of attempts.") + } case let .failToConnect(peripheral: _, error: error): weakSelf.pairingTimer?.invalidate() weakSelf.connectionTimeout?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Fail to connect to peripheral with error \(String(describing: error?.localizedDescription))") + IOPLog().iopLogSwiftFunction(message: "Fail to connect to peripheral with error \(String(describing: error?.localizedDescription))") + case let .bluetoothEnabled(enabled: enabled): if !enabled { debugPrint("Bluetooth disabled!") + IOPLog().iopLogSwiftFunction(message: "Bluetooth disabled!") + weakSelf.connectionTimeout?.invalidate() weakSelf.pairingTimer?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Bluetooth disabled.") @@ -220,6 +254,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { connectionTimeout = nil iopCentralManager.disconnect(peripheral: peripheral) testResult.value = SecurityTestResult(passed: false, description: "Peripheral wasn't reconnected in 10 seconds.") + IOPLog().iopLogSwiftFunction(message: "Peripheral wasn't reconnected in 10 seconds") + } private func reconnectedPeripheralDelegateSubscription() { @@ -236,12 +272,16 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { } weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Service Test Phase 3 didn't found.") + IOPLog().iopLogSwiftFunction(message: "Service Test Phase 3 didn't found.") + case let .successForCharacteristics(characteristics): guard let pairingCharacteristic = characteristics.first(where: { characteristic in characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID }) else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -252,6 +292,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { case let .successGetValue(value: data, characteristic: characteristic): guard characteristic.uuid == weakSelf.iopTestPhase3TestedCharacteristicUUID else { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Tested characteristic didn't found.") + IOPLog().iopLogSwiftFunction(message: "Tested characteristic didn't found.") + return } @@ -261,6 +303,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { weakSelf.testResult.value = SecurityTestResult(passed: true, description: "") } else if weakSelf.retryCount == 0 { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Wrong value in a characteristic.") + IOPLog().iopLogSwiftFunction(message: "Wrong value in a characteristic.") + } case .failure(error: _): @@ -268,6 +312,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { if weakSelf.retryCount == 0 { weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Exceeded an allowed number of attempts.") + IOPLog().iopLogSwiftFunction(message: "Exceeded an allowed number of attempts.") + } case .unknown: @@ -276,6 +322,8 @@ class SILIOPLEPrivacyHealper: SILTestCaseWithRetries { default: weakSelf.pairingTimer?.invalidate() weakSelf.testResult.value = SecurityTestResult(passed: false, description: "Unknown failure from peripheral delegate.") + IOPLog().iopLogSwiftFunction(message: "Unknown failure from peripheral delegate.") + } }) disposeBag.add(token: peripheralDelegateSubscription) diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.swift b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.swift new file mode 100644 index 00000000..456cdaf0 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.swift @@ -0,0 +1,22 @@ +// +// SILIOPDeviceResetInfoPopupViewController.swift +// BlueGecko +// +// Created by SovanDas Maity on 11/07/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILIOPDeviceResetInfoPopupViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + @IBAction func okBtn(_ sender: UIButton) { + self.dismiss(animated: false, completion: nil) + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.xib b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.xib new file mode 100644 index 00000000..a5d0e597 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPDeviceResetInfoPopupViewController.xib @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift index 5322241c..5a0760df 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift @@ -11,7 +11,7 @@ import UIKit @objc @objcMembers -class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { +class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, SILIOPTesterViewModelDelegate { @IBOutlet weak var allSpace: UIStackView! @IBOutlet weak var tableView: UITableView! @@ -153,15 +153,66 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab } @objc func shareTestResult() { - guard let viewModel = viewModel, currentTestState != .initiated else { return } - let filesToShare = [viewModel.getReportFile() as Any] as [Any] - let iopTestLogSubject = "IOP Test Log" - let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil) - activityViewController.setValue(iopTestLogSubject, forKey: "Subject") - activityViewController.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + let alert = UIAlertController(title: "Select log file.", message: "", preferredStyle: .alert) + let debugLog = UIAlertAction(title: "Application Debug Log", style: .default) { (action) in + self.shareLogFile(logType: "ConsoleLog") + } + + let resultLog = UIAlertAction(title: "Test Result Log", style: .default) { (action) in + self.shareLogFile(logType: "UILog") + } - self.present(activityViewController, animated: true, completion: nil) + + + let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: nil) + alert.addAction(resultLog) + alert.addAction(debugLog) + alert.addAction(cancelAction) + //alert.popoverPresentationController?.sourceView = self.btnShare + self.present(alert, animated: true, completion: nil) + + + //Console: +// let fileSh = viewModel.getConsolLogsFile() +// +//// if let file = viewModel.getMeshLogsFile() { +//// self.shareTestResultTemp(fileURL: file, fileName: "Application/Mesh Logs") +//// } +// +// +// let iopTestLogSubject = "IOP Test Log" +// +// let activityViewController = UIActivityViewController(activityItems: [fileSh as Any], applicationActivities: nil) +// activityViewController.setValue(iopTestLogSubject, forKey: "Subject") +// activityViewController.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem +// +// self.present(activityViewController, animated: true, completion: nil) + } + + func shareLogFile(logType: String) { + guard let viewModel = viewModel, currentTestState != .initiated else { return } + if logType == "UILog" { + viewModel.prepareTestReport() + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + var filesToShare:[Any] = [] + if logType == "UILog" { + filesToShare = [viewModel.getReportFile() as Any] + }else if logType == "ConsoleLog" { + filesToShare = [viewModel.getConsolLogsFile() as Any] + } + + let iopTestLogSubject = "IOP Test Log" + + let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil) + activityViewController.setValue(iopTestLogSubject, forKey: "Subject") + activityViewController.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + + self.present(activityViewController, animated: true, completion: nil) + } + } private func showBluetoothDisabledAlert() { @@ -176,6 +227,8 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab func setupViewModel() { guard let deviceName = self.deviceNameToSearch else { return } self.viewModel = SILIOPTesterViewModel(deviceNameToSearch: deviceName) + + self.viewModel?.SILIOPTesterViewModelDelegate = self } func showDocumentPickerView() { @@ -218,6 +271,7 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab extension SILIOPTesterViewController: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { debugPrint("DID PICK") + IOPLog().iopLogSwiftFunction(message: "DID PICK") self.sendChosenUrl(urls: urls) } @@ -231,7 +285,20 @@ extension SILIOPTesterViewController: UIDocumentPickerDelegate { func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { debugPrint("DID CANCEL") + IOPLog().iopLogSwiftFunction(message: "DID CANCEL PICKER") NotificationCenter.default.post(Notification(name: .SILIOPFileUrlChosen, object: nil, userInfo: nil)) controller.dismiss(animated: true, completion: nil) } } +//MARK: SILIOPTesterViewModelDelegate +extension SILIOPTesterViewController { + func notifyAfterAllTest() { + print("END") + DispatchQueue.main.async { + let SILIOPDeviceResetInfoPopupViewControllerObj = SILIOPDeviceResetInfoPopupViewController(nibName: "SILIOPDeviceResetInfoPopupViewController", bundle: nil) + SILIOPDeviceResetInfoPopupViewControllerObj.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext + self.present(SILIOPDeviceResetInfoPopupViewControllerObj, animated: false) + } + + } +} diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/ViewModel/SILIOPTesterViewModel.swift b/SiliconLabsApp/ViewControllers/IOP Test App/ViewModel/SILIOPTesterViewModel.swift index 28d94593..e80be582 100644 --- a/SiliconLabsApp/ViewControllers/IOP Test App/ViewModel/SILIOPTesterViewModel.swift +++ b/SiliconLabsApp/ViewControllers/IOP Test App/ViewModel/SILIOPTesterViewModel.swift @@ -9,6 +9,11 @@ import Foundation import CoreBluetooth import UIKit +import CocoaLumberjack + +protocol SILIOPTesterViewModelDelegate { + func notifyAfterAllTest() +} class SILIOPTesterViewModel: NSObject, ObservableObject { private var iopCentralManager: SILIOPTesterCentralManager = SILIOPTesterCentralManager() @@ -42,6 +47,7 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { case ended } + var SILIOPTesterViewModelDelegate:SILIOPTesterViewModelDelegate? var testStateStatus: SILObservable = SILObservable(initialValue: .initiated) var bluetoothState: SILObservable = SILObservable(initialValue: true) @@ -100,7 +106,9 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { var testCaseResults = [SILTestResult]() for testScenario in iopTest { let testCaseStatuses: [SILTestStatus] = testScenario.tests.map { _ in return .waiting } + print(testScenario.tests) allTestCases += testScenario.tests.count + print(allTestCases) for testCase in testScenario.tests { testCaseResults.append(SILTestResult(testID: testCase.testID, testName: testCase.testName, testStatus: .waiting)) @@ -135,14 +143,25 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { let failureStatus = SILTestStatus.failed(reason: SILTestFailureReason(description: "Mandatory test \(testID) failed.")) testCaseResults.markTestAfterIndex(indexOfFailedTestID, with: failureStatus) } - + private func prepareLoggerForTesting() { + //ViewModelServices.sharedInstance.bluetoothMeshNetworkManager.dropDatabase() + IOPLogFilePrinter.clearLogDir() + if let fileLogger = DDLog.allLoggers.last as? DDFileLogger { + fileLogger.rollLogFile(withCompletion: { + print("File rolled") + }) + } + } func startTest() { + prepareLoggerForTesting() createNewIOPTest() setInitialUIState() timestamp = Date.init() testStateStatus.value = .running debugPrint("START TEST") + IOPLog().iopLogSwiftFunction(message: "START TEST") + testParameters = ["iopCentralManager": self.iopCentralManager, "browserCentralManager": self.browserCentralManager, "peripheralLocalName": self.deviceNameToSearch] as [String : Any] @@ -154,11 +173,16 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { observableTokens.append(iopTest[i].testResults.observe({ testResults in if testResults.isEmpty { return } guard let weakSelf = weakSelf else { return } + print("SOVAN TEST: \(self.iopTest[i])") + print("SOVAN TEST RESULT: \(testResults)") + print(i) weakSelf.printTestResultInfo(testResults) let newTestCaseStatuses: [SILTestStatus] = testResults.map { testResult in weakSelf.testCaseResults.update(newTestResult: testResult) return testResult.testStatus } + + weakSelf.cellViewModels[i].update(newTestCaseStatuses: newTestCaseStatuses) weakSelf.updateTableViewWithCurrentTestScenarioIndex.value = i weakSelf.inProgressTestCases = weakSelf.testCaseResults.testInProgressCount() @@ -176,11 +200,17 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { case .failed(reason: _), .unknown(reason: _): + if weakSelf.iopTest[i].isMandatory { weakSelf.markRestTestsAsFailed(fromTestAtIndex: i + 1, andfromTestID: testResults.last!.testID) weakSelf.endTesting() } else { weakSelf.runNextTestIfPossible(index: i) + if i == 6 { + //print(i) + weakSelf.markRestTestsAsFailed(fromTestAtIndex: 6 + 1, andfromTestID: testResults.last!.testID) + weakSelf.markRestTestsAsFailed(fromTestAtIndex: 8 + 1, andfromTestID: testResults.last!.testID) + } } default: @@ -196,6 +226,9 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { private func printTestResultInfo(_ testResults: [SILTestResult]) { for testResult in testResults { var testResultText = "TEST RESULT \(testResult.testID) \(testResult.testName) \(testResult.testStatus.rawValue)" + + IOPLog().iopLogSwiftFunction(message: "TEST RESULT \(testResult.testID) \(testResult.testName) \(testResult.testStatus.rawValue)") + print(testResult) switch testResult.testStatus { case let .passed(details: details): if let details = details { @@ -236,8 +269,8 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { updateParametersDictionary(newArtifacts: dict, testIndex: i) iopTest[i].invalidateObservableTokens() - print(iopTest) - print(iopTest.count) + //print(iopTest) + // print(iopTest.count) if i + 1 < iopTest.count { iopTest[i + 1].injectParameters(parameters: testParameters) iopTest[i + 1].performTestScenario() @@ -297,12 +330,18 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { func endTesting() { debugPrint("END TESTING") + + IOPLog().iopLogSwiftFunction(message: "END TEST") + stopTest() prepareTestReport() testStateStatus.value = .ended + SILIOPTesterViewModelDelegate?.notifyAfterAllTest() + } - private func prepareTestReport() { + func prepareTestReport() { + IOPLog().iopLogSwiftFunction(message: "END TEST") let deviceSystemVersion = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)" testReport = SILIOPTestReport(timestamp: timestamp ?? Date(), @@ -313,18 +352,27 @@ class SILIOPTesterViewModel: NSObject, ObservableObject { } func getReportFile() -> URL { - let fileWriter = SILIOPFileWriter(firmware: firmwareInfo?.firmware ?? .unknown, - timestamp: timestamp ?? Date(), - deviceModelName: deviceModelName) - - if fileWriter.createEmptyFile(atPath: fileWriter.getFilePath), let testReport = testReport { - let report = testReport.generateReport() - if fileWriter.openFile(filePath: fileWriter.getFilePath) { - _ = fileWriter.append(text: report) - fileWriter.closeFile() + let fileWriter = SILIOPFileWriter(firmware: self.firmwareInfo?.firmware ?? .unknown, + timestamp: self.timestamp ?? Date(), + deviceModelName: self.deviceModelName) + + + if fileWriter.createEmptyFile(atPath: fileWriter.getFilePath), let testReport = self.testReport { + let report = testReport.generateReport() + if fileWriter.openFile(filePath: fileWriter.getFilePath) { + _ = fileWriter.append(text: report) + fileWriter.closeFile() + } } - } + return fileWriter.getFileUrl } + + func getConsolLogsFile() -> URL? { + if let fileLogger = DDLog.allLoggers.last as? DDFileLogger { + return URL(fileURLWithPath: fileLogger.currentLogFileInfo!.filePath) + } + return nil + } } diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/APIService/APIRequest.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/APIService/APIRequest.swift new file mode 100644 index 00000000..ecf636a9 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/APIService/APIRequest.swift @@ -0,0 +1,79 @@ +// +// APIRequest.swift +// IPAddressDemo +// +// Created by SovanDas Maity on 24/06/24. +// + +import Foundation + +enum HttpMethods: String { + case POST = "POST" + case GET = "GET" + } +struct ApiHTTPrequest { + +} + +class APIRequest { + static let sharedInstance = APIRequest() + + func postApiCall(parameterDictionary: String, url : String, completionBlock: @escaping (_ ReponsData: Data?, _ APIClientError:Error?) -> Void) { + + let Url = String(format: "http://192.168.10.10/\(url)") + guard let serviceUrl = URL(string: Url) else { return } + var request = URLRequest(url: serviceUrl) + request.httpMethod = HttpMethods.POST.rawValue + request.setValue("Application/json", forHTTPHeaderField: "Content-Type") + request.timeoutInterval = 10 + let parameters = parameterDictionary + let postData = parameters.data(using: .utf8) + + request.httpBody = postData + let session = URLSession.shared + session.dataTask(with: request) { (data, response, error) in + if let response = response { + print(response) + if let httpResponse = response as? HTTPURLResponse { + if httpResponse.statusCode == 200 { + completionBlock(data, nil) + }else{ + completionBlock(nil, error) + } + }else{ + completionBlock(nil, error) + } + }else{ + completionBlock(nil, error) + } + + }.resume() + } + + func getApiCall(url : String, completionBlock: @escaping (_ ReponsData: Data?, _ APIClientError:Error?) -> Void) { + let Url = String(format: "http://192.168.10.10/\(url)") + guard let serviceUrl = URL(string: Url) else { return } + var request = URLRequest(url: serviceUrl) + request.httpMethod = HttpMethods.GET.rawValue + request.setValue("Application/json", forHTTPHeaderField: "Content-Type") + request.timeoutInterval = 10 + let session = URLSession.shared + session.dataTask(with: request) { (data, response, error) in + if let response = response { + print(response) + if let httpResponse = response as? HTTPURLResponse { + if httpResponse.statusCode == 200 { + completionBlock(data, nil) + }else{ + completionBlock(nil, error) + } + }else{ + completionBlock(nil, error) + } + }else{ + completionBlock(nil, error) + } + + }.resume() + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiLEDViewController.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiLEDViewController.swift new file mode 100644 index 00000000..9c91bfdb --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiLEDViewController.swift @@ -0,0 +1,441 @@ +// +// SILWiFiLEDViewController.swift +// BlueGecko +// +// Created by SovanDas Maity on 27/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit +import SVProgressHUD + +class SILWiFiLEDViewController: UIViewController { + @IBOutlet weak var blubImg: UIImageView! + @IBOutlet weak var redColorBtn: UIButton! + @IBOutlet weak var greenColorBtn: UIButton! + @IBOutlet weak var blueColorBtn: UIButton! + @IBOutlet weak var onBtn: UIButton! + @IBOutlet weak var offBtn: UIButton! + + + + var silWiFiLedSensorsViewModelObject:SILWiFiLedSensorsViewModel = SILWiFiLedSensorsViewModel() + var redColor: Bool = false + var greenColor: Bool = false + var blueColor: Bool = false + var redColorVlue: String = "on" + var greenColorVlue: String = "on" + var blueColorVlue: String = "on" + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + //blubImg.image = LedImage.ledOnImage + self.onBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + ledStatus() + } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + redColorVlue = "on" + greenColorVlue = "on" + blueColorVlue = "on" + SVProgressHUD.show(withStatus: "Connecting") + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in + getLedStatus() + } + } + func ledStatus(){ + //{"status_led": "on/off"} + self.silWiFiLedSensorsViewModelObject.statusLed(requestMethod: HttpMethods.GET.rawValue) { [self] sensorsData, APIClientError in + if APIClientError == nil{ + if let statusValue: String = sensorsData?["status_led"] as? String{ + print(statusValue) + if statusValue == "on" { + postLedStatus() + } + } + } + } + } + func postLedStatus(){ + self.silWiFiLedSensorsViewModelObject.statusLed(requestMethod: HttpMethods.POST.rawValue) { sensorsData, APIClientError in + if APIClientError == nil{ + print(sensorsData) + } + } + } + + func getLedStatus() { + silWiFiLedSensorsViewModelObject.getLedData { sensorsData, APIClientError in + DispatchQueue.main.async { + SVProgressHUD.dismiss() + } + if APIClientError == nil{ + + if let ledDic: Dictionary = sensorsData { + self.stateOfLed(ledDic: ledDic) + } + } + } + } + func stateOfLed(ledDic: Dictionary ){ + self.redColorVlue = "\(ledDic["red"] ?? "")" + self.greenColorVlue = "\(ledDic["green"] ?? "")" + self.blueColorVlue = "\(ledDic["blue"] ?? "")" + if "\(ledDic["red"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOnState.rawValue{ + self.setLedStatus(type: LedType.ledOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOffState.rawValue { + self.setLedStatus(type: LedType.ledOff.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOffState.rawValue { + self.setLedStatus(type: LedType.redOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOffState.rawValue { + self.setLedStatus(type: LedType.greenOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOnState.rawValue { + self.setLedStatus(type: LedType.blueOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOffState.rawValue { + self.setLedStatus(type: LedType.redGreenOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOnState.rawValue { + self.setLedStatus(type: LedType.redBlueOn.rawValue) + }else if "\(ledDic["red"] ?? "")" == LedStatus.ledOffState.rawValue && "\(ledDic["green"] ?? "")" == LedStatus.ledOnState.rawValue && "\(ledDic["blue"] ?? "")" == LedStatus.ledOnState.rawValue { + self.setLedStatus(type: LedType.greenBuleOn.rawValue) + } + } + + func setLedStatus(type: String){ + var ledImage = UIImage() + var ledColor = UIColor() + + switch type { + case LedType.ledOn.rawValue: + ledImage = LedImage.ledOnImage ?? UIImage() + DispatchQueue.main.async { + //self.redColorCheckImg.image = LedImage.checkBoxActiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_siliconLabsRedColor") + self.redColor = true + //self.greenColorCheckImg.image = LedImage.checkBoxActiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_regularGreenColor") + self.greenColor = true + //self.blueColorCheckImg.image = LedImage.checkBoxActiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_regularBlueColor") + self.blueColor = true + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.ledOff.rawValue: + ledImage = LedImage.ledOffImage ?? UIImage() + DispatchQueue.main.async { + //self.redColorCheckImg.image = LedImage.checkBoxInactiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.redColor = false + //self.greenColorCheckImg.image = LedImage.checkBoxInactiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.greenColor = false + //self.blueColorCheckImg.image = LedImage.checkBoxInactiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.blueColor = false + + self.onBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.offBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + } + case LedType.redOn.rawValue: + //ledImage = LedImage.redLedOnImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 255.0, green: 0.0, blue: 0.0, alpha: 1) + DispatchQueue.main.async { + //self.redColorCheckImg.image = LedImage.checkBoxActiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_siliconLabsRedColor") + self.redColor = true + //self.greenColorCheckImg.image = LedImage.checkBoxInactiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.greenColor = false + //self.blueColorCheckImg.image = LedImage.checkBoxInactiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.blueColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.greenOn.rawValue: + //ledImage = LedImage.greenLedOnImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 0.0, green: 255.0, blue: 0.0, alpha: 1) + DispatchQueue.main.async { + //self.greenColorCheckImg.image = LedImage.checkBoxActiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_regularGreenColor") + self.greenColor = true + //self.redColorCheckImg.image = LedImage.checkBoxInactiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.redColor = false + //self.blueColorCheckImg.image = LedImage.checkBoxInactiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.blueColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.blueOn.rawValue: + //ledImage = LedImage.blueLedOnImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 0.0, green: 0.0, blue: 255.0, alpha: 1) + DispatchQueue.main.async { + //self.blueColorCheckImg.image = LedImage.checkBoxActiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_regularBlueColor") + self.blueColor = true + //self.redColorCheckImg.image = LedImage.checkBoxInactiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.redColor = false + //self.greenColorCheckImg.image = LedImage.checkBoxInactiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.greenColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.redGreenOn.rawValue: + //ledImage = LedImage.yellowLedImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 255.0, green: 255.0, blue: 0.0, alpha: 1) + DispatchQueue.main.async { + //self.redColorCheckImg.image = LedImage.checkBoxActiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_siliconLabsRedColor") + self.redColor = true + //self.greenColorCheckImg.image = LedImage.checkBoxActiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_regularGreenColor") + self.greenColor = true + //self.blueColorCheckImg.image = LedImage.checkBoxInactiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.blueColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.redBlueOn.rawValue: + //ledImage = LedImage.magentaLedImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 255.0, green: 0.0, blue: 255.0, alpha: 1) + DispatchQueue.main.async { + //self.redColorCheckImg.image = LedImage.checkBoxActiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_siliconLabsRedColor") + self.redColor = true + //self.blueColorCheckImg.image = LedImage.checkBoxActiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_regularBlueColor") + self.blueColor = true + //self.greenColorCheckImg.image = LedImage.checkBoxInactiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.greenColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + case LedType.greenBuleOn.rawValue: + //ledImage = LedImage.cyanLedImage ?? UIImage() + ledImage = LedImage.blubOffTint ?? UIImage() + ledColor = UIColor(red: 0.0, green: 255.0, blue: 255.0, alpha: 1) + DispatchQueue.main.async { + //self.greenColorCheckImg.image = LedImage.checkBoxActiveImage + self.greenColorBtn.backgroundColor = UIColor(named: "sil_regularGreenColor") + self.greenColor = true + //self.blueColorCheckImg.image = LedImage.checkBoxActiveImage + self.blueColorBtn.backgroundColor = UIColor(named: "sil_regularBlueColor") + self.blueColor = true + //self.redColorCheckImg.image = LedImage.checkBoxInactiveImage + self.redColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.redColor = false + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + } + default: + print("Have you done something new?") + } + DispatchQueue.main.async { + self.blubImg.image = ledImage + if type != LedType.ledOn.rawValue && type != LedType.ledOff.rawValue { + self.blubImg.tintColor = ledColor + } + } + } + @IBAction func Led_On(_ sender: Any) { + redColorVlue = "on" + greenColorVlue = "on" + blueColorVlue = "on" + self.onBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + self.offBtn.backgroundColor = UIColor(named: "sil_boulderColor") + ledControll() + } + + func ledControll() { + let paramStr = """ + {"red": "\(redColorVlue)", "green": "\(greenColorVlue)", "blue": "\(blueColorVlue)"} + """ + silWiFiLedSensorsViewModelObject.ledOnOf(ledType: LedType.ledOn.rawValue, parameter: paramStr, urlEndpoint: "led") { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + print(ledDic) +// self.redColorVlue = "\(ledDic["red"] ?? "")" +// self.greenColorVlue = "\(ledDic["green"] ?? "")" +// self.blueColorVlue = "\(ledDic["blue"] ?? "")" +// self.setLedStatus(type: LedType.ledOn.rawValue) + self.stateOfLed(ledDic: ledDic) + }else{ + + } + } + } + } + + @IBAction func Led_off(_ sender: Any) { + redColorVlue = "off" + greenColorVlue = "off" + blueColorVlue = "off" + self.onBtn.backgroundColor = UIColor(named: "sil_boulderColor") + self.offBtn.backgroundColor = UIColor(named: "sil_primaryTextColor") + ledControll() +// silWiFiLedSensorsViewModelObject.ledOnOf(ledType: LedType.ledOff.rawValue, parameter: """ +// {"red": "off", "green": "off", "blue": "off"} +// """, urlEndpoint: "led") { sensorsData, APIClientError in +// if APIClientError == nil{ +// if let ledDic: Dictionary = sensorsData { +// print(ledDic) +//// self.redColorVlue = "\(ledDic["red"] ?? "")" +//// self.greenColorVlue = "\(ledDic["green"] ?? "")" +//// self.blueColorVlue = "\(ledDic["blue"] ?? "")" +//// self.setLedStatus(type: LedType.ledOff.rawValue) +// self.stateOfLed(ledDic: ledDic) +// } +// } +// } + } + + @IBAction func Led_Red(_ sender: Any) { + silWiFiLedSensorsViewModelObject.ledOnOf(ledType: LedType.redOn.rawValue, parameter: """ + {"red": "on", "green": "off", "blue": "off"} + """, urlEndpoint: "led") { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + print(ledDic) + self.redColorVlue = "\(ledDic["red"] ?? "")" + self.greenColorVlue = "\(ledDic["green"] ?? "")" + self.blueColorVlue = "\(ledDic["blue"] ?? "")" + self.setLedStatus(type: LedType.redOn.rawValue) + } + } + } + } + + + @IBAction func Led_blue(_ sender: Any) { + silWiFiLedSensorsViewModelObject.ledOnOf(ledType: LedType.blueOn.rawValue, parameter: """ + {"red": "off", "green": "off", "blue": "on"} + """, urlEndpoint: "led") { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + print(ledDic) + self.redColorVlue = "\(ledDic["red"] ?? "")" + self.greenColorVlue = "\(ledDic["green"] ?? "")" + self.blueColorVlue = "\(ledDic["blue"] ?? "")" + self.setLedStatus(type: LedType.blueOn.rawValue) + } + } + } + } + + + @IBAction func Led_Green(_ sender: Any) { + silWiFiLedSensorsViewModelObject.ledOnOf(ledType: LedType.blueOn.rawValue, parameter: """ + {"red": "off", "green": "on", "blue": "off"} + """, urlEndpoint: "led") { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + print(ledDic) + self.redColorVlue = "\(ledDic["red"] ?? "")" + self.greenColorVlue = "\(ledDic["green"] ?? "")" + self.blueColorVlue = "\(ledDic["blue"] ?? "")" + self.setLedStatus(type: LedType.greenOn.rawValue) + } + } + } + } + + @IBAction func colorCheckBtn(_ sender: UIButton) { + if sender.tag == 1 { + if redColor { + //redColorCheckImg.image = LedImage.checkBoxInactiveImage + //redColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + redColor = false + redColorVlue = "off" + if !greenColor { + greenColorVlue = "off" + } + if !blueColor { + blueColorVlue = "off" + } + + }else{ + //redColorCheckImg.image = LedImage.checkBoxActiveImage + //redColorBtn.backgroundColor = UIColor(named: "sil_siliconLabsRedColor") + redColor = true + redColorVlue = "on" + if !greenColor { + greenColorVlue = "off" + } + if !blueColor { + blueColorVlue = "off" + } + } + }else if sender.tag == 2{ + if greenColor { + //greenColorCheckImg.image = LedImage.checkBoxInactiveImage + //greenColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + greenColor = false + greenColorVlue = "off" + if !redColor { + redColorVlue = "off" + } + if !blueColor { + blueColorVlue = "off" + } + + }else{ + //greenColorCheckImg.image = LedImage.checkBoxActiveImage + //greenColorBtn.backgroundColor = UIColor(named: "sil_regularGreenColor") + greenColor = true + greenColorVlue = "on" + if !redColor { + redColorVlue = "off" + } + if !blueColor { + blueColorVlue = "off" + } + } + }else if sender.tag == 3{ + if blueColor { + //blueColorCheckImg.image = LedImage.checkBoxInactiveImage + //blueColorBtn.backgroundColor = UIColor(named: "sil_boulderColor") + blueColor = false + blueColorVlue = "off" + if !redColor { + redColorVlue = "off" + } + if !greenColor { + greenColorVlue = "off" + } + + }else{ + //blueColorCheckImg.image = LedImage.checkBoxActiveImage + //blueColorBtn.backgroundColor = UIColor(named: "sil_regularBlueColor") + blueColor = true + blueColorVlue = "on" + if !redColor { + redColorVlue = "off" + } + if !greenColor { + greenColorVlue = "off" + } + } + } + ledControll() + } + @IBAction func cancelBtn(_ sender: UIButton) { + //sensorePopupView.isHidden = true + self.dismiss(animated: false, completion: nil) + } + @IBAction func refreshBtn(_ sender: UIButton) { + getLedStatus() + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVC.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVC.swift new file mode 100644 index 00000000..eab34cf4 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVC.swift @@ -0,0 +1,311 @@ +// +// SILWiFiMotionVC.swift +// BlueGecko +// +// Created by SovanDas Maity on 29/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit +import SceneKit + +class SILWiFiMotionVC: UIViewController, MotionDemoInteractionOutput, SILWiFiMotionSensorsViewModelProtocol { + + fileprivate static let defaultWheelSize: Meters = 0.0301 + + fileprivate var acceleration = ThunderboardVector() + fileprivate var orientation = ThunderboardInclination() + fileprivate var position = ThunderboardWheel(diameter: defaultWheelSize) + + var connectedDeviceView: ConnectedDeviceBarView? + var connectedDeviceBarHeight: CGFloat = 70.0 + + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var tableLeftInset: NSLayoutConstraint! + @IBOutlet weak var tableRightInset: NSLayoutConstraint! + + //@IBOutlet var navigationBar: UIView! + + var silWiFiMotionSensorsViewModel:SILWiFiMotionSensorsViewModel = SILWiFiMotionSensorsViewModel() + + let tableInset: CGFloat = 16.0 + + var motionDemoView: MotionDemoView? + + var motionView: MotionDemoView! { + if let view: MotionDemoView = self.motionDemoView { + return view + } else { + if let cell: MotionCell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? MotionCell { + self.motionDemoView = cell.motionView + return self.motionDemoView + } + } + return nil + } + + var interaction: MotionDemoInteraction! + //var deviceConnector: DeviceConnection? + var ledMaterials: [SCNMaterial] = [] + var apiCallTimer: Timer? + fileprivate var calibrationAlert: UIAlertController? + + fileprivate let calibrationTitle = "Calibrating" + fileprivate let calibrationMessage = "Please ensure the Thunderboard is stationary during calibration" + + override func viewDidLoad() { + super.viewDidLoad() + silWiFiMotionSensorsViewModel.SILWiFiMotionSensorsViewModelDelegate = self + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.setupModel() + } + setupTableView() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + apiCallTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] (timer) in + self?.silWiFiMotionSensorsViewModel.getMotionData() + }) + + //interaction.checkMissingSensors() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + //self.setupWheel() + self.updateModelOrientation(ThunderboardInclination(x: 0, y: 0, z: 0), animated: false) + //self.interaction.updateView() + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + //deviceConnector?.disconnectAllDevices() + apiCallTimer = nil + apiCallTimer?.invalidate() + } + + func displayInfoAbout(missingCapabilities: Set) { + let alertMessage = "The device cannot work properly, because it has broken sensors: \(missingCapabilities.map { $0.name }.joined(separator: ", ")). \nYou will be redirected to home screen." + self.alertWithOKButton(title: "Broken sensors", message: alertMessage) { _ in + self.navigationController?.popViewController(animated: true) + } + } + + func dispatchSetup() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.setupModel() + self.tableView.reloadData() + } + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.setupWheel() + self.updateModelOrientation(ThunderboardInclination(x: 0, y: 0, z: 0), animated: false) + self.interaction.updateView() + self.tableView.reloadData() + } + } + + func setupModel() { + // no-op - implemented in subclasses + } + + func setupTableView() { + if #available(iOS 13, *) { + tableView.separatorStyle = .none; + } else { + tableLeftInset.constant = tableInset; + tableRightInset.constant = tableInset; + } + } + + func modelTranformMatrixForOrientation(_ orientation: ThunderboardInclination) -> SCNMatrix4 { + // no-op - implemented in subclasses to account for model orientation deltas + return motionView.modelIdentity + } + + //MARK: - MotionDemoInteractionOutput + + func updateOrientation(_ orientation: ThunderboardInclination) { + let degrees = " °" + motionView.orientationXValue?.text = orientation.x.tb_toString(0)! + degrees + motionView.orientationYValue?.text = orientation.y.tb_toString(0)! + degrees + motionView.orientationZValue?.text = orientation.z.tb_toString(0)! + degrees + + updateModelOrientation(orientation, animated: true) + } + + func updateAcceleration(_ vector: ThunderboardVector) { + let gravity = " g" + + motionView.accelerationXValue?.text = vector.x.tb_toString(2, minimumDecimalPlaces: 2)! + motionView.accelerationXValue?.text = vector.x.tb_toString(2, minimumDecimalPlaces: 2)! + gravity + motionView.accelerationYValue?.text = vector.y.tb_toString(2, minimumDecimalPlaces: 2)! + gravity + motionView.accelerationZValue?.text = vector.z.tb_toString(2, minimumDecimalPlaces: 2)! + gravity + } + + func updateWheel(_ diameter: Meters) { + let settings = ThunderboardSettings() + switch settings.measurement { + case .metric: + let diameterInCentimeters: Centimeters = diameter * 100 + motionView.wheelDiameterValue?.text = diameterInCentimeters.tb_toString(2)! + " cm" + case .imperial: + let diameterInInches = diameter.tb_toInches() + motionView.wheelDiameterValue?.text = diameterInInches.tb_toString(2)! + "\"" + } + } + + func updateLocation(_ distance: Float, speed: Float, rpm: Float, totalRpm: UInt) { + let settings = ThunderboardSettings() + switch settings.measurement { + case .metric: + motionView.distanceValue?.text = distance.tb_toString(1) + motionView.speedValue?.text = speed.tb_toString(1) + case .imperial: + motionView.distanceValue?.text = distance.tb_toFeet().tb_toString(1) + motionView.speedValue?.text = speed.tb_toFeet().tb_toString(1) + } + motionView.rpmValue?.text = rpm.tb_toString(0) + motionView.totalRpmValue?.text = String(totalRpm) + } + + func updateLedColor(_ on: Bool, color: LedRgb) { + updateModelLedColor(on ? color.uiColor : StyleColor.mediumGray) + } + + func deviceCalibrating(_ isCalibrating: Bool) { + if isCalibrating { + if calibrationAlert == nil { + calibrationAlert = UIAlertController(title: calibrationTitle, message: calibrationMessage, preferredStyle: .alert) + calibrationAlert?.view.tintColor = StyleColor.vileRed + present(self.calibrationAlert!, animated: true, completion: nil) + } + } else { + guard calibrationAlert != nil else { return } + + // Call dismiss on self because calling it on UIAlertController does not produce a completion call + dismiss(animated: true, completion: { + let alertController = UIAlertController(title: "Calibration successful", message: nil, preferredStyle: .alert) + + let cancelAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true, completion: nil) + }) + calibrationAlert = nil + } + } + + //MARK: - Actions + + @IBAction func calibrateButtonPressed(_ sender: AnyObject) { + interaction.calibrate() + } + + @IBAction func backButton() { + self.navigationController?.popViewController(animated: true) + } + + //MARK: - Private + + fileprivate func setupUnitsLabels() { + let settings = ThunderboardSettings() + + switch settings.measurement { + case .metric: + motionView.distanceValueLabel?.text = "m" + motionView.speedValueLabel?.text = "m/s" + case .imperial: + motionView.distanceValueLabel?.text = "ft" + motionView.speedValueLabel?.text = "ft/s" + } + + motionView.rpmValueLabel?.text = "rpm" + motionView.totalRpmValueLabel?.text = "total revolutions" + } + + fileprivate func setupWheel() { + let diameter = interaction.wheelDiameter() + updateWheel(diameter) + } + + fileprivate func updateModelOrientation(_ orientation : ThunderboardInclination, animated: Bool) { + let finalTransform = modelTranformMatrixForOrientation(orientation) + + SCNTransaction.begin() + SCNTransaction.animationDuration = animated ? 0.1 : 0.0 + motionView.scene.rootNode.childNodes.first?.transform = finalTransform + SCNTransaction.commit() + } + + fileprivate func updateModelLedColor(_ color: UIColor) { + ledMaterials.forEach { (material) in + material.diffuse.contents = color + material.emission.contents = color + } + } + + func notifyMotionSensorsData(sensorsData: Dictionary?) { + if let sensorsData = sensorsData { + let aX = (sensorsData["accelerometer"] as? Dictionary)?["x"] + let aY = (sensorsData["accelerometer"] as? Dictionary)?["y"] + let aZ = (sensorsData["accelerometer"] as? Dictionary)?["z"] + + let gX = (sensorsData["gyroscope"] as? Dictionary)?["x"] + let gY = (sensorsData["gyroscope"] as? Dictionary)?["y"] + let gZ = (sensorsData["gyroscope"] as? Dictionary)?["z"] + let xAcceleration = α("\(aX ?? "")") ?? 0.0 + let yAcceleration = α("\(aY ?? "")") ?? 0.0 + let zAcceleration = α("\(aZ ?? "")") ?? 0.0 + ThunderboardVector(x: xAcceleration, y: yAcceleration, z: zAcceleration) + let xDegrees = Degree("\(gX ?? "")") ?? 0.0 + let yDegrees = Degree("\(gY ?? "")") ?? 0.0 + let zDegrees = Degree("\(gZ ?? "")") ?? 0.0 + + //self.uiUpdate(sensorsData: sensorsData) + + + + + updateModelOrientation(orientation, animated: true) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + + let degrees = " °" + self.motionView.orientationXValue?.text = "\(gX ?? "")" + degrees + self.motionView.orientationYValue?.text = "\(gY ?? "")" + degrees + self.motionView.orientationZValue?.text = "\(gZ ?? "")" + degrees + + let gravity = " g" + self.motionView.accelerationXValue?.text = "\(aX ?? "")" + gravity + self.motionView.accelerationYValue?.text = "\(aY ?? "")" + gravity + self.motionView.accelerationZValue?.text = "\(aZ ?? "")" + gravity + + self.updateModelOrientation(ThunderboardInclination(x: xDegrees, y: yDegrees, z: zDegrees), animated: true) + //self.tableView.reloadData() + } + } + } +} +extension SILWiFiMotionVC: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: MotionCell = tableView.dequeueReusableCell(withIdentifier: "MotionCell") as! MotionCell + motionDemoView = cell.motionView + setupModel() + + return cell + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + SILTableViewWithShadowCells.tableView(tableView, willDisplay: cell, forRowAt: indexPath) + } +} + +extension SILWiFiMotionVC: UITableViewDelegate { + +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVcCh.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVcCh.swift new file mode 100644 index 00000000..088af09d --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionVcCh.swift @@ -0,0 +1,101 @@ +// +// SILWiFiMotionVcCh.swift +// BlueGecko +// +// Created by SovanDas Maity on 29/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit +import SceneKit + +class SILWiFiMotionVcCh: SILWiFiMotionVC { + + public var deviceModelName: String! + + override func viewDidLoad() { + super.viewDidLoad() + setLeftAlignedTitle("Motion") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.tabBarController?.hideTabBarAndUpdateFrames() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + self.navigationController?.tabBarController?.showTabBarAndUpdateFrames() + } + + override func setupModel() { + + let scaleFactor: Float = 0.75 + let identity = SCNMatrix4Identity + let scale = SCNMatrix4Scale(identity, scaleFactor, scaleFactor, scaleFactor) + + var initialOrientation = SCNMatrix4Rotate(scale, 0, 1, 0, 0) + initialOrientation = SCNMatrix4Rotate(initialOrientation, .pi/2, 1, 0, 0) + + let modelScene = deviceModelName == "BRD4184A" ? "BRD4184A_LowPoly.scn" : "TBSense_Rev_Lowpoly_2.obj" + motionView.setModelScene(modelScene, initialOrientation: initialOrientation) + ledMaterials = locateMaterialsNamed([ + "thunderboardsense_lowpoly_007:lambert28sg", + "thunderboardsense_lowpoly_007:lambert32sg", + "lambert25sg", + "lambert26sg", + ]) + } + + override func modelTranformMatrixForOrientation(_ orientation: ThunderboardInclination) -> SCNMatrix4 { + let modelIdentity = motionView.modelIdentity + + if #available(iOS 13, *) { + var transform = SCNMatrix4Rotate(modelIdentity!, -orientation.x.tb_toRadian(), 1, 0, 0) + transform = SCNMatrix4Rotate(transform, orientation.y.tb_toRadian(), 0, 0, 1) + transform = SCNMatrix4Rotate(transform, orientation.z.tb_toRadian(), 0, 1, 0) + return transform + } else { + var transform = SCNMatrix4Rotate(modelIdentity!, -orientation.x.tb_toRadian(), 1, 0, 0) + transform = SCNMatrix4Rotate(transform, -orientation.y.tb_toRadian(), 0, 1, 0) + transform = SCNMatrix4Rotate(transform, orientation.z.tb_toRadian(), 0, 0, 1) + return transform + } + } + + // MARK: - Private + + fileprivate func locateMaterialsNamed(_ names: [String]) -> [SCNMaterial] { + let lowercaseNames = names.map({ $0.lowercased() }) + func recurseNode(_ node: SCNNode) -> [SCNMaterial] { + var results: [SCNMaterial] = [] + + node.childNodes.forEach({ (child) in + if let _ = child.geometry { + + child.childNodes.forEach({ + results.append(contentsOf: recurseNode($0)) + }) + + child.geometry?.materials.forEach({ (material) in + guard let materialName = material.name?.lowercased() else { + return + } + + if lowercaseNames.contains(materialName) { + results.append(material) + } + }) + } + + results.append(contentsOf: recurseNode(child)) + }) + + return results + } + + let results = recurseNode(motionView.scene.rootNode) + log.debug("results: \(results)") + return results + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionViewController.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionViewController.swift new file mode 100644 index 00000000..d67d323e --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWiFiMotionViewController.swift @@ -0,0 +1,102 @@ +// +// SILWiFiMotionViewController.swift +// BlueGecko +// +// Created by SovanDas Maity on 28/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILWiFiMotionViewController: UIViewController, SILWiFiMotionSensorsViewModelProtocol { + + @IBOutlet weak var accelerationXLbl: StyledLabel! + @IBOutlet weak var accelerationYLbl: StyledLabel! + @IBOutlet weak var accelerationZLbl: StyledLabel! + + @IBOutlet weak var orientationXLbl: StyledLabel! + @IBOutlet weak var orientationYLbl: StyledLabel! + @IBOutlet weak var orientationZLbl: StyledLabel! + + var silWiFiMotionSensorsViewModel:SILWiFiMotionSensorsViewModel = SILWiFiMotionSensorsViewModel() + var apiCallTimer: Timer? + + var accelerationXStr: String = "" + var accelerationYStr: String = "" + var accelerationZStr: String = "" + + var orientationXStr: String = "" + var orientationYStr: String = "" + var orientationZStr: String = "" + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + silWiFiMotionSensorsViewModel.SILWiFiMotionSensorsViewModelDelegate = self + + let motionData = ["gyroscope": ["x": orientationXStr, "y": orientationYStr, "z": orientationZStr], "accelerometer": ["x": accelerationXStr, "y": accelerationYStr, "z": accelerationZStr]] + uiUpdate(sensorsData: motionData) + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + apiCallTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { [weak self] (timer) in + self?.silWiFiMotionSensorsViewModel.getMotionData() + }) + //silWiFiMotionSensorsViewModel.getMotionData() + } + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + } + + @IBAction func cancelBtn(_ sender: UIButton) { + //sensorePopupView.isHidden = true + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + self.dismiss(animated: false, completion: nil) + } + @IBAction func refreshBtn(_ sender: UIButton) { + silWiFiMotionSensorsViewModel.getMotionData() + } + func uiUpdate(sensorsData: Dictionary){ + //print(sensorsData) + let degrees = " °" + let gravity = " g" + DispatchQueue.main.async { + self.orientationXLbl.text = "\((sensorsData["gyroscope"] as? Dictionary)?["x"] ?? "")\(degrees)" + self.orientationYLbl.text = "\((sensorsData["gyroscope"] as? Dictionary)?["y"] ?? "")\(degrees)" + self.orientationZLbl.text = "\((sensorsData["gyroscope"] as? Dictionary)?["z"] ?? "")\(degrees)" + self.accelerationXLbl.text = "\((sensorsData["accelerometer"] as? Dictionary)?["x"] ?? "")\(gravity)" + self.accelerationYLbl.text = "\((sensorsData["accelerometer"] as? Dictionary)?["y"] ?? "")\(gravity)" + self.accelerationZLbl.text = "\((sensorsData["accelerometer"] as? Dictionary)?["z"] ?? "")\(gravity)" + } + + } + func notifyMotionSensorsData(sensorsData: Dictionary?) { + if let sensorsData = sensorsData { + let aX = (sensorsData["accelerometer"] as? Dictionary)?["x"] + let aY = (sensorsData["accelerometer"] as? Dictionary)?["y"] + let aZ = (sensorsData["accelerometer"] as? Dictionary)?["z"] + + let gX = (sensorsData["accelerometer"] as? Dictionary)?["x"] + let gY = (sensorsData["accelerometer"] as? Dictionary)?["y"] + let gZ = (sensorsData["accelerometer"] as? Dictionary)?["z"] + let xAcceleration = α("\(aX ?? "")") ?? 0.0 + let yAcceleration = α("\(aY ?? "")") ?? 0.0 + let zAcceleration = α("\(aZ ?? "")") ?? 0.0 + ThunderboardVector(x: xAcceleration, y: yAcceleration, z: zAcceleration) + let xDegrees = Degree("\(gX ?? "")") ?? 0.0 + let yDegrees = Degree("\(gY ?? "")") ?? 0.0 + let zDegrees = Degree("\(gZ ?? "")") ?? 0.0 + ThunderboardInclination(x: xDegrees, y: yDegrees, z: zDegrees) + self.uiUpdate(sensorsData: sensorsData) + } + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWifiSensorsHomeView.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWifiSensorsHomeView.swift new file mode 100644 index 00000000..afed3ca9 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/SILWifiSensorsHomeView.swift @@ -0,0 +1,256 @@ +// +// SILWifiSensorsHomeView.swift +// BlueGecko +// +// Created by Mantosh Kumar on 05/04/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit +import SVProgressHUD + +class SILWifiSensorsHomeView: UIViewController, SILWiFiSensorsViewModelProtocol { + + @IBOutlet weak var sensoreValueLbl: UILabel! + @IBOutlet weak var sensoreImg: UIImageView! + @IBOutlet weak var sensoreTitleLbl: UILabel! + @IBOutlet weak var sensorePopupViewTitle: UILabel! + @IBOutlet weak var sensorePopupView: UIView! + @IBOutlet var collectionView: UICollectionView! + @IBOutlet weak var noDataView: UIView! + + //var timer = Timer() + var sensorsData: [Any] = [] + var sensorTypeStr: String = "" + var apiCallTimer: Timer? + + var silwifiSensorsViewModelObject:SILWiFiSensorsViewModel = SILWiFiSensorsViewModel() + + override func viewDidLoad() { + super.viewDidLoad() + //silwifiSensorsViewModelObject.checkServerAvailability() + //setupNavigationBar() + silwifiSensorsViewModelObject.SILWiFiSensorsViewModelDelegate = self + updateUI() + + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + SVProgressHUD.show(withStatus: "Connecting") + sensorTypeStr = "" + silwifiSensorsViewModelObject.getTemperatureData { sensorsData, APIClientError in + if APIClientError == nil{ + DispatchQueue.main.async { + self.silwifiSensorsViewModelObject.getAllSensorData() + //self.silwifiSensorsViewModelObject.getAllSensor() + } + }else{ + DispatchQueue.main.async { + SVProgressHUD.dismiss() + } + } + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.tabBarController?.tabBar.isHidden = true + } + + private func updateUI() { + setLeftAlignedTitle("WiFi Sensors") + let nib = UINib(nibName: "SILSensorCell", bundle: nil) + collectionView.register(nib, forCellWithReuseIdentifier: "SILSensorCell") + collectionView.backgroundColor = UIColor.clear + collectionView.delegate = self + sensorePopupView.isHidden = true + } + + @IBAction func cancelBtn(_ sender: UIButton) { + sensorePopupView.isHidden = true + sensorTypeStr = "" + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + + } + @IBAction func refreshBtn(_ sender: UIButton) { + if sensorTypeStr == SensorType.temp.rawValue { + self.getTemp() + }else if sensorTypeStr == SensorType.humudity.rawValue { + self.getHumudity() + }else if sensorTypeStr == SensorType.ambient.rawValue { + self.getAmbient() + } + } + +} +extension SILWifiSensorsHomeView { + + func notifySensorsData(sensorsData: [Any]) { + self.sensorsData = sensorsData + DispatchQueue.main.async { + SVProgressHUD.dismiss() + if self.sensorsData.count > 0 { + self.noDataView.isHidden = true + self.collectionView.isHidden = false + }else{ + self.noDataView.isHidden = false + self.collectionView.isHidden = true + } + self.collectionView.reloadData() + } + } +} + +extension SILWifiSensorsHomeView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return sensorsData.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SILSensorCell", for: indexPath as IndexPath) as? SILSensorCell else { return UICollectionViewCell() } + cell.updateSensorValue(sensorsData: sensorsData[indexPath.row] as! Dictionary) + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 5, left: 10, bottom: 20, right: 10) + } + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + selectedSensor(cellIndex: indexPath.row) + } + +} +extension SILWifiSensorsHomeView { + + func setTimer(apiType: String){ + if apiType == SensorType.temp.rawValue { + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + apiCallTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { [weak self] (timer) in + //self?.getRequest() + self?.getTemp() + }) + }else if apiType == SensorType.humudity.rawValue { + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + apiCallTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { [weak self] (timer) in + //self?.getRequest() + self?.getHumudity() + }) + }else if apiType == SensorType.ambient.rawValue { + if self.apiCallTimer != nil{ + self.apiCallTimer?.invalidate() + self.apiCallTimer = nil + } + apiCallTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { [weak self] (timer) in + //self?.getRequest() + self?.getAmbient() + }) + + } + } + func getTemp(){ + silwifiSensorsViewModelObject.getTemperatureData { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + DispatchQueue.main.async { + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.temperaturePopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.temperatureTitle.rawValue, sensoreImg: SensorImage.temp ?? UIImage(), sensoreValueLbl: "\(ledDic["temperature_celcius"] ?? "")°C") + } + } + } + } + } + func getHumudity() { + silwifiSensorsViewModelObject.getHumidityData { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + DispatchQueue.main.async { + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.humudityPopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.humudityTitle.rawValue, sensoreImg: SensorImage.humidity ?? UIImage(), sensoreValueLbl: "\(ledDic["humidity_percentage"] ?? "")%") + } + } + } + } + } + func getAmbient() { + silwifiSensorsViewModelObject.getLightData { sensorsData, APIClientError in + if APIClientError == nil{ + if let ledDic: Dictionary = sensorsData { + DispatchQueue.main.async { + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.ambientLightPopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.ambientLightTitle.rawValue, sensoreImg: SensorImage.ambient ?? UIImage(), sensoreValueLbl: "\(ledDic["ambient_light_lux"] ?? "") lx") + } + } + } + } + } +} +extension SILWifiSensorsHomeView { + func setPopUpData(sensorePopupViewTitle: String, sensoreTitleLbl: String, sensoreImg: UIImage, sensoreValueLbl: String) { + self.sensorePopupViewTitle.text = sensorePopupViewTitle + self.sensoreTitleLbl.text = sensoreTitleLbl + self.sensoreImg.image = sensoreImg + self.sensoreValueLbl.text = sensoreValueLbl + } + + func selectedSensor(cellIndex: Int) { + let storyboard = UIStoryboard(name: "SILWifiSensors", bundle: .main) + if let sensorsDataDic: Dictionary = sensorsData[cellIndex] as? Dictionary{ + switch "\(sensorsDataDic["title"] ?? "")" { + case SensorType.temp.rawValue: + sensorePopupView.isHidden = false + self.sensorTypeStr = SensorType.temp.rawValue + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.temperaturePopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.temperatureTitle.rawValue, sensoreImg: SensorImage.temp ?? UIImage(), sensoreValueLbl: "\(sensorsDataDic["value"] ?? "")°C") + self.setTimer(apiType: SensorType.temp.rawValue) + case SensorType.humudity.rawValue: + sensorePopupView.isHidden = false + self.sensorTypeStr = SensorType.humudity.rawValue + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.humudityPopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.humudityTitle.rawValue, sensoreImg: SensorImage.humidity ?? UIImage(), sensoreValueLbl: "\(sensorsDataDic["value"] ?? "")%") + self.setTimer(apiType: SensorType.humudity.rawValue) + case SensorType.ambient.rawValue: + sensorePopupView.isHidden = false + self.sensorTypeStr = SensorType.ambient.rawValue + self.setPopUpData(sensorePopupViewTitle: SensorePopupViewName.ambientLightPopupViewTitle.rawValue, sensoreTitleLbl: SensorTitle.ambientLightTitle.rawValue, sensoreImg: SensorImage.ambient ?? UIImage(), sensoreValueLbl: "\((sensorsDataDic["value"] as? [String: Any])?["ambient_light_lux"] ?? "") lx") + self.setTimer(apiType: SensorType.ambient.rawValue) + case SensorType.led.rawValue: + sensorePopupView.isHidden = true + let SILWiFiLEDViewControllerObj = storyboard.instantiateViewController(withIdentifier: "SILWiFiLEDViewController") + SILWiFiLEDViewControllerObj.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext + present(SILWiFiLEDViewControllerObj, animated: false) + + //navigationController?.pushViewController(SILWiFiLEDViewControllerObj, animated: true) + case SensorType.motion.rawValue: + sensorePopupView.isHidden = true + //SILWiFiMotionVcCh + //let SILWiFiMotionViewControllerObj = storyboard.instantiateViewController(withIdentifier: "SILWiFiMotionVcCh") + let SILWiFiMotionViewControllerObj = storyboard.instantiateViewController(withIdentifier: "SILWiFiMotionViewController") + SILWiFiMotionViewControllerObj.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext + present(SILWiFiMotionViewControllerObj, animated: false) + + default: + print("Have you done something new?") + } + } + } +} + +extension SILWifiSensorsHomeView: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + return true + } +} + diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/WiFiMotionDemoConnection.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/WiFiMotionDemoConnection.swift new file mode 100644 index 00000000..dd113c99 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Controller/WiFiMotionDemoConnection.swift @@ -0,0 +1,145 @@ +// +// WiFiMotionDemoConnection.swift +// BlueGecko +// +// Created by SovanDas Maity on 29/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit + +class WiFiMotionDemoConnection: MotionDemoConnection { + var device: any Device + + + fileprivate var bleDevice: BleDevice { + get { return device as! BleDevice } + } + + weak var connectionDelegate: MotionDemoConnectionDelegate? + + fileprivate let startCalibrationId = 0x01 + fileprivate let resetOrientationId = 0x02 + + init(device: BleDevice) { + self.device = device + self.bleDevice.demoConnectionCharacteristicValueUpdated = { [weak self] (characteristic: CBCharacteristic) in + self?.characteristicUpdated(characteristic) + } + self.bleDevice.demoDeviceDisconnectedHook = { [weak self] in + self?.connectionDelegate?.demoDeviceDisconnected() + } + } + + func characteristicUpdated(_ characteristic: CBCharacteristic) { + + switch characteristic.uuid { + case CBUUID.CSCMeasurement: + notifyRotation(characteristic) + + case CBUUID.AccelerationMeasurement: + notifyAcceleration(characteristic) + + case CBUUID.OrientationMeasurement: + notifyOrientation(characteristic) + + case CBUUID.Command: + notifyCommand(characteristic) + + case CBUUID.CSCControlPoint: + notifyCSCControlPoint(characteristic) + + case CBUUID.SenseRGBOutput: + notifyColorUpdated(characteristic) + + default: + log.debug("unknown UUID: \(characteristic.uuid)") + break + } + } + + // MotionDemoConnection protocol + + func startCalibration() { + let data = Data(bytes: [UInt8(0x01)]) + self.bleDevice.writeValueForCharacteristic(CBUUID.Command, value: data) + + self.connectionDelegate?.startedCalibration() + } + + func resetOrientation() { + let data = Data(bytes: [UInt8(0x02)]) + self.bleDevice.writeValueForCharacteristic(CBUUID.Command, value: data) + + self.connectionDelegate?.startedOrientationReset() + } + + func resetRevolutions() { + let data = Data(bytes: [UInt8(0x01), 0, 0, 0, 0]) + self.bleDevice.writeValueForCharacteristic(CBUUID.CSCControlPoint, value: data) + + self.connectionDelegate?.startedRevolutionsReset() + } + + func readLedColor() { + self.bleDevice.readValuesForCharacteristic(CBUUID.SenseRGBOutput) + } + + // Internal + + fileprivate func notifyRotation(_ characteristic: CBCharacteristic) { + if let cscMeasurement:ThunderboardCSCMeasurement = characteristic.tb_cscMeasurementValue() { + + let revolutions = cscMeasurement.revolutionsSinceConnecting + let elapsedTime = cscMeasurement.secondsSinceConnecting + self.connectionDelegate?.rotationUpdated(UInt(revolutions), elapsedTime: elapsedTime) + } + } + + fileprivate func notifyOrientation(_ characteristic: CBCharacteristic) { + if let inclination = characteristic.tb_inclinationValue() { + self.connectionDelegate?.orientationUpdated(inclination) + } + } + + fileprivate func notifyAcceleration(_ characteristic: CBCharacteristic) { + if let vector = characteristic.tb_vectorValue() { + self.connectionDelegate?.accelerationUpdated(vector) + } + } + + fileprivate func notifyCommand(_ characteristic: CBCharacteristic) { + if let value = characteristic.tb_uint32Value() { + + let command = Int(value >> 8) & 0b11 + if command == startCalibrationId { + self.connectionDelegate?.finishedCalbration() + } + + else if command == resetOrientationId { + self.connectionDelegate?.finishedOrientationReset() + } + + else { + log.debug("Unknown notify command: \(command)") + } + } + } + + fileprivate func notifyColorUpdated(_ characteristic: CBCharacteristic) { + guard let ledState = characteristic.tb_analogLedState() else { + return + } + + switch ledState { + case .rgb(let on, let color): + connectionDelegate?.ledColorUpdated(on, color: color) + default: + break + } + } + + fileprivate func notifyCSCControlPoint(_ characteristic: CBCharacteristic) { + self.connectionDelegate?.finishedRevolutionsReset() + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILAllWiFiSensorModel.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILAllWiFiSensorModel.swift new file mode 100644 index 00000000..38a9b902 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILAllWiFiSensorModel.swift @@ -0,0 +1,40 @@ +// +// SILAllWiFiSensorModel.swift +// BlueGecko +// +// Created by SovanDas Maity on 11/07/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +enum SensorImage { + static let temp = UIImage(named: "icon - temp") + static let motion = UIImage(named: "WiFi_motion_icon") + static let humidity = UIImage(named: "icon - environment") + static let LED_Status = UIImage(named: "WiFi_led_icon") + static let ambient = UIImage(named: "icon - light") + static let unknown = UIImage(named: "icon - environment") +} + +enum SensorType: String { + case temp = "Temperature" + case humudity = "Humidity" + case ambient = "Ambient Light" + case led = "LED" + case motion = "Motion" + static let allSensors = ["Temperature", "Humidity", "Ambient Light", "Motion", "LED"] +} +enum SensorTitle: String { + case temperatureTitle = "Temperature" + case humudityTitle = "Humidity" + case ambientLightTitle = "Ambient Light" + case ledLightTitle = "LED" + case motionTitle = "Motion" +} +enum SensorePopupViewName: String { + case temperaturePopupViewTitle = "Temperature Sensor" + case humudityPopupViewTitle = "Humidity Sensor" + case ambientLightPopupViewTitle = "Ambient Light Sensor" + case ledLightPopupViewTitle = "LED Control" + case motionPopupViewTitle = "Motion Sensor" +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILWiFiLEDModel.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILWiFiLEDModel.swift new file mode 100644 index 00000000..40941bdc --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/Model/SILWiFiLEDModel.swift @@ -0,0 +1,44 @@ +// +// SILWiFiLEDModel.swift +// BlueGecko +// +// Created by SovanDas Maity on 27/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +enum LedImage { + static let ledOnImage = UIImage(named: "lightOn") + static let ledOffImage = UIImage(named: "lightOff") + static let redLedOnImage = UIImage(named: "bulb_red") + static let greenLedOnImage = UIImage(named: "bulb_green") + static let blueLedOnImage = UIImage(named: "bulb_blue") + static let magentaLedImage = UIImage(named: "bulb_magenta") + static let cyanLedImage = UIImage(named: "bulb_cyan") + static let yellowLedImage = UIImage(named: "bulb_yellow") + static let checkBoxActiveImage = UIImage(named: "checkBoxActive") + static let checkBoxInactiveImage = UIImage(named: "checkBoxInactive") + static let blubOffTint = UIImage(named: "blub_off_tint") +} +enum LedType: String { + case ledOn = "ledOn" + case ledOff = "ledOff" + case redOn = "redOn" + case greenOn = "greenOn" + case blueOn = "blueOn" + case redOff = "redOff" + case greenOff = "greenOff" + case blueOff = "blueOff" + case redGreenOn = "redGreenOn" + case redBlueOn = "redBlueOn" + case greenBuleOn = "greenBuleOn" +} +enum LedStatus: String { + case ledOnState = "on" + case ledOffState = "off" +} +enum LedColorType: String { + case redType = "red" + case greenType = "green" + case blueType = "blue" +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.swift new file mode 100644 index 00000000..ef06caa3 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.swift @@ -0,0 +1,69 @@ +// +// SILSensorCell.swift +// BlueGecko +// +// Created by Mantosh Kumar on 05/04/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import UIKit + + + +class SILSensorCell: UICollectionViewCell { + + @IBOutlet weak var canvasView: UIView! + @IBOutlet weak var sensorIconImage: UIImageView! + @IBOutlet weak var sensorTitleLabel: UILabel! + @IBOutlet weak var sensorValueLabel: UILabel! + + let cornerRadius: CGFloat = 16.0 + + override func awakeFromNib() { + super.awakeFromNib() + setupCellAppearence() + } + + override func layoutSubviews() { + super.layoutSubviews() + layer.masksToBounds = false + backgroundColor = UIColor.clear + addShadow(withOffset: SILCellShadowOffset, radius: SILCellShadowRadius) + } + + private func setupCellAppearence() { + canvasView.layer.cornerRadius = cornerRadius + } + + func updateSensorValue(sensorsData: Dictionary) { + print(sensorsData) + var valOfSensor = "" + + switch "\(sensorsData["title"] ?? "")" { + case SensorType.temp.rawValue: + valOfSensor = "\(sensorsData["value"] ?? "")°C" + case SensorType.humudity.rawValue: + valOfSensor = "\(sensorsData["value"] ?? "")%" + case SensorType.ambient.rawValue: + if let ambientDic: Dictionary = sensorsData["value"] as? Dictionary { + valOfSensor = "\(ambientDic["ambient_light_lux"] ?? "")lx" + } +// valOfSensor = "\(sensorsData["value"] ?? "")" + default: + print("Have you done something new?") + } + sensorTitleLabel.text = "\(sensorsData["title"] ?? "")" + //sensorValueLabel.text = "\(valOfSensor)" + if sensorsData["title"] as! String == SensorType.temp.rawValue { + sensorIconImage.image = SensorImage.temp + }else if sensorsData["title"] as! String == SensorType.humudity.rawValue { + sensorIconImage.image = SensorImage.humidity + }else if sensorsData["title"] as! String == SensorType.ambient.rawValue { + sensorIconImage.image = SensorImage.ambient + }else if sensorsData["title"] as! String == SensorType.led.rawValue { + sensorIconImage.image = SensorImage.LED_Status + }else if sensorsData["title"] as! String == SensorType.motion.rawValue { + sensorIconImage.image = SensorImage.motion + } + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.xib b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.xib new file mode 100644 index 00000000..0c9eee29 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/Cell/SILSensorCell.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + Roboto-Medium + + + Roboto-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/SILWifiSensors.storyboard b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/SILWifiSensors.storyboard new file mode 100644 index 00000000..3474099d --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/View/SILWifiSensors.storyboard @@ -0,0 +1,1317 @@ + + + + + + + + + + + + + + Roboto-Medium + + + Roboto-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiLedSensorsViewModel.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiLedSensorsViewModel.swift new file mode 100644 index 00000000..b76fe418 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiLedSensorsViewModel.swift @@ -0,0 +1,97 @@ +// +// SILWiFiLedSensorsViewModel.swift +// BlueGecko +// +// Created by SovanDas Maity on 27/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation + +class SILWiFiLedSensorsViewModel { + + typealias completionBlockSensors = (_ sensorsData: Dictionary?, _ APIClientError:Error?) -> Void + + + func getLedData(completionBlockSensors: @escaping completionBlockSensors) { + APIRequest.sharedInstance.getApiCall(url: "led") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let ledDic: Dictionary = json as? Dictionary{ + completionBlockSensors(ledDic, nil) + } + } catch { + completionBlockSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockSensors(nil, APIClientError) + } + } + } + + func ledOnOf(ledType: String, parameter: String, urlEndpoint: String, completionBlockSensors: @escaping completionBlockSensors){ + APIRequest.sharedInstance.postApiCall(parameterDictionary: parameter, url: urlEndpoint) { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let ledDic: Dictionary = json as? Dictionary{ + completionBlockSensors(ledDic, nil) + } + } catch { + completionBlockSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockSensors(nil, APIClientError) + } + } + } + func statusLed(requestMethod:String, completionBlockSensors: @escaping completionBlockSensors) { + //{"status_led": "on/off"} + if requestMethod == HttpMethods.POST.rawValue { + let paramStr = """ + {"status_led": "off"} + """ + APIRequest.sharedInstance.postApiCall(parameterDictionary: paramStr, url: "status_led") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let statusLedDic: Dictionary = json as? Dictionary{ + completionBlockSensors(statusLedDic, nil) + } + } catch { + completionBlockSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockSensors(nil, APIClientError) + } + + } + }else if requestMethod == HttpMethods.GET.rawValue { + APIRequest.sharedInstance.getApiCall(url: "status_led") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let statusLedDic: Dictionary = json as? Dictionary{ + completionBlockSensors(statusLedDic, nil) + } + } catch { + completionBlockSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockSensors(nil, APIClientError) + } + } + } + + } + +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiMotionSensorsViewModel.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiMotionSensorsViewModel.swift new file mode 100644 index 00000000..77d2bbe7 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiMotionSensorsViewModel.swift @@ -0,0 +1,121 @@ +// +// SILWiFiMotionSensorsViewModel.swift +// BlueGecko +// +// Created by SovanDas Maity on 27/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +protocol SILWiFiMotionSensorsViewModelProtocol { + // blueprint of a method + func notifyMotionSensorsData(sensorsData: Dictionary?) +} + +class SILWiFiMotionSensorsViewModel { + + var SILWiFiMotionSensorsViewModelDelegate: SILWiFiMotionSensorsViewModelProtocol? + typealias completionBlockMotionSensors = (_ sensorsData: Dictionary?, _ APIClientError:Error?) -> Void + var motionSensorsData = Dictionary() + var gyroscope = Dictionary() + var accelerometer = Dictionary() + let APIRequestdispatchGroup = DispatchGroup() + let concurrentQueue = DispatchQueue(label: "com.gcd.motionsensordispatchGroup", attributes: .concurrent) + var countInt = 0 + + func getGyroscopeData(completionBlockMotionSensors: @escaping completionBlockMotionSensors) { + APIRequest.sharedInstance.getApiCall(url: "gyroscope") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + completionBlockMotionSensors(temperatureDic, nil) + } + } catch { + completionBlockMotionSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockMotionSensors(nil, APIClientError) + } + } + } + + func getAccelerometerData(completionBlockMotionSensors: @escaping completionBlockMotionSensors) { + APIRequest.sharedInstance.getApiCall(url: "accelerometer") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + completionBlockMotionSensors(temperatureDic, nil) + } + } catch { + completionBlockMotionSensors(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlockMotionSensors(nil, APIClientError) + } + } + } + + func getMotionData() { + + if countInt == 3 { + countInt = 0 + } + //APIRequestdispatchGroup.leave() + + concurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + self.getGyroscopeData { sensorsData, APIClientError in + if APIClientError == nil { + if let ledDic: Dictionary = sensorsData { + self.gyroscope = ledDic + self.APIRequestdispatchGroup.leave() + }else{ + self.APIRequestdispatchGroup.leave() + } + + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + concurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + self.getAccelerometerData { sensorsData, APIClientError in + if APIClientError == nil { + if let ledDic: Dictionary = sensorsData { + self.accelerometer = ledDic + self.APIRequestdispatchGroup.leave() + }else{ + self.APIRequestdispatchGroup.leave() + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + + APIRequestdispatchGroup.notify(queue: .main) { + print("All functions completed notify") + print("All API done: \(self.countInt)") + print("gyroscope: \(self.gyroscope) ") + print("accelerometer: \(self.accelerometer) ") + self.sendMotionData() + } + APIRequestdispatchGroup.wait() + print("All functions completed wait") + } + + func sendMotionData() { + self.motionSensorsData = [:] + self.motionSensorsData = ["gyroscope": gyroscope, "accelerometer": accelerometer] + if self.motionSensorsData.count > 0 { + SILWiFiMotionSensorsViewModelDelegate?.notifyMotionSensorsData(sensorsData: self.motionSensorsData) + } + } +} diff --git a/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiSensorsViewModel.swift b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiSensorsViewModel.swift new file mode 100644 index 00000000..e4508d32 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/WiFi_Sensor/ViewModel/SILWiFiSensorsViewModel.swift @@ -0,0 +1,311 @@ +// +// SILWiFiSensorsViewModel.swift +// BlueGecko +// +// Created by SovanDas Maity on 26/06/24. +// Copyright © 2024 SiliconLabs. All rights reserved. +// + +import Foundation +import Network + + +protocol SILWiFiSensorsViewModelProtocol { + // blueprint of a method + func notifySensorsData(sensorsData: [Any]) +} + +class SILWiFiSensorsViewModel { + //static let silwifiSensorsViewModelObject = SILWiFiSensorsViewModel() + var SILWiFiSensorsViewModelDelegate: SILWiFiSensorsViewModelProtocol? + var temperature = "" + var humidity = "" + var ambientLightLux = "" + var whiteLightLux = "" + var gyroscope = Dictionary() + var accelerometer = Dictionary() + var led = Dictionary() + + + typealias completionBlockSensors = (_ sensorsData: [Any]?, _ APIClientError:Error?) -> Void + typealias completionBlock = (_ sensorsData: Dictionary?, _ APIClientError:Error?) -> Void + + + var sensorsData: [Any] = [] + let APIRequestdispatchGroup = DispatchGroup() + let sensorConcurrentQueue = DispatchQueue(label: "com.gcd.sensordispatchGroup", attributes: .concurrent) + var countInt = 0 + + + func getSensors() { + sensorsData = [] + for val in SensorType.allSensors{ + var valOfSensor = "" + var tempDic = Dictionary() + switch val { + case SensorType.temp.rawValue: + tempDic = ["title": "\(val)", "value": temperature] + case SensorType.humudity.rawValue: + tempDic = ["title": "\(val)", "value": humidity] + case SensorType.ambient.rawValue: + //valOfSensor = "AL:\(ambientLightLux) WL:\(whiteLightLux)" + //valOfSensor = "\(ambientLightLux) lx" + tempDic = ["title": "\(val)", "value": ["ambient_light_lux": ambientLightLux, "white_light_lux": whiteLightLux]] + case SensorType.motion.rawValue: + let motionDic = ["gyroscope": gyroscope, "accelerometer": accelerometer] + tempDic = ["title": "\(val)", "value": motionDic] + case SensorType.led.rawValue: + tempDic = ["title": "\(val)", "value": led] + + default: + print("Have you done something new?") + } + //let tempDic = ["title": "\(val)", "value": valOfSensor] + sensorsData.append(tempDic) + } + if sensorsData.count > 0 { + print(sensorsData) + SILWiFiSensorsViewModelDelegate?.notifySensorsData(sensorsData: sensorsData) + }else{ + SILWiFiSensorsViewModelDelegate?.notifySensorsData(sensorsData: sensorsData) + } + } + + + func getAllSensor() { + APIRequest.sharedInstance.getApiCall(url: "all_sensors") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) +// if let temperatureDic: Dictionary = json as? Dictionary{ +// self.temperature = "\(temperatureDic["temperature_celcius"] ?? "")" +// } + } catch { + print(APIClientError) + } + }else{ + print(APIClientError) + } + } + } + + func getAllSensorData(){ + if self.countInt == 0 { + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "temperature") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + self.temperature = "\(temperatureDic["temperature_celcius"] ?? "")" + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + + } + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "humidity") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let humidityDic: Dictionary = json as? Dictionary{ + self.humidity = "\(humidityDic["humidity_percentage"] ?? "")" + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "light") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let lightDic: Dictionary = json as? Dictionary{ + self.ambientLightLux = "\(lightDic["ambient_light_lux"] ?? "")" + self.whiteLightLux = "\(lightDic["white_light_lux"] ?? "")" + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + + } + + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "led") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let ledDic: Dictionary = json as? Dictionary{ + self.led = ledDic + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "gyroscope") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let gyroscopeDic: Dictionary = json as? Dictionary{ + self.gyroscope = gyroscopeDic + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + sensorConcurrentQueue.async(group: APIRequestdispatchGroup) { + self.APIRequestdispatchGroup.enter() + APIRequest.sharedInstance.getApiCall(url: "accelerometer") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let accelerometerDic: Dictionary = json as? Dictionary{ + self.accelerometer = accelerometerDic + } + self.countInt += 1 + self.APIRequestdispatchGroup.leave() + } catch { + self.APIRequestdispatchGroup.leave() + print(APIClientError) + } + }else{ + self.APIRequestdispatchGroup.leave() + } + } + } + } + + APIRequestdispatchGroup.notify(queue: .main) { + if self.countInt == 6 { + self.countInt = 0 + } + self.getSensors() + } + APIRequestdispatchGroup.wait() + print("All functions completed wait") + } + func getTemperatureData(completionBlock: @escaping completionBlock) { + APIRequest.sharedInstance.getApiCall(url: "temperature") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + completionBlock(temperatureDic, nil) + } + } catch { + completionBlock(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlock(nil, APIClientError) + } + } + } + func getHumidityData(completionBlock: @escaping completionBlock) { + APIRequest.sharedInstance.getApiCall(url: "humidity") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + completionBlock(temperatureDic, nil) + } + } catch { + completionBlock(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlock(nil, APIClientError) + } + } + } + func getLightData(completionBlock: @escaping completionBlock) { + APIRequest.sharedInstance.getApiCall(url: "light") { ReponsData, APIClientError in + if APIClientError == nil { + do { + let json = try JSONSerialization.jsonObject(with: ReponsData ?? Data(), options: []) + print(json) + if let temperatureDic: Dictionary = json as? Dictionary{ + completionBlock(temperatureDic, nil) + } + } catch { + completionBlock(nil, APIClientError) + print(APIClientError) + } + }else{ + completionBlock(nil, APIClientError) + } + } + + } + + func checkServerAvailability() { + let monitor = NWPathMonitor() + monitor.pathUpdateHandler = { path in + let usesWiFi = path.usesInterfaceType(.wifi) + let usesCellular = path.usesInterfaceType(.cellular) + if path.status == .satisfied { + print("Internet connection is available.") + // Perform actions when internet is available + } else { + print("Internet connection is not available.") + // Perform actions when internet is not available + } + } + let queue = DispatchQueue(label: "NetworkMonitor") + monitor.start(queue: queue) + + } + +}