diff --git a/Podfile b/Podfile index 603ce753..d6496222 100644 --- a/Podfile +++ b/Podfile @@ -18,7 +18,7 @@ def shared_pods pod 'PureLayout', '~> 3.1.4' pod 'SVProgressHUD', '~> 2.2.5' pod 'UICircularProgressRing', '~> 4.1.0' - pod 'WYPopoverController', '~> 0.2.2' + pod 'WYPopoverController', :git => 'https://github.com/sammcewan/WYPopoverController.git' pod 'XMLDictionary', '~> 1.4.1' pod 'Realm', '~> 4.3.2' pod 'RealmSwift' diff --git a/Podfile.lock b/Podfile.lock index 308aaee0..52f714ec 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -31,7 +31,7 @@ PODS: - RxSwift (6.2.0) - SVProgressHUD (2.2.5) - UICircularProgressRing (4.1.0) - - WYPopoverController (0.2.2) + - WYPopoverController (0.3.9) - XMLDictionary (1.4.1) DEPENDENCIES: @@ -55,7 +55,7 @@ DEPENDENCIES: - RxSwift (~> 6.2.0) - SVProgressHUD (~> 2.2.5) - UICircularProgressRing (~> 4.1.0) - - WYPopoverController (~> 0.2.2) + - WYPopoverController (from `https://github.com/sammcewan/WYPopoverController.git`) - XMLDictionary (~> 1.4.1) SPEC REPOS: @@ -80,18 +80,22 @@ SPEC REPOS: - RxSwift - SVProgressHUD - UICircularProgressRing - - WYPopoverController - XMLDictionary EXTERNAL SOURCES: Eddystone: :branch: nservidio/add-properties-to-Generic :git: https://github.com/IntrepidPursuits/eddystone-ios.git + WYPopoverController: + :git: https://github.com/sammcewan/WYPopoverController.git CHECKOUT OPTIONS: Eddystone: :commit: ecbaed68ff82aecc82a52671fae17552658c2b8d :git: https://github.com/IntrepidPursuits/eddystone-ios.git + WYPopoverController: + :commit: 9b8011bd95bd95f6fca6a0afa78965fbf645a062 + :git: https://github.com/sammcewan/WYPopoverController.git SPEC CHECKSUMS: ActionSheetPicker-3.0: eef157d75e151f255c5333d26656c7fbfe905a51 @@ -115,9 +119,9 @@ SPEC CHECKSUMS: RxSwift: d356ab7bee873611322f134c5f9ef379fa183d8f SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 UICircularProgressRing: 5a32c01f54474571b1e66e8fa050c0f83d6d981b - WYPopoverController: 00d879c25bddd1fd873dcc21aa98c8308d7f1555 + WYPopoverController: a9db25ac2841a686acdc0f3a99bdb21545db32f4 XMLDictionary: fa07b6ff422b3a91d47a5de9bc82e3fc04fbd167 -PODFILE CHECKSUM: fa38ccfd90caab8b342bbeb884f33901d78598f1 +PODFILE CHECKSUM: 0c27fdd7e95d8fb75e6cc907591fbf6ff042fa18 -COCOAPODS: 1.10.1 +COCOAPODS: 1.10.2 diff --git a/README.md b/README.md index 4a38eac1..6566a640 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,44 @@ -# EFR Connect Mobile Application -This is the source code for the EFR Connect mobile application. +# Blue Gecko for iOS +This is the source code for the Blue Gecko mobile application for iOS. -## What is EFR Connect BLE mobile app? +## Overview: -Silicon Labs EFR Connect is a generic BLE mobile app for testing and debugging Bluetooth® Low Energy applications. With EFR Connect, you can quickly troubleshoot your BLE embedded application code, Over-the-Air (OTA) firmware update, data throughput, and interoperability with Android and iOS mobiles, among the many other features. You can use the EFR Connect app with all Silicon Labs Bluetooth development kits, Systems on Chip (SoC), and modules. +* [Project Overview](#project-overview) - A general overview of the purpose of this project. +* [Project Setup](#project-setup) - How to get set up with the Blue Gecko iOS project. +* [Architecture](#architecture) - A general overview of the app's architecture. -## Why download EFR Connect? -EFR Connect radically saves the time you will use for testing and debugging! With EFR Connect, you can quickly see what’s wrong with your code and how to fix and optimize it. EFR Connect is the first BLE mobile app allowing you to test data throughput and mobile interoperability with a single tap on the app. +## Project Overview -## How does it work? -Using EFR Connect BLE mobile app is easy. It runs on your mobile devices such as a smartphone or tablet. It utilizes the Bluetooth adapter on the mobile to scan, connect and interact with nearby BLE hardware. +The Silicon Labs Blue Gecko App displays temperature measurements from the Silicon Labs Wireless Starter Kit (SLWSK). The app can also indicate proximity to the SLWSTK using Find Me Profile (FMP) & Proximity Profiles (PXP). Finally, the SLWSK can be configured as a Retail Beacon to send advertisement data to the App. -After connecting the EFR Connect app and BLE hardware (e.g., a dev kit), the Blinky test on the app shows a green light indicating when your setup is ready to go. The app includes simple demos to teach you how to get started with EFR Connect and all Silicon Labs development tools. +The current retail version can be found on the [US AppStore](https://itunes.apple.com/us/app/silicon-labs-blue-gecko-wstk/id1030932759?mt=8). -The Browser, Advertiser, and Logging features help you to find and fix bugs quickly and test throughput and mobile interoperability simply, with a tap of a button. With our Simplicity Studio’s Network Analyzer tool (free of charge), you can view the packet trace data and dive into the details. +## Project Setup -## Demos and Sample Apps -EFR Connect includes many demos to test sample apps in the Silicon Labs GSDK quickly. Here are demo examples: +In order to load firmware to the device, get set up with [Simplicity Studio](https://www.silabs.com/products/development-tools/software/simplicity-studio). -- **Blinky**: The ”Hello World” of BLE – Toggling a LED is only one tap away. -- **Throughput**: Measure application data throughput between the BLE hardware - and your mobile device in both directions -- **Health Thermometer**: Connect to a BLE hardware kit and receive the temperature data from the on-board sensor. -- **Connected Lighting DMP**: Leverage the dynamic multi-protocol (DMP) sample apps to control a DMP light node from a mobile and protocol-specific switch node (Zigbee, proprietary) while keeping the light status in sync across all devices. -- **Range Test**: Visualize the RSSI and other RF performance data on the mobile phone while running the Range Test sample application on a pair of Silicon Labs radio boards. -- **Motion**: Control a 3D render of a Silicon Labs Thunderboard or Dev Kit that follows the phyiscal board movements. -- **Environment**: Read and display the data from the on-board sensors on a Silicon Labs Thunderboard or Dev Kit. -- **Wi-Fi Commissioning**: Commission a Wi-Fi device over BLE. +The Silicon Labs iOS Application is built to support iOS 11 .0 and higher, and utilizes [CocoaPods](https://cocoapods.org/#install) for library dependency management. -## Development Features -EFR Connect helps developers create and troubleshoot Bluetooth applications running on Silicon Labs’ BLE hardware. Here’s a rundown of some example functionalities. +The application also contains a "hidden" debug button to the immediate right of the navigation bar that becomes visible on TouchDownInside, and contains a local Bluetooth device explorer. -**Bluetooth Browser** - A powerful tool to explore the BLE devices around you. Key features include: -- Scan and sort results with a rich data set -- Label favorite devices to surface on the top of scanning results -- Advanced filtering to identify the types of devices you want to find -- Save filters for later use -- Multiple connections -- Bluetooth 5 advertising extensions -- Rename services and characteristics with 128-bit UUIDs (mappings dictionary) -- Over-the-air (OTA) device firmware upgrade (DFU) in reliable and fast modes -- Configurable MTU and connection interval -- All GATT operations +## Architecture -**Bluetooth Advertiser** – Create and enable multiple parallel advertisement sets: -- Legacy and extended advertising -- Configurable advertisement interval, TX Power, primary/secondary PHYs -- Manual advertisement start/stop and stop based on a time/event limit -- Support for multiple AD types +#### Health Thermometer -**Bluetooth GATT Configurator** – Create and manipulate multiple GATT databases -- Add services, characteristics and descriptors -- Operate the local GATT from the browser when connected to a device -- Import/export GATT database between the mobile device and Simplicity Studio GATT Configurator +To test the Health Thermometer, load the `SOC - Thermometer` firmware in Simplicity Studio. Open the app and select the Health Thermometer cell. You will be presented with a popover. You are able to select Blue Gecko devices or Other devices. Blue Geckos is selected by default. Select Other. You should see a thermometer called Thermometer Example in the list. You should be able to select that thermometer and be brought to a temperature readout screen. -**Bluetooth Interoperability Test** – Verify interoperability between the BLE hardware - and your mobile device -- Runs a sequence of BLE operations to verify interoperability -- Export results log +#### Bluetooth Beaconing +When you enter the Bluetooth Beaconing portion of the app, the app begins to look for several types of "beacons" including iBeacon, AltBeacons, BGBeacons, and Eddystone Beacons. -## Additional information -The app can be found on the [Google PlayStore](https://play.google.com/store/apps/details?id=com.siliconlabs.bledemo&hl=en) and [Apple App Store](https://apps.apple.com/us/app/blue-gecko/id1030932759). - -[Learn more about EFR Connect BLE mobile app](https://www.silabs.com/developers/efr-connect-mobile-app). - -[Release Notes](https://www.silabs.com/developers/efr-connect-mobile-app) - -For more information on Silicon Labs product portfolio please visit [www.silabs.com](https://www.silabs.com). +#### Key Fobs +In order to test the Key Fob mode, make sure that the `SOC - Smart Phone App` firmware is loaded. Open the app and select Key Fobs. Make sure that the hardware is displaying "HTM/KEYFOB MODE" on its LCD display. If it isn't, press the PB0 button. Then press the FIND button in the app. notice that the two small LEDs between the LCD display and the Blue Gecko board are now flashing! If you back out of the Key Fob screen you will see that the app is disconnecting and the LEDs will stop flashing. -## License - - Copyright 2021 Silicon Laboratories - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - +#### Bluetooth Browser +The Bluetooth browser searches for all Bluetooth peripherals in the surrounding area. +## Additional information +For more information, please visit [www.silabs.com](https://www.silabs.com). diff --git a/SiliconLabsApp-Bridging-Header.h b/SiliconLabsApp-Bridging-Header.h index 4d1b9f04..c26d452c 100644 --- a/SiliconLabsApp-Bridging-Header.h +++ b/SiliconLabsApp-Bridging-Header.h @@ -10,9 +10,7 @@ #import "SILBeacon.h" #import "SILCentralManager.h" #import "SILDiscoveredPeripheralDisplayDataViewModel.h" -#import "SILDiscoveredPeripheral.h" #import "SILBrowserBeaconType.h" -#import "SILRSSIMeasurementTable.h" #import "UIColor+SILColors.h" #import "SILApp.h" #import "WYPopoverController+SILHelpers.h" @@ -44,6 +42,8 @@ #import "UIImage+SILImages.h" #import "SILBrowserLogViewController.h" #import "SILBrowserFilterViewController.h" +#import "SILBrowserFilterViewControllerDelegate.h" +#import "SILBrowserFilterViewModel.h" #import "SILBrowserConnectionsViewController.h" #import "SILStoryboard+Constants.h" #import "SILBrowserBeaconType.h" diff --git a/SiliconLabsApp.xcodeproj/project.pbxproj b/SiliconLabsApp.xcodeproj/project.pbxproj index c0bd27b1..5add0eab 100644 --- a/SiliconLabsApp.xcodeproj/project.pbxproj +++ b/SiliconLabsApp.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 04758A22271EC9510038D9AC /* EnvironmentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0461B3662719DD900076D627 /* EnvironmentCollectionViewCell.swift */; }; 048E4FD42719CFF7009D88A3 /* SimulatedIoDemoConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048E4FD32719CFF7009D88A3 /* SimulatedIoDemoConnection.swift */; }; 048E4FE02719D022009D88A3 /* SimulatedDeviceScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048E4FDF2719D022009D88A3 /* SimulatedDeviceScanner.swift */; }; + 04B1F49427DF74DA0083CB3C /* RSSIGraphYAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B1F49327DF74DA0083CB3C /* RSSIGraphYAxisRenderer.swift */; }; + 04B1F49627DF76B40083CB3C /* RSSIGraphXAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B1F49527DF76B40083CB3C /* RSSIGraphXAxisRenderer.swift */; }; 0701674A1BE2B5B70075F005 /* SILBitFieldFieldModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 070167491BE2B5B70075F005 /* SILBitFieldFieldModel.m */; }; 0702AB2C1BDFEE35009527B0 /* SILCharacteristicFieldValueResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB2B1BDFEE35009527B0 /* SILCharacteristicFieldValueResolver.m */; }; 0702AB331BE10E07009527B0 /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB311BE10E07009527B0 /* SILDebugCharacteristicValueFieldTableViewCell.m */; }; @@ -50,11 +52,6 @@ 0781384A1BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138481BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m */; }; 078138591BE9B014001EFE7E /* SILTextFieldEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138571BE9B014001EFE7E /* SILTextFieldEntryCell.m */; }; 0797E34D1BF0071D0046EF0E /* SILTextFieldEntryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0797E34B1BF0071D0046EF0E /* SILTextFieldEntryCell.xib */; }; - 07B1AD781BFD043000D4D454 /* SILBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD771BFD043000D4D454 /* SILBeaconViewModel.m */; }; - 07B1AD7B1BFD05CA00D4D454 /* SILBGBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7A1BFD05CA00D4D454 /* SILBGBeaconViewModel.m */; }; - 07B1AD7E1BFD0A2100D4D454 /* SILIBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7D1BFD0A2100D4D454 /* SILIBeaconViewModel.m */; }; - 07B1AD841BFD0B4F00D4D454 /* SILAltBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD831BFD0B4F00D4D454 /* SILAltBeaconViewModel.m */; }; - 07B1AD8B1BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD891BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m */; }; 07B8A8801BC41462001948C1 /* UIView+SILAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A87F1BC41462001948C1 /* UIView+SILAnimations.m */; }; 07B8A8841BC47615001948C1 /* SILServiceTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8831BC47615001948C1 /* SILServiceTableModel.m */; }; 07B8A8871BC47790001948C1 /* SILCharacteristicTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8861BC47790001948C1 /* SILCharacteristicTableModel.m */; }; @@ -88,10 +85,8 @@ 0C2FCB0C1F9A542300F4F259 /* SILBluetoothModelManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA74D1BD5858F00C2B07E /* SILBluetoothModelManager.m */; }; 0C2FCB0D1F9A542300F4F259 /* SILSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A3501A8D4FD100DAAFD3 /* SILSettings.m */; }; 0C2FCB0E1F9A542300F4F259 /* SILWeakTargetWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661F1A855DA8006C6B2F /* SILWeakTargetWrapper.m */; }; - 0C2FCB101F9A542300F4F259 /* SILBeaconRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEB1A73040C0004B2F4 /* SILBeaconRegistry.m */; }; 0C2FCB111F9A542300F4F259 /* SILDebugServiceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8CF1BCD6E3B001948C1 /* SILDebugServiceTableViewCell.m */; }; 0C2FCB121F9A542300F4F259 /* SILDebugAdvDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138461BE99502001EFE7E /* SILDebugAdvDetailsViewController.m */; }; - 0C2FCB151F9A542300F4F259 /* SILBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD771BFD043000D4D454 /* SILBeaconViewModel.m */; }; 0C2FCB171F9A542300F4F259 /* SILCharacteristicFieldValueResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB2B1BDFEE35009527B0 /* SILCharacteristicFieldValueResolver.m */; }; 0C2FCB181F9A542300F4F259 /* SILBluetoothBitFieldModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA75E1BD687CC00C2B07E /* SILBluetoothBitFieldModel.m */; }; 0C2FCB191F9A542300F4F259 /* SILDiscoveredPeripheralDisplayDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 49BB94B41E68FB4B0068670E /* SILDiscoveredPeripheralDisplayDataViewModel.m */; }; @@ -103,7 +98,6 @@ 0C2FCB1F1F9A542300F4F259 /* SILDoubleKeyDictionaryPair.m in Sources */ = {isa = PBXBuildFile; fileRef = 0752B7811BDEEC2C0064CBF0 /* SILDoubleKeyDictionaryPair.m */; }; 0C2FCB211F9A542300F4F259 /* SILBluetoothEnumerationModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7611BD688D300C2B07E /* SILBluetoothEnumerationModel.m */; }; 0C2FCB221F9A542300F4F259 /* RSSISliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43591F291C17001EE0D2 /* RSSISliderTableViewCell.swift */; }; - 0C2FCB241F9A542300F4F259 /* SILBGBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7A1BFD05CA00D4D454 /* SILBGBeaconViewModel.m */; }; 0C2FCB281F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB311BE10E07009527B0 /* SILDebugCharacteristicValueFieldTableViewCell.m */; }; 0C2FCB291F9A542300F4F259 /* UIImage+SILImages.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF71A7304210004B2F4 /* UIImage+SILImages.m */; }; 0C2FCB2B1F9A542300F4F259 /* TLMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA162DCD1F0541B600E3DB22 /* TLMData.swift */; }; @@ -111,13 +105,11 @@ 0C2FCB2D1F9A542300F4F259 /* SILTextFieldEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138571BE9B014001EFE7E /* SILTextFieldEntryCell.m */; }; 0C2FCB2E1F9A542300F4F259 /* SILAdvertisementDataModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0755DD061BD086180003886D /* SILAdvertisementDataModel.m */; }; 0C2FCB2F1F9A542300F4F259 /* SILCharacteristicTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8861BC47790001948C1 /* SILCharacteristicTableModel.m */; }; - 0C2FCB301F9A542300F4F259 /* SILBeaconDataModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40D1EFC62C7007C14D3 /* SILBeaconDataModel.m */; }; 0C2FCB311F9A542300F4F259 /* SILDebugCharacteristicPropertyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8C91BCD6E3B001948C1 /* SILDebugCharacteristicPropertyView.m */; }; 0C2FCB321F9A542300F4F259 /* CBPeripheral+Services.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6A1E7057F800AE9E56 /* CBPeripheral+Services.m */; }; 0C2FCB331F9A542300F4F259 /* SILDebugProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A89B1BC5F872001948C1 /* SILDebugProperty.m */; }; 0C2FCB341F9A542300F4F259 /* SILPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEA1E7A2EC200223F3E /* SILPopoverViewController.m */; }; 0C2FCB351F9A542300F4F259 /* SILThermometerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */; }; - 0C2FCB361F9A542300F4F259 /* SILRetailBeaconDetailsHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */; }; 0C2FCB371F9A542300F4F259 /* SILOTAHUDPeripheralViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4E508D1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m */; }; 0C2FCB381F9A542300F4F259 /* SILDebugAdvDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138481BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m */; }; 0C2FCB391F9A542300F4F259 /* EddystoneScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4977B0311E607D99006DE78C /* EddystoneScanner.swift */; }; @@ -126,23 +118,17 @@ 0C2FCB3D1F9A542300F4F259 /* SILApp+AttributedProfiles.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE01A76B67900676C5C /* SILApp+AttributedProfiles.m */; }; 0C2FCB3E1F9A542300F4F259 /* SILOTAFirmwareFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C91E7719D40057C1E0 /* SILOTAFirmwareFile.m */; }; 0C2FCB3F1F9A542300F4F259 /* SILBitRowModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0752B77B1BDED93F0064CBF0 /* SILBitRowModel.m */; }; - 0C2FCB401F9A542300F4F259 /* SILRetailBeaconDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40A1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m */; }; 0C2FCB411F9A542300F4F259 /* SILCollectionViewRightAlignedFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C8FF0E1A71A9A900DF062F /* SILCollectionViewRightAlignedFlowLayout.m */; }; 0C2FCB421F9A542300F4F259 /* UIColor+SILColors.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBDA1A767DCB00676C5C /* UIColor+SILColors.m */; }; - 0C2FCB431F9A542300F4F259 /* SILBeaconRegistryEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD891BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m */; }; 0C2FCB441F9A542300F4F259 /* SILAppSelectionInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE41A76C0E800676C5C /* SILAppSelectionInfoViewController.m */; }; 0C2FCB451F9A542300F4F259 /* SILTemperatureType.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CDEB3B1A698F4900AC7B33 /* SILTemperatureType.m */; }; 0C2FCB471F9A542300F4F259 /* CBService+Categories.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6D1E70585100AE9E56 /* CBService+Categories.m */; }; - 0C2FCB481F9A542300F4F259 /* SILEddystoneBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 495BF34F1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m */; }; 0C2FCB491F9A542300F4F259 /* SILBluetoothDescriptorModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7581BD5CDB400C2B07E /* SILBluetoothDescriptorModel.m */; }; 0C2FCB4A1F9A542300F4F259 /* SILRoundedViewBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B3EC9B1AADD9BD005A687F /* SILRoundedViewBehaviour.m */; }; 0C2FCB4B1F9A542300F4F259 /* EddystoneBeacon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E96F3E1E64618E00516DDC /* EddystoneBeacon.swift */; }; - 0C2FCB4C1F9A542300F4F259 /* SILAltBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD831BFD0B4F00D4D454 /* SILAltBeaconViewModel.m */; }; 0C2FCB4D1F9A542300F4F259 /* SILKeyValueViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C01E77170B0057C1E0 /* SILKeyValueViewModel.m */; }; 0C2FCB4E1F9A542300F4F259 /* SILDebugCharacteristicEnumerationFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB3B1BE1844E009527B0 /* SILDebugCharacteristicEnumerationFieldTableViewCell.m */; }; 0C2FCB4F1F9A542300F4F259 /* SILTemperatureMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CDEB3E1A69911D00AC7B33 /* SILTemperatureMeasurement.m */; }; - 0C2FCB501F9A542300F4F259 /* SILRetailBeaconAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEF1A73040C0004B2F4 /* SILRetailBeaconAppViewController.m */; }; - 0C2FCB511F9A542300F4F259 /* SILBeaconDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF4101EFC65F5007C14D3 /* SILBeaconDataViewModel.m */; }; 0C2FCB521F9A542300F4F259 /* SILOTAFirmwareUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C61E7717A90057C1E0 /* SILOTAFirmwareUpdate.m */; }; 0C2FCB531F9A542300F4F259 /* SILBodySensorLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773EF1A684B9700B93058 /* SILBodySensorLocation.m */; }; 0C2FCB541F9A542300F4F259 /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43571F27EA65001EE0D2 /* TextFieldTableViewCell.swift */; }; @@ -158,7 +144,6 @@ 0C2FCB631F9A542300F4F259 /* SILOTAFirmwareUpdateManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 49C10EEE1E71E0EC00C67256 /* SILOTAFirmwareUpdateManager.m */; }; 0C2FCB641F9A542300F4F259 /* SILOTAProgressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEF1E7A33C900223F3E /* SILOTAProgressViewController.m */; }; 0C2FCB651F9A542300F4F259 /* SILDebugCharacteristicToggleFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB361BE179EE009527B0 /* SILDebugCharacteristicToggleFieldTableViewCell.m */; }; - 0C2FCB661F9A542300F4F259 /* SILRSSIMeasurementTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */; }; 0C2FCB671F9A542300F4F259 /* SILConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E461A82C1C400510E39 /* SILConstants.m */; }; 0C2FCB681F9A542300F4F259 /* SILCalibrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A34D1A8D1F3000DAAFD3 /* SILCalibrationViewController.m */; }; 0C2FCB6B1F9A542300F4F259 /* SILBitFieldFieldModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 070167491BE2B5B70075F005 /* SILBitFieldFieldModel.m */; }; @@ -174,21 +159,17 @@ 0C2FCB7A1F9A542300F4F259 /* SILServiceTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8831BC47615001948C1 /* SILServiceTableModel.m */; }; 0C2FCB7B1F9A542300F4F259 /* SILBluetoothSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 79E62B4E1ECCA29400344CAA /* SILBluetoothSearch.m */; }; 0C2FCB7D1F9A542300F4F259 /* SILBluetoothCharacteristicModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7551BD5C89700C2B07E /* SILBluetoothCharacteristicModel.m */; }; - 0C2FCB7E1F9A542300F4F259 /* SILDiscoveredPeripheral.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773DC1A670F8100B93058 /* SILDiscoveredPeripheral.m */; }; 0C2FCB7F1F9A542300F4F259 /* SILBluetoothServiceModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7521BD5BD3500C2B07E /* SILBluetoothServiceModel.m */; }; 0C2FCB801F9A542300F4F259 /* SILWeakNotificationPair.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C667231B0A5BD90083C248 /* SILWeakNotificationPair.m */; }; 0C2FCB811F9A542300F4F259 /* GradientSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435D1F2A3F15001EE0D2 /* GradientSlider.swift */; }; 0C2FCB831F9A542300F4F259 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CAA2251A64011900A49DAF /* main.m */; }; 0C2FCB841F9A542300F4F259 /* DebugDeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA154A71F20F3D8002078B5 /* DebugDeviceViewModel.swift */; }; 0C2FCB851F9A542300F4F259 /* SILDebugPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138361BE97370001EFE7E /* SILDebugPopoverViewController.m */; }; - 0C2FCB861F9A542300F4F259 /* SILRSSIMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */; }; - 0C2FCB871F9A542300F4F259 /* SILIBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7D1BFD0A2100D4D454 /* SILIBeaconViewModel.m */; }; 0C2FCB891F9A542300F4F259 /* SILBluetoothBitModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA75B1BD6862A00C2B07E /* SILBluetoothBitModel.m */; }; 0C2FCB8B1F9A542300F4F259 /* NSDictionary+SILErrorCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93AA1E757FB10057C1E0 /* NSDictionary+SILErrorCode.m */; }; 0C2FCB8C1F9A542300F4F259 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435B1F2A35A3001EE0D2 /* GradientView.swift */; }; 0C2FCB8E1F9A542300F4F259 /* KZBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E64C95BB1AB20DB60029E23A /* KZBehaviour.m */; }; 0C2FCB8F1F9A542300F4F259 /* SILDescriptorTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8891BC47834001948C1 /* SILDescriptorTableModel.m */; }; - 0C2FCB901F9A542300F4F259 /* SILBeaconRegistryEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFED1A73040C0004B2F4 /* SILBeaconRegistryEntry.m */; }; 0C2FCB911F9A542300F4F259 /* SILBarGraphCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C8FF0B1A71869A00DF062F /* SILBarGraphCollectionViewCell.m */; }; 0C2FCB931F9A542300F4F259 /* NSError+SILHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF51A7304210004B2F4 /* NSError+SILHelpers.m */; }; 0C2FCB941F9A542300F4F259 /* SILAdvertisementDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 49BB94B11E68F2DC0068670E /* SILAdvertisementDataViewModel.m */; }; @@ -254,11 +235,9 @@ 0F4E50FF21525FDC00F58ACE /* SILBluetoothModelManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA74D1BD5858F00C2B07E /* SILBluetoothModelManager.m */; }; 0F4E510021525FDC00F58ACE /* SILSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A3501A8D4FD100DAAFD3 /* SILSettings.m */; }; 0F4E510121525FDC00F58ACE /* SILWeakTargetWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661F1A855DA8006C6B2F /* SILWeakTargetWrapper.m */; }; - 0F4E510321525FDC00F58ACE /* SILBeaconRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEB1A73040C0004B2F4 /* SILBeaconRegistry.m */; }; 0F4E510421525FDC00F58ACE /* SILDebugServiceTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8CF1BCD6E3B001948C1 /* SILDebugServiceTableViewCell.m */; }; 0F4E510521525FDC00F58ACE /* SILDebugAdvDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138461BE99502001EFE7E /* SILDebugAdvDetailsViewController.m */; }; 0F4E510621525FDC00F58ACE /* SILDeviceSelectionViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CC658141F96C3C200953CDC /* SILDeviceSelectionViewModel.m */; }; - 0F4E510821525FDC00F58ACE /* SILBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD771BFD043000D4D454 /* SILBeaconViewModel.m */; }; 0F4E510A21525FDC00F58ACE /* SILCharacteristicFieldValueResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB2B1BDFEE35009527B0 /* SILCharacteristicFieldValueResolver.m */; }; 0F4E510B21525FDC00F58ACE /* SILBluetoothBitFieldModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA75E1BD687CC00C2B07E /* SILBluetoothBitFieldModel.m */; }; 0F4E510C21525FDC00F58ACE /* SILDiscoveredPeripheralDisplayDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 49BB94B41E68FB4B0068670E /* SILDiscoveredPeripheralDisplayDataViewModel.m */; }; @@ -270,7 +249,6 @@ 0F4E511221525FDC00F58ACE /* SILDoubleKeyDictionaryPair.m in Sources */ = {isa = PBXBuildFile; fileRef = 0752B7811BDEEC2C0064CBF0 /* SILDoubleKeyDictionaryPair.m */; }; 0F4E511321525FDC00F58ACE /* SILBluetoothEnumerationModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7611BD688D300C2B07E /* SILBluetoothEnumerationModel.m */; }; 0F4E511421525FDC00F58ACE /* RSSISliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43591F291C17001EE0D2 /* RSSISliderTableViewCell.swift */; }; - 0F4E511521525FDC00F58ACE /* SILBGBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7A1BFD05CA00D4D454 /* SILBGBeaconViewModel.m */; }; 0F4E511821525FDC00F58ACE /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB311BE10E07009527B0 /* SILDebugCharacteristicValueFieldTableViewCell.m */; }; 0F4E511921525FDC00F58ACE /* UIImage+SILImages.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF71A7304210004B2F4 /* UIImage+SILImages.m */; }; 0F4E511B21525FDC00F58ACE /* TLMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA162DCD1F0541B600E3DB22 /* TLMData.swift */; }; @@ -278,14 +256,12 @@ 0F4E511D21525FDC00F58ACE /* SILTextFieldEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138571BE9B014001EFE7E /* SILTextFieldEntryCell.m */; }; 0F4E511E21525FDC00F58ACE /* SILAdvertisementDataModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0755DD061BD086180003886D /* SILAdvertisementDataModel.m */; }; 0F4E511F21525FDC00F58ACE /* SILCharacteristicTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8861BC47790001948C1 /* SILCharacteristicTableModel.m */; }; - 0F4E512021525FDC00F58ACE /* SILBeaconDataModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40D1EFC62C7007C14D3 /* SILBeaconDataModel.m */; }; 0F4E512121525FDC00F58ACE /* SILRangeTestAppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F3EB6B620C524C30062A7C1 /* SILRangeTestAppViewModel.swift */; }; 0F4E512221525FDC00F58ACE /* SILDebugCharacteristicPropertyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8C91BCD6E3B001948C1 /* SILDebugCharacteristicPropertyView.m */; }; 0F4E512321525FDC00F58ACE /* CBPeripheral+Services.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6A1E7057F800AE9E56 /* CBPeripheral+Services.m */; }; 0F4E512421525FDC00F58ACE /* SILDebugProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A89B1BC5F872001948C1 /* SILDebugProperty.m */; }; 0F4E512521525FDC00F58ACE /* SILPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEA1E7A2EC200223F3E /* SILPopoverViewController.m */; }; 0F4E512621525FDC00F58ACE /* SILThermometerSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBEA1A77C75400676C5C /* SILThermometerSegmentedControl.m */; }; - 0F4E512721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */; }; 0F4E512821525FDC00F58ACE /* SILOTAHUDPeripheralViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = DC4E508D1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m */; }; 0F4E512921525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138481BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m */; }; 0F4E512A21525FDC00F58ACE /* EddystoneScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4977B0311E607D99006DE78C /* EddystoneScanner.swift */; }; @@ -294,28 +270,22 @@ 0F4E512E21525FDC00F58ACE /* SILApp+AttributedProfiles.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE01A76B67900676C5C /* SILApp+AttributedProfiles.m */; }; 0F4E512F21525FDC00F58ACE /* SILOTAFirmwareFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C91E7719D40057C1E0 /* SILOTAFirmwareFile.m */; }; 0F4E513021525FDC00F58ACE /* SILBitRowModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 0752B77B1BDED93F0064CBF0 /* SILBitRowModel.m */; }; - 0F4E513121525FDC00F58ACE /* SILRetailBeaconDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40A1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m */; }; 0F4E513221525FDC00F58ACE /* SILCollectionViewRightAlignedFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C8FF0E1A71A9A900DF062F /* SILCollectionViewRightAlignedFlowLayout.m */; }; 0F4E513321525FDC00F58ACE /* UIColor+SILColors.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBDA1A767DCB00676C5C /* UIColor+SILColors.m */; }; - 0F4E513421525FDC00F58ACE /* SILBeaconRegistryEntryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD891BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m */; }; 0F4E513521525FDC00F58ACE /* SILAppSelectionInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E666CBE41A76C0E800676C5C /* SILAppSelectionInfoViewController.m */; }; 0F4E513621525FDC00F58ACE /* SILTemperatureType.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CDEB3B1A698F4900AC7B33 /* SILTemperatureType.m */; }; 0F4E513821525FDC00F58ACE /* SILCentralManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E401A82809B00510E39 /* SILCentralManager.m */; }; 0F4E513921525FDC00F58ACE /* CBService+Categories.m in Sources */ = {isa = PBXBuildFile; fileRef = 4924BA6D1E70585100AE9E56 /* CBService+Categories.m */; }; - 0F4E513A21525FDC00F58ACE /* SILEddystoneBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 495BF34F1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m */; }; 0F4E513B21525FDC00F58ACE /* SILBluetoothDescriptorModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7581BD5CDB400C2B07E /* SILBluetoothDescriptorModel.m */; }; 0F4E513C21525FDC00F58ACE /* SILRoundedViewBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B3EC9B1AADD9BD005A687F /* SILRoundedViewBehaviour.m */; }; 0F4E513D21525FDC00F58ACE /* SILRangeTestSettingValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8163C020C52386008B98C6 /* SILRangeTestSettingValue.swift */; }; 0F4E513E21525FDC00F58ACE /* EddystoneBeacon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E96F3E1E64618E00516DDC /* EddystoneBeacon.swift */; }; 0F4E513F21525FDC00F58ACE /* SILRangeTestModeSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F3AC36520ADAABF0010091C /* SILRangeTestModeSelectionViewController.swift */; }; 0F4E514021525FDC00F58ACE /* SILRangeTestPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FD15EB920BD3D0D00ED43BB /* SILRangeTestPeripheral.swift */; }; - 0F4E514121525FDC00F58ACE /* SILAltBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD831BFD0B4F00D4D454 /* SILAltBeaconViewModel.m */; }; 0F4E514221525FDC00F58ACE /* SILKeyValueViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C01E77170B0057C1E0 /* SILKeyValueViewModel.m */; }; 0F4E514321525FDC00F58ACE /* SILDebugCharacteristicEnumerationFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB3B1BE1844E009527B0 /* SILDebugCharacteristicEnumerationFieldTableViewCell.m */; }; 0F4E514421525FDC00F58ACE /* SILRangeTestCharacteristic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F44BCAD20EE5E0E00CD27B3 /* SILRangeTestCharacteristic.swift */; }; 0F4E514521525FDC00F58ACE /* SILTemperatureMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CDEB3E1A69911D00AC7B33 /* SILTemperatureMeasurement.m */; }; - 0F4E514621525FDC00F58ACE /* SILRetailBeaconAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEF1A73040C0004B2F4 /* SILRetailBeaconAppViewController.m */; }; - 0F4E514721525FDC00F58ACE /* SILBeaconDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF4101EFC65F5007C14D3 /* SILBeaconDataViewModel.m */; }; 0F4E514821525FDC00F58ACE /* SILOTAFirmwareUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C61E7717A90057C1E0 /* SILOTAFirmwareUpdate.m */; }; 0F4E514921525FDC00F58ACE /* SILBodySensorLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773EF1A684B9700B93058 /* SILBodySensorLocation.m */; }; 0F4E514A21525FDC00F58ACE /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43571F27EA65001EE0D2 /* TextFieldTableViewCell.swift */; }; @@ -333,7 +303,6 @@ 0F4E515821525FDC00F58ACE /* SILOTAFirmwareUpdateManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 49C10EEE1E71E0EC00C67256 /* SILOTAFirmwareUpdateManager.m */; }; 0F4E515921525FDC00F58ACE /* SILOTAProgressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEF1E7A33C900223F3E /* SILOTAProgressViewController.m */; }; 0F4E515A21525FDC00F58ACE /* SILDebugCharacteristicToggleFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0702AB361BE179EE009527B0 /* SILDebugCharacteristicToggleFieldTableViewCell.m */; }; - 0F4E515B21525FDC00F58ACE /* SILRSSIMeasurementTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */; }; 0F4E515C21525FDC00F58ACE /* SILConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E461A82C1C400510E39 /* SILConstants.m */; }; 0F4E515D21525FDC00F58ACE /* SILCalibrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A34D1A8D1F3000DAAFD3 /* SILCalibrationViewController.m */; }; 0F4E515F21525FDC00F58ACE /* SILBitFieldFieldModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 070167491BE2B5B70075F005 /* SILBitFieldFieldModel.m */; }; @@ -350,22 +319,18 @@ 0F4E516F21525FDC00F58ACE /* SILServiceTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8831BC47615001948C1 /* SILServiceTableModel.m */; }; 0F4E517021525FDC00F58ACE /* SILBluetoothSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 79E62B4E1ECCA29400344CAA /* SILBluetoothSearch.m */; }; 0F4E517121525FDC00F58ACE /* SILBluetoothCharacteristicModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7551BD5C89700C2B07E /* SILBluetoothCharacteristicModel.m */; }; - 0F4E517221525FDC00F58ACE /* SILDiscoveredPeripheral.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773DC1A670F8100B93058 /* SILDiscoveredPeripheral.m */; }; 0F4E517321525FDC00F58ACE /* SILBluetoothServiceModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA7521BD5BD3500C2B07E /* SILBluetoothServiceModel.m */; }; 0F4E517421525FDC00F58ACE /* SILWeakNotificationPair.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C667231B0A5BD90083C248 /* SILWeakNotificationPair.m */; }; 0F4E517521525FDC00F58ACE /* GradientSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435D1F2A3F15001EE0D2 /* GradientSlider.swift */; }; 0F4E517721525FDC00F58ACE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CAA2251A64011900A49DAF /* main.m */; }; 0F4E517821525FDC00F58ACE /* DebugDeviceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA154A71F20F3D8002078B5 /* DebugDeviceViewModel.swift */; }; 0F4E517921525FDC00F58ACE /* SILDebugPopoverViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 078138361BE97370001EFE7E /* SILDebugPopoverViewController.m */; }; - 0F4E517A21525FDC00F58ACE /* SILRSSIMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */; }; - 0F4E517B21525FDC00F58ACE /* SILIBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B1AD7D1BFD0A2100D4D454 /* SILIBeaconViewModel.m */; }; 0F4E517C21525FDC00F58ACE /* SILBluetoothBitModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BBA75B1BD6862A00C2B07E /* SILBluetoothBitModel.m */; }; 0F4E517E21525FDC00F58ACE /* SILRangeTestTXValueUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F75B9D021395E3200E8D9E9 /* SILRangeTestTXValueUpdater.swift */; }; 0F4E517F21525FDC00F58ACE /* NSDictionary+SILErrorCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93AA1E757FB10057C1E0 /* NSDictionary+SILErrorCode.m */; }; 0F4E518021525FDC00F58ACE /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435B1F2A35A3001EE0D2 /* GradientView.swift */; }; 0F4E518121525FDC00F58ACE /* KZBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E64C95BB1AB20DB60029E23A /* KZBehaviour.m */; }; 0F4E518221525FDC00F58ACE /* SILDescriptorTableModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 07B8A8891BC47834001948C1 /* SILDescriptorTableModel.m */; }; - 0F4E518321525FDC00F58ACE /* SILBeaconRegistryEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFED1A73040C0004B2F4 /* SILBeaconRegistryEntry.m */; }; 0F4E518421525FDC00F58ACE /* SILBarGraphCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C8FF0B1A71869A00DF062F /* SILBarGraphCollectionViewCell.m */; }; 0F4E518621525FDC00F58ACE /* SILRangeTestManufacturerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F69656820ECD32A0083C32A /* SILRangeTestManufacturerData.swift */; }; 0F4E518721525FDC00F58ACE /* NSError+SILHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF51A7304210004B2F4 /* NSError+SILHelpers.m */; }; @@ -503,7 +468,6 @@ 1E26ECB825501A0A002FFAAB /* SILAdvertiserAdTypeCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECA525501A0A002FFAAB /* SILAdvertiserAdTypeCellViewModel.swift */; }; 1E26ECB925501A0A002FFAAB /* SILAdvertiserHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECA625501A0A002FFAAB /* SILAdvertiserHomeViewModel.swift */; }; 1E26ECBA25501A0A002FFAAB /* SILLocalNameSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECA825501A0A002FFAAB /* SILLocalNameSettingViewModel.swift */; }; - 1E26ECBB25501A0A002FFAAB /* SILAdvertiserRemoveWarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECAA25501A0A002FFAAB /* SILAdvertiserRemoveWarningViewModel.swift */; }; 1E26ECBC25501A0A002FFAAB /* SILAdvertisingDataButtonCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECAC25501A0A002FFAAB /* SILAdvertisingDataButtonCellViewModel.swift */; }; 1E26ECBD25501A0A002FFAAB /* SILAdvertiserDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECAD25501A0A002FFAAB /* SILAdvertiserDetailsViewModel.swift */; }; 1E26ECBE25501A0A002FFAAB /* SILAdvertisingDataTitleCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E26ECAE25501A0A002FFAAB /* SILAdvertisingDataTitleCellViewModel.swift */; }; @@ -687,7 +651,6 @@ 1EC92EF123A393D400FD2219 /* HomeScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF023A393D400FD2219 /* HomeScreen.storyboard */; }; 1EC92EF323A3955000FD2219 /* SILAppTypeConnectedLighting.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF223A3955000FD2219 /* SILAppTypeConnectedLighting.storyboard */; }; 1EC92EF523A3956B00FD2219 /* SILAppTypeHealthThermometer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF423A3956B00FD2219 /* SILAppTypeHealthThermometer.storyboard */; }; - 1EC92EF723A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF623A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard */; }; 1EC92EF923A3959200FD2219 /* SILAppBluetoothBrowser.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EF823A3959200FD2219 /* SILAppBluetoothBrowser.storyboard */; }; 1EC92EFB23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1EC92EFA23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard */; }; 1ED5E25E2689F7230048A171 /* SILGattDescriptorMarkerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED5E25D2689F7230048A171 /* SILGattDescriptorMarkerTest.swift */; }; @@ -768,7 +731,7 @@ 1EFC76AB26AEDFA40035594E /* SILGattAssignedNumberDropDownInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC769526AEDFA40035594E /* SILGattAssignedNumberDropDownInfo.swift */; }; 1EFC76AC26AEDFA40035594E /* SILDefaultDescriptorsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC769626AEDFA40035594E /* SILDefaultDescriptorsHelper.swift */; }; 1EFC76AD26AEDFA40035594E /* SILGattAssignedNumberInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC769726AEDFA40035594E /* SILGattAssignedNumberInfo.swift */; }; - 1EFC76AE26AEDFA40035594E /* SILGattConfiguratorFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC769826AEDFA40035594E /* SILGattConfiguratorFileWriter.swift */; }; + 1EFC76AE26AEDFA40035594E /* SILFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC769826AEDFA40035594E /* SILFileWriter.swift */; }; 1EFC76B626AEE0120035594E /* SILBlinkyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76B526AEE0120035594E /* SILBlinkyViewModel.swift */; }; 1EFC76BE26AEE02A0035594E /* SILThroughputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76BD26AEE02A0035594E /* SILThroughputViewModel.swift */; }; 1EFC76C926AEE0420035594E /* SILSortViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76C626AEE0420035594E /* SILSortViewModel.swift */; }; @@ -777,7 +740,6 @@ 1EFC76D626AEE05C0035594E /* SILExitWarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76D326AEE05C0035594E /* SILExitWarningViewModel.swift */; }; 1EFC76D726AEE05C0035594E /* SILRemoveWarningViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76D426AEE05C0035594E /* SILRemoveWarningViewModel.swift */; }; 1EFC76DE26AEE07D0035594E /* SILErrorDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76DD26AEE07D0035594E /* SILErrorDetailsViewModel.swift */; }; - 1EFC76E526AEE0E00035594E /* SILBeaconRegistryEntryViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76E426AEE0DF0035594E /* SILBeaconRegistryEntryViewModel.m */; }; 1EFC76ED26AEE1010035594E /* SILOTAProgressViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76EC26AEE1010035594E /* SILOTAProgressViewModel.m */; }; 1EFC76F426AEE1270035594E /* DebugDeviceFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76F326AEE1270035594E /* DebugDeviceFilterViewModel.swift */; }; 1EFC76FC26AEE1610035594E /* SILBrowserLogViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFC76FA26AEE1610035594E /* SILBrowserLogViewModel.m */; }; @@ -889,7 +851,6 @@ 494F93C41E7717540057C1E0 /* SILOTAFirmwareUpdateViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C31E7717540057C1E0 /* SILOTAFirmwareUpdateViewModel.m */; }; 494F93C71E7717A90057C1E0 /* SILOTAFirmwareUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C61E7717A90057C1E0 /* SILOTAFirmwareUpdate.m */; }; 494F93CA1E7719D40057C1E0 /* SILOTAFirmwareFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 494F93C91E7719D40057C1E0 /* SILOTAFirmwareFile.m */; }; - 495BF3501E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 495BF34F1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m */; }; 496A42F31E7C582F006E87F2 /* SILBigRedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 496A42F21E7C582F006E87F2 /* SILBigRedButton.m */; }; 4977B0321E607D99006DE78C /* EddystoneScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4977B0311E607D99006DE78C /* EddystoneScanner.swift */; }; 4990CB48CB947376AC957E0D /* Pods_SiliconLabsAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53141C6BB0A69CCB49A16AB2 /* Pods_SiliconLabsAppTests.framework */; }; @@ -901,7 +862,6 @@ 49FB4BED1E7A2EC200223F3E /* SILPopoverViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 49FB4BEB1E7A2EC200223F3E /* SILPopoverViewController.xib */; }; 49FB4BF11E7A33C900223F3E /* SILOTAProgressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 49FB4BEF1E7A33C900223F3E /* SILOTAProgressViewController.m */; }; 49FB4BF21E7A33C900223F3E /* SILOTAProgressViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 49FB4BF01E7A33C900223F3E /* SILOTAProgressViewController.xib */; }; - 4C126076241118A20086951A /* UIColor+SILColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C126075241118A20086951A /* UIColor+SILColors.swift */; }; 4C2C63382409242D0080CE76 /* SILFavoritePeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2C63372409242D0080CE76 /* SILFavoritePeripheral.swift */; }; 4C2CB431240E5A110079040D /* SILServiceMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CB430240E5A110079040D /* SILServiceMap.swift */; }; 4C2CB433240E5A340079040D /* SILCharacteristicMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CB432240E5A340079040D /* SILCharacteristicMap.swift */; }; @@ -957,12 +917,19 @@ 80066FF72768AFB6007A861C /* String+VersionCompare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80066FF62768AFB6007A861C /* String+VersionCompare.swift */; }; 8008B8FD25E67DB400439F3A /* DebugDeviceFilterViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8008B8FC25E67DB400439F3A /* DebugDeviceFilterViewModelSpec.swift */; }; 8008B99925E6918400439F3A /* SILTemperatureMeasurementSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8008B99825E6918400439F3A /* SILTemperatureMeasurementSpec.m */; }; + 8011D02827BFF01000F08754 /* SILGraphView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8011D02727BFF01000F08754 /* SILGraphView.xib */; }; + 8011D02D27BFF09200F08754 /* SILGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011D02C27BFF09200F08754 /* SILGraphView.swift */; }; + 8011D03027CE587F00F08754 /* SILRSSIGraphViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011D02F27CE587F00F08754 /* SILRSSIGraphViewModel.swift */; }; 8012744E278C30E5006E12E5 /* Data+ManufacturerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8012744D278C30E5006E12E5 /* Data+ManufacturerData.swift */; }; + 80280F0B27D8BC24008D6F60 /* SILRSSIMeasurementTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80280F0A27D8BC24008D6F60 /* SILRSSIMeasurementTable.swift */; }; + 80280F0F27DA12DB008D6F60 /* SILDiscoveredPeripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80280F0E27DA12DA008D6F60 /* SILDiscoveredPeripheral.swift */; }; + 80280F1127DA25B2008D6F60 /* CLLocationManager+Reactive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80280F1027DA25B2008D6F60 /* CLLocationManager+Reactive.swift */; }; 802A28A327566F7800E701A1 /* SILBlinkyPeripheralGATTDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802A28A227566F7800E701A1 /* SILBlinkyPeripheralGATTDatabase.swift */; }; 802A28AA27566F9D00E701A1 /* SILThunderboardPeripheralGATTDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802A28A927566F9D00E701A1 /* SILThunderboardPeripheralGATTDatabase.swift */; }; 802A2997275778AF00E701A1 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802A2996275778AF00E701A1 /* DemoViewController.swift */; }; 8041CCC8256E5A0900C5C368 /* SILDescriptorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8041CCC7256E5A0900C5C368 /* SILDescriptorTableViewCell.swift */; }; 8044307D270F008200DD3EA6 /* SILIOPTestReconnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044307C270F008200DD3EA6 /* SILIOPTestReconnectManager.swift */; }; + 8044F5FC28118A9300AF5ABC /* RSSIGraphViewLineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8044F5FB28118A9300AF5ABC /* RSSIGraphViewLineChartView.swift */; }; 80521478255A7A510021385F /* SILSortViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80521477255A7A510021385F /* SILSortViewController.swift */; }; 8052147E255A80F40021385F /* SILSortTypeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8052147D255A80F40021385F /* SILSortTypeViewCell.swift */; }; 80521484255A81070021385F /* SILSortModeViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80521483255A81070021385F /* SILSortModeViewCell.swift */; }; @@ -996,7 +963,12 @@ 809656842524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m in Sources */ = {isa = PBXBuildFile; fileRef = 809656832524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m */; }; 809656852524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m in Sources */ = {isa = PBXBuildFile; fileRef = 809656832524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m */; }; 809656862524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m in Sources */ = {isa = PBXBuildFile; fileRef = 809656832524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m */; }; + 809BEA2B27BEDB53006BC2D9 /* SILRSSIGraphViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 809BEA2927BEDB52006BC2D9 /* SILRSSIGraphViewController.swift */; }; + 809BEA2C27BEDB53006BC2D9 /* SILAppRSSIGraph.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 809BEA2A27BEDB52006BC2D9 /* SILAppRSSIGraph.storyboard */; }; + 809DE3CC27F4AC1E00BD71E0 /* SILRSSIGraphCentralManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8011D03127CE670700F08754 /* SILRSSIGraphCentralManager.swift */; }; 80AF191E2677A068002A58DB /* SILGattConfiguratorCheckBoxCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80AF191D2677A068002A58DB /* SILGattConfiguratorCheckBoxCellView.swift */; }; + 80B3E2D627D1044400838066 /* CBCentralManager+Reactive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3E2D527D1044400838066 /* CBCentralManager+Reactive.swift */; }; + 80B3E2DA27D77AE800838066 /* BehaviorRelay+Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3E2D927D77AE800838066 /* BehaviorRelay+Collections.swift */; }; 80B5FE73275F7474008F08A8 /* SILAppTypeWifiCommissioning.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 80B5FE72275F7474008F08A8 /* SILAppTypeWifiCommissioning.storyboard */; }; 80B5FE7A275F8FDD008F08A8 /* String+Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B5FE79275F8FDC008F08A8 /* String+Subscript.swift */; }; 80B5FE81275F900B008F08A8 /* SILPeripheralDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B5FE80275F900B008F08A8 /* SILPeripheralDelegate.swift */; }; @@ -1022,6 +994,11 @@ 80C6706A25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */; }; 80C6706B25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */; }; 80CCC0652564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80CCC0642564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift */; }; + 80DBE36F282E4B0D00F7C579 /* SILIOPInfoPopup.xib in Resources */ = {isa = PBXBuildFile; fileRef = 80DBE36D282E4B0D00F7C579 /* SILIOPInfoPopup.xib */; }; + 80DBE370282E4B0D00F7C579 /* SILIOPInfoPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80DBE36E282E4B0D00F7C579 /* SILIOPInfoPopup.swift */; }; + 80FB2ED127FDA77A00241470 /* SILPeripheralDataFilterHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FB2ED027FDA77A00241470 /* SILPeripheralDataFilterHelper.swift */; }; + 80FB2ED327FF29CD00241470 /* UIViewController+ShowExportFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FB2ED227FF29CD00241470 /* UIViewController+ShowExportFiles.swift */; }; + 80FFFFAC27F1FBF7004F139F /* SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FFFFAB27F1FBF7004F139F /* SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift */; }; 9450946334099D8FAA384ABE /* Pods_BlueGeckoWithHomeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84342BE824D4F9248E13114A /* Pods_BlueGeckoWithHomeKit.framework */; }; 9B44734425137C6100355E0A /* Colours.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B44734325137C6100355E0A /* Colours.xcassets */; }; 9B44734525137C6100355E0A /* Colours.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9B44734325137C6100355E0A /* Colours.xcassets */; }; @@ -1035,15 +1012,11 @@ 9BCA4F122514C2BB00E3AADD /* SILBrowserSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BCA4F112514C2BB00E3AADD /* SILBrowserSettings.m */; }; AA0724F71F388D2D001446CA /* DebugDeviceFilterViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0724F61F388D2D001446CA /* DebugDeviceFilterViewModelTests.swift */; }; AA0FF4081EFC11AD007C14D3 /* SILRetailBeaconDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA0FF4061EFC11AD007C14D3 /* SILRetailBeaconDetailsViewController.xib */; }; - AA0FF40B1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40A1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m */; }; - AA0FF40E1EFC62C7007C14D3 /* SILBeaconDataModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF40D1EFC62C7007C14D3 /* SILBeaconDataModel.m */; }; - AA0FF4111EFC65F5007C14D3 /* SILBeaconDataViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AA0FF4101EFC65F5007C14D3 /* SILBeaconDataViewModel.m */; }; AA162DCE1F0541B600E3DB22 /* TLMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA162DCD1F0541B600E3DB22 /* TLMData.swift */; }; AA4D43581F27EA65001EE0D2 /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43571F27EA65001EE0D2 /* TextFieldTableViewCell.swift */; }; AA4D435A1F291C17001EE0D2 /* RSSISliderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D43591F291C17001EE0D2 /* RSSISliderTableViewCell.swift */; }; AA4D435C1F2A35A3001EE0D2 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435B1F2A35A3001EE0D2 /* GradientView.swift */; }; AA4D435E1F2A3F15001EE0D2 /* GradientSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA4D435D1F2A3F15001EE0D2 /* GradientSlider.swift */; }; - AA5F62EB1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */; }; AA5F62ED1F0A859F007EDAAC /* SILRetailBeaconDetailsHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AA5F62EC1F0A859F007EDAAC /* SILRetailBeaconDetailsHeaderView.xib */; }; AA78173F1F33A86100B37B09 /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA78173E1F33A86100B37B09 /* UIFont+Extensions.swift */; }; AA7817431F34C5C500B37B09 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7817421F34C5C500B37B09 /* Int+Extensions.swift */; }; @@ -1058,8 +1031,6 @@ E607A36C1A9627E600DAAFD3 /* SILTemperatureMeasurementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E607A36B1A9627E600DAAFD3 /* SILTemperatureMeasurementTests.m */; }; E621B33F1A65573500223C5A /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E621B33E1A65573500223C5A /* CoreBluetooth.framework */; }; E621B34C1A655B3F00223C5A /* SILApp.m in Sources */ = {isa = PBXBuildFile; fileRef = E621B34B1A655B3F00223C5A /* SILApp.m */; }; - E642661A1A84F1D6006C6B2F /* SILRSSIMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */; }; - E642661D1A8527C0006C6B2F /* SILRSSIMeasurementTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */; }; E64266201A855DA8006C6B2F /* SILWeakTargetWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = E642661F1A855DA8006C6B2F /* SILWeakTargetWrapper.m */; }; E64266231A8565BA006C6B2F /* SILProximityCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = E64266221A8565BA006C6B2F /* SILProximityCalculator.m */; }; E64C95BC1AB20DB60029E23A /* KZBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E64C95BB1AB20DB60029E23A /* KZBehaviour.m */; }; @@ -1074,7 +1045,6 @@ E6A37E471A82C1C400510E39 /* SILConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = E6A37E461A82C1C400510E39 /* SILConstants.m */; }; E6B3EC9C1AADD9BD005A687F /* SILRoundedViewBehaviour.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B3EC9B1AADD9BD005A687F /* SILRoundedViewBehaviour.m */; }; E6B3ECA21AAE2C8B005A687F /* SILCalibrationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E6B3ECA01AAE2C8B005A687F /* SILCalibrationViewController.xib */; }; - E6B773DD1A670F8100B93058 /* SILDiscoveredPeripheral.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773DC1A670F8100B93058 /* SILDiscoveredPeripheral.m */; }; E6B773ED1A68474800B93058 /* SILHeartRateMeasurement.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773EC1A68474800B93058 /* SILHeartRateMeasurement.m */; }; E6B773F01A684B9700B93058 /* SILBodySensorLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = E6B773EF1A684B9700B93058 /* SILBodySensorLocation.m */; }; E6C667241B0A5BD90083C248 /* SILWeakNotificationPair.m in Sources */ = {isa = PBXBuildFile; fileRef = E6C667231B0A5BD90083C248 /* SILWeakNotificationPair.m */; }; @@ -1085,9 +1055,6 @@ E6CAA2311A64011900A49DAF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E6CAA2301A64011900A49DAF /* Images.xcassets */; }; E6CAA2341A64011900A49DAF /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = E6CAA2321A64011900A49DAF /* LaunchScreen.xib */; }; E6CCCFF01A73040C0004B2F4 /* SILBeacon.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFE91A73040C0004B2F4 /* SILBeacon.m */; }; - E6CCCFF11A73040C0004B2F4 /* SILBeaconRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEB1A73040C0004B2F4 /* SILBeaconRegistry.m */; }; - E6CCCFF21A73040C0004B2F4 /* SILBeaconRegistryEntry.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFED1A73040C0004B2F4 /* SILBeaconRegistryEntry.m */; }; - E6CCCFF31A73040C0004B2F4 /* SILRetailBeaconAppViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFEF1A73040C0004B2F4 /* SILRetailBeaconAppViewController.m */; }; E6CCCFFA1A7304210004B2F4 /* NSError+SILHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF51A7304210004B2F4 /* NSError+SILHelpers.m */; }; E6CCCFFB1A7304210004B2F4 /* UIImage+SILImages.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF71A7304210004B2F4 /* UIImage+SILImages.m */; }; E6CCCFFC1A7304210004B2F4 /* WYPopoverController+SILHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = E6CCCFF91A7304210004B2F4 /* WYPopoverController+SILHelpers.m */; }; @@ -1120,6 +1087,8 @@ 0461B3662719DD900076D627 /* EnvironmentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentCollectionViewCell.swift; sourceTree = ""; }; 048E4FD32719CFF7009D88A3 /* SimulatedIoDemoConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatedIoDemoConnection.swift; sourceTree = ""; }; 048E4FDF2719D022009D88A3 /* SimulatedDeviceScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatedDeviceScanner.swift; sourceTree = ""; }; + 04B1F49327DF74DA0083CB3C /* RSSIGraphYAxisRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSIGraphYAxisRenderer.swift; sourceTree = ""; }; + 04B1F49527DF76B40083CB3C /* RSSIGraphXAxisRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSIGraphXAxisRenderer.swift; sourceTree = ""; }; 06CC32BDFF67F87AF59FFD4D /* Pods-BlueGeckoWithHomeKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueGeckoWithHomeKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BlueGeckoWithHomeKit/Pods-BlueGeckoWithHomeKit.debug.xcconfig"; sourceTree = ""; }; 070167481BE2B5B70075F005 /* SILBitFieldFieldModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBitFieldFieldModel.h; sourceTree = ""; }; 070167491BE2B5B70075F005 /* SILBitFieldFieldModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBitFieldFieldModel.m; sourceTree = ""; }; @@ -1180,16 +1149,6 @@ 078138561BE9B014001EFE7E /* SILTextFieldEntryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILTextFieldEntryCell.h; sourceTree = ""; }; 078138571BE9B014001EFE7E /* SILTextFieldEntryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILTextFieldEntryCell.m; sourceTree = ""; }; 0797E34B1BF0071D0046EF0E /* SILTextFieldEntryCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILTextFieldEntryCell.xib; sourceTree = ""; }; - 07B1AD761BFD043000D4D454 /* SILBeaconViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILBeaconViewModel.h; path = SiliconLabsApp/ViewModels/SILBeaconViewModel.h; sourceTree = SOURCE_ROOT; }; - 07B1AD771BFD043000D4D454 /* SILBeaconViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SILBeaconViewModel.m; path = SiliconLabsApp/ViewModels/SILBeaconViewModel.m; sourceTree = SOURCE_ROOT; }; - 07B1AD791BFD05CA00D4D454 /* SILBGBeaconViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILBGBeaconViewModel.h; path = SiliconLabsApp/ViewModels/SILBGBeaconViewModel.h; sourceTree = SOURCE_ROOT; }; - 07B1AD7A1BFD05CA00D4D454 /* SILBGBeaconViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SILBGBeaconViewModel.m; path = SiliconLabsApp/ViewModels/SILBGBeaconViewModel.m; sourceTree = SOURCE_ROOT; }; - 07B1AD7C1BFD0A2100D4D454 /* SILIBeaconViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILIBeaconViewModel.h; path = SiliconLabsApp/ViewModels/SILIBeaconViewModel.h; sourceTree = SOURCE_ROOT; }; - 07B1AD7D1BFD0A2100D4D454 /* SILIBeaconViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SILIBeaconViewModel.m; path = SiliconLabsApp/ViewModels/SILIBeaconViewModel.m; sourceTree = SOURCE_ROOT; }; - 07B1AD821BFD0B4F00D4D454 /* SILAltBeaconViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILAltBeaconViewModel.h; path = SiliconLabsApp/ViewModels/SILAltBeaconViewModel.h; sourceTree = SOURCE_ROOT; }; - 07B1AD831BFD0B4F00D4D454 /* SILAltBeaconViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SILAltBeaconViewModel.m; path = SiliconLabsApp/ViewModels/SILAltBeaconViewModel.m; sourceTree = SOURCE_ROOT; }; - 07B1AD881BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeaconRegistryEntryCell.h; sourceTree = ""; }; - 07B1AD891BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconRegistryEntryCell.m; sourceTree = ""; }; 07B8A87E1BC41462001948C1 /* UIView+SILAnimations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+SILAnimations.h"; sourceTree = ""; }; 07B8A87F1BC41462001948C1 /* UIView+SILAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+SILAnimations.m"; sourceTree = ""; }; 07B8A8821BC47615001948C1 /* SILServiceTableModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILServiceTableModel.h; path = AttributeTableModels/SILServiceTableModel.h; sourceTree = ""; }; @@ -1358,7 +1317,6 @@ 1E26ECA525501A0A002FFAAB /* SILAdvertiserAdTypeCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertiserAdTypeCellViewModel.swift; sourceTree = ""; }; 1E26ECA625501A0A002FFAAB /* SILAdvertiserHomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertiserHomeViewModel.swift; sourceTree = ""; }; 1E26ECA825501A0A002FFAAB /* SILLocalNameSettingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILLocalNameSettingViewModel.swift; sourceTree = ""; }; - 1E26ECAA25501A0A002FFAAB /* SILAdvertiserRemoveWarningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertiserRemoveWarningViewModel.swift; sourceTree = ""; }; 1E26ECAC25501A0A002FFAAB /* SILAdvertisingDataButtonCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertisingDataButtonCellViewModel.swift; sourceTree = ""; }; 1E26ECAD25501A0A002FFAAB /* SILAdvertiserDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertiserDetailsViewModel.swift; sourceTree = ""; }; 1E26ECAE25501A0A002FFAAB /* SILAdvertisingDataTitleCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILAdvertisingDataTitleCellViewModel.swift; sourceTree = ""; }; @@ -1555,7 +1513,6 @@ 1EC92EF023A393D400FD2219 /* HomeScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = HomeScreen.storyboard; sourceTree = ""; }; 1EC92EF223A3955000FD2219 /* SILAppTypeConnectedLighting.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeConnectedLighting.storyboard; sourceTree = ""; }; 1EC92EF423A3956B00FD2219 /* SILAppTypeHealthThermometer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeHealthThermometer.storyboard; sourceTree = ""; }; - 1EC92EF623A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeRetailBeacon.storyboard; sourceTree = ""; }; 1EC92EF823A3959200FD2219 /* SILAppBluetoothBrowser.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppBluetoothBrowser.storyboard; sourceTree = ""; }; 1EC92EFA23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SILAppTypeHomeKitDebug.storyboard; sourceTree = ""; }; 1ED5E25D2689F7230048A171 /* SILGattDescriptorMarkerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILGattDescriptorMarkerTest.swift; sourceTree = ""; }; @@ -1646,7 +1603,7 @@ 1EFC769526AEDFA40035594E /* SILGattAssignedNumberDropDownInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILGattAssignedNumberDropDownInfo.swift; sourceTree = ""; }; 1EFC769626AEDFA40035594E /* SILDefaultDescriptorsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILDefaultDescriptorsHelper.swift; sourceTree = ""; }; 1EFC769726AEDFA40035594E /* SILGattAssignedNumberInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILGattAssignedNumberInfo.swift; sourceTree = ""; }; - 1EFC769826AEDFA40035594E /* SILGattConfiguratorFileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILGattConfiguratorFileWriter.swift; sourceTree = ""; }; + 1EFC769826AEDFA40035594E /* SILFileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILFileWriter.swift; sourceTree = ""; }; 1EFC76B526AEE0120035594E /* SILBlinkyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILBlinkyViewModel.swift; sourceTree = ""; }; 1EFC76BD26AEE02A0035594E /* SILThroughputViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILThroughputViewModel.swift; sourceTree = ""; }; 1EFC76C626AEE0420035594E /* SILSortViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILSortViewModel.swift; sourceTree = ""; }; @@ -1656,7 +1613,6 @@ 1EFC76D326AEE05C0035594E /* SILExitWarningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILExitWarningViewModel.swift; sourceTree = ""; }; 1EFC76D426AEE05C0035594E /* SILRemoveWarningViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILRemoveWarningViewModel.swift; sourceTree = ""; }; 1EFC76DD26AEE07D0035594E /* SILErrorDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILErrorDetailsViewModel.swift; sourceTree = ""; }; - 1EFC76E426AEE0DF0035594E /* SILBeaconRegistryEntryViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconRegistryEntryViewModel.m; sourceTree = ""; }; 1EFC76EB26AEE1000035594E /* SILOTAProgressViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILOTAProgressViewModel.h; sourceTree = ""; }; 1EFC76EC26AEE1010035594E /* SILOTAProgressViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILOTAProgressViewModel.m; sourceTree = ""; }; 1EFC76F326AEE1270035594E /* DebugDeviceFilterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugDeviceFilterViewModel.swift; sourceTree = ""; }; @@ -1792,8 +1748,6 @@ 494F93C81E7719D40057C1E0 /* SILOTAFirmwareFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILOTAFirmwareFile.h; sourceTree = ""; }; 494F93C91E7719D40057C1E0 /* SILOTAFirmwareFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILOTAFirmwareFile.m; sourceTree = ""; }; 494F93CB1E7884150057C1E0 /* Entitlements.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.entitlements; sourceTree = ""; }; - 495BF34E1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SILEddystoneBeaconViewModel.h; path = SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.h; sourceTree = SOURCE_ROOT; }; - 495BF34F1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SILEddystoneBeaconViewModel.m; path = SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.m; sourceTree = SOURCE_ROOT; }; 495BF3571E5F9D9A002B3F8D /* SiliconLabsApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SiliconLabsApp-Bridging-Header.h"; sourceTree = ""; }; 496A42F11E7C582F006E87F2 /* SILBigRedButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBigRedButton.h; sourceTree = ""; }; 496A42F21E7C582F006E87F2 /* SILBigRedButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBigRedButton.m; sourceTree = ""; }; @@ -1812,7 +1766,6 @@ 49FB4BEE1E7A33C900223F3E /* SILOTAProgressViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILOTAProgressViewController.h; sourceTree = ""; }; 49FB4BEF1E7A33C900223F3E /* SILOTAProgressViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILOTAProgressViewController.m; sourceTree = ""; }; 49FB4BF01E7A33C900223F3E /* SILOTAProgressViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILOTAProgressViewController.xib; sourceTree = ""; }; - 4C126075241118A20086951A /* UIColor+SILColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+SILColors.swift"; sourceTree = ""; }; 4C2C63372409242D0080CE76 /* SILFavoritePeripheral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILFavoritePeripheral.swift; sourceTree = ""; }; 4C2CB430240E5A110079040D /* SILServiceMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILServiceMap.swift; sourceTree = ""; }; 4C2CB432240E5A340079040D /* SILCharacteristicMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILCharacteristicMap.swift; sourceTree = ""; }; @@ -1890,12 +1843,20 @@ 8008B8FC25E67DB400439F3A /* DebugDeviceFilterViewModelSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugDeviceFilterViewModelSpec.swift; sourceTree = ""; }; 8008B99725E6918300439F3A /* BlueGeckoTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueGeckoTests-Bridging-Header.h"; sourceTree = ""; }; 8008B99825E6918400439F3A /* SILTemperatureMeasurementSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILTemperatureMeasurementSpec.m; sourceTree = ""; }; + 8011D02727BFF01000F08754 /* SILGraphView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILGraphView.xib; sourceTree = ""; }; + 8011D02C27BFF09200F08754 /* SILGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILGraphView.swift; sourceTree = ""; }; + 8011D02F27CE587F00F08754 /* SILRSSIGraphViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRSSIGraphViewModel.swift; sourceTree = ""; }; + 8011D03127CE670700F08754 /* SILRSSIGraphCentralManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRSSIGraphCentralManager.swift; sourceTree = ""; }; 8012744D278C30E5006E12E5 /* Data+ManufacturerData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+ManufacturerData.swift"; sourceTree = ""; }; + 80280F0A27D8BC24008D6F60 /* SILRSSIMeasurementTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRSSIMeasurementTable.swift; sourceTree = ""; }; + 80280F0E27DA12DA008D6F60 /* SILDiscoveredPeripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILDiscoveredPeripheral.swift; sourceTree = ""; }; + 80280F1027DA25B2008D6F60 /* CLLocationManager+Reactive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CLLocationManager+Reactive.swift"; sourceTree = ""; }; 802A28A227566F7800E701A1 /* SILBlinkyPeripheralGATTDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBlinkyPeripheralGATTDatabase.swift; sourceTree = ""; }; 802A28A927566F9D00E701A1 /* SILThunderboardPeripheralGATTDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILThunderboardPeripheralGATTDatabase.swift; sourceTree = ""; }; 802A2996275778AF00E701A1 /* DemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = ""; }; 8041CCC7256E5A0900C5C368 /* SILDescriptorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILDescriptorTableViewCell.swift; sourceTree = ""; }; 8044307C270F008200DD3EA6 /* SILIOPTestReconnectManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILIOPTestReconnectManager.swift; sourceTree = ""; }; + 8044F5FB28118A9300AF5ABC /* RSSIGraphViewLineChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSIGraphViewLineChartView.swift; sourceTree = ""; }; 80521477255A7A510021385F /* SILSortViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortViewController.swift; sourceTree = ""; }; 8052147D255A80F40021385F /* SILSortTypeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortTypeViewCell.swift; sourceTree = ""; }; 80521483255A81070021385F /* SILSortModeViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILSortModeViewCell.swift; sourceTree = ""; }; @@ -1922,7 +1883,11 @@ 8096567A2524A6E4000E98F5 /* SILDebugCharacteristicEncodingFieldView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SILDebugCharacteristicEncodingFieldView.xib; sourceTree = ""; }; 809656822524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SILDebugCharacteristicEncodingFieldView.h; sourceTree = ""; }; 809656832524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SILDebugCharacteristicEncodingFieldView.m; sourceTree = ""; }; + 809BEA2927BEDB52006BC2D9 /* SILRSSIGraphViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILRSSIGraphViewController.swift; sourceTree = ""; }; + 809BEA2A27BEDB52006BC2D9 /* SILAppRSSIGraph.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SILAppRSSIGraph.storyboard; sourceTree = ""; }; 80AF191D2677A068002A58DB /* SILGattConfiguratorCheckBoxCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILGattConfiguratorCheckBoxCellView.swift; sourceTree = ""; }; + 80B3E2D527D1044400838066 /* CBCentralManager+Reactive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CBCentralManager+Reactive.swift"; sourceTree = ""; }; + 80B3E2D927D77AE800838066 /* BehaviorRelay+Collections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BehaviorRelay+Collections.swift"; sourceTree = ""; }; 80B5FE72275F7474008F08A8 /* SILAppTypeWifiCommissioning.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SILAppTypeWifiCommissioning.storyboard; sourceTree = ""; }; 80B5FE79275F8FDC008F08A8 /* String+Subscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Subscript.swift"; sourceTree = ""; }; 80B5FE80275F900B008F08A8 /* SILPeripheralDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILPeripheralDelegate.swift; sourceTree = ""; }; @@ -1946,6 +1911,11 @@ 80C67058255169950083D20C /* SILRadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRadioButton.swift; sourceTree = ""; }; 80C6706825516A450083D20C /* SILTimeLimitRadioButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILTimeLimitRadioButtonState.swift; sourceTree = ""; }; 80CCC0642564F8780018D649 /* SILBluetoothBrowserExpandableViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILBluetoothBrowserExpandableViewManager.swift; sourceTree = ""; }; + 80DBE36D282E4B0D00F7C579 /* SILIOPInfoPopup.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILIOPInfoPopup.xib; sourceTree = ""; }; + 80DBE36E282E4B0D00F7C579 /* SILIOPInfoPopup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SILIOPInfoPopup.swift; sourceTree = ""; }; + 80FB2ED027FDA77A00241470 /* SILPeripheralDataFilterHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILPeripheralDataFilterHelper.swift; sourceTree = ""; }; + 80FB2ED227FF29CD00241470 /* UIViewController+ShowExportFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+ShowExportFiles.swift"; sourceTree = ""; }; + 80FFFFAB27F1FBF7004F139F /* SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift; sourceTree = ""; }; 84342BE824D4F9248E13114A /* Pods_BlueGeckoWithHomeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BlueGeckoWithHomeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8A73A6BB1453DAE49320F05E /* Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsAppWithoutHomeKit/Pods-SiliconLabsAppWithoutHomeKit.release.xcconfig"; sourceTree = ""; }; 8E2432EAF11ADE0582587F26 /* Pods-SiliconLabsApp Dev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsApp Dev.release.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsApp Dev/Pods-SiliconLabsApp Dev.release.xcconfig"; sourceTree = ""; }; @@ -1962,19 +1932,11 @@ AA0724F51F388D2D001446CA /* SiliconLabsAppTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SiliconLabsAppTests-Bridging-Header.h"; sourceTree = ""; }; AA0724F61F388D2D001446CA /* DebugDeviceFilterViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugDeviceFilterViewModelTests.swift; sourceTree = ""; }; AA0FF4071EFC11AD007C14D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = SILRetailBeaconDetailsViewController.xib; sourceTree = ""; }; - AA0FF4091EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRetailBeaconDetailsViewController.h; sourceTree = ""; }; - AA0FF40A1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRetailBeaconDetailsViewController.m; sourceTree = ""; }; - AA0FF40C1EFC62C7007C14D3 /* SILBeaconDataModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeaconDataModel.h; sourceTree = ""; }; - AA0FF40D1EFC62C7007C14D3 /* SILBeaconDataModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconDataModel.m; sourceTree = ""; }; - AA0FF40F1EFC65F5007C14D3 /* SILBeaconDataViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeaconDataViewModel.h; sourceTree = ""; }; - AA0FF4101EFC65F5007C14D3 /* SILBeaconDataViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconDataViewModel.m; sourceTree = ""; }; AA162DCD1F0541B600E3DB22 /* TLMData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TLMData.swift; sourceTree = ""; }; AA4D43571F27EA65001EE0D2 /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldTableViewCell.swift; path = ../Views/TextFieldTableViewCell.swift; sourceTree = ""; }; AA4D43591F291C17001EE0D2 /* RSSISliderTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RSSISliderTableViewCell.swift; path = ../Views/RSSISliderTableViewCell.swift; sourceTree = ""; }; AA4D435B1F2A35A3001EE0D2 /* GradientView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = ../Views/GradientView.swift; sourceTree = ""; }; AA4D435D1F2A3F15001EE0D2 /* GradientSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientSlider.swift; path = ../Views/GradientSlider.swift; sourceTree = ""; }; - AA5F62E91F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRetailBeaconDetailsHeaderView.h; sourceTree = ""; }; - AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRetailBeaconDetailsHeaderView.m; sourceTree = ""; }; AA5F62EC1F0A859F007EDAAC /* SILRetailBeaconDetailsHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = SILRetailBeaconDetailsHeaderView.xib; path = RetailBeacon/SILRetailBeaconDetailsHeaderView.xib; sourceTree = ""; }; AA78173E1F33A86100B37B09 /* UIFont+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIFont+Extensions.swift"; sourceTree = ""; }; AA7817421F34C5C500B37B09 /* Int+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; @@ -1999,7 +1961,6 @@ DCB4310F1E8315CE00EE94F0 /* SILOTAHUDView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILOTAHUDView.h; sourceTree = ""; }; DCB431101E8315CE00EE94F0 /* SILOTAHUDView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILOTAHUDView.m; sourceTree = ""; }; DCB431121E8315EF00EE94F0 /* SILOTAHUDView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SILOTAHUDView.xib; sourceTree = ""; }; - DCE784DD1E7AF0700038C87A /* SILBeaconRegistryEntryViewModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SILBeaconRegistryEntryViewModel.h; path = SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.h; sourceTree = SOURCE_ROOT; }; DD05F35A97FE411FF4B9277B /* Pods-BlueGecko.debug-wireless-gecko.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueGecko.debug-wireless-gecko.xcconfig"; path = "Pods/Target Support Files/Pods-BlueGecko/Pods-BlueGecko.debug-wireless-gecko.xcconfig"; sourceTree = ""; }; E406AEFE9A8583311524FBD8 /* Pods-SiliconLabsApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SiliconLabsApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-SiliconLabsApp/Pods-SiliconLabsApp.release.xcconfig"; sourceTree = ""; }; E607A34C1A8D1F3000DAAFD3 /* SILCalibrationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILCalibrationViewController.h; sourceTree = ""; }; @@ -2010,10 +1971,6 @@ E621B33E1A65573500223C5A /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; E621B34A1A655B3F00223C5A /* SILApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILApp.h; sourceTree = ""; }; E621B34B1A655B3F00223C5A /* SILApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILApp.m; sourceTree = ""; }; - E64266181A84F1D6006C6B2F /* SILRSSIMeasurement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRSSIMeasurement.h; sourceTree = ""; }; - E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRSSIMeasurement.m; sourceTree = ""; }; - E642661B1A8527C0006C6B2F /* SILRSSIMeasurementTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRSSIMeasurementTable.h; sourceTree = ""; }; - E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRSSIMeasurementTable.m; sourceTree = ""; }; E642661E1A855DA8006C6B2F /* SILWeakTargetWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILWeakTargetWrapper.h; sourceTree = ""; }; E642661F1A855DA8006C6B2F /* SILWeakTargetWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILWeakTargetWrapper.m; sourceTree = ""; }; E64266211A8565BA006C6B2F /* SILProximityCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILProximityCalculator.h; sourceTree = ""; }; @@ -2040,8 +1997,6 @@ E6B3EC9A1AADD9BD005A687F /* SILRoundedViewBehaviour.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRoundedViewBehaviour.h; sourceTree = ""; }; E6B3EC9B1AADD9BD005A687F /* SILRoundedViewBehaviour.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRoundedViewBehaviour.m; sourceTree = ""; }; E6B3ECA11AAE2C8B005A687F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = SILCalibrationViewController.xib; sourceTree = ""; }; - E6B773DB1A670F8100B93058 /* SILDiscoveredPeripheral.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILDiscoveredPeripheral.h; sourceTree = ""; }; - E6B773DC1A670F8100B93058 /* SILDiscoveredPeripheral.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILDiscoveredPeripheral.m; sourceTree = ""; }; E6B773EB1A68474800B93058 /* SILHeartRateMeasurement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILHeartRateMeasurement.h; sourceTree = ""; }; E6B773EC1A68474800B93058 /* SILHeartRateMeasurement.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILHeartRateMeasurement.m; sourceTree = ""; }; E6B773EE1A684B9700B93058 /* SILBodySensorLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBodySensorLocation.h; sourceTree = ""; }; @@ -2063,12 +2018,6 @@ E6CAA23E1A64011900A49DAF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E6CCCFE81A73040C0004B2F4 /* SILBeacon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeacon.h; sourceTree = ""; }; E6CCCFE91A73040C0004B2F4 /* SILBeacon.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeacon.m; sourceTree = ""; }; - E6CCCFEA1A73040C0004B2F4 /* SILBeaconRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeaconRegistry.h; sourceTree = ""; }; - E6CCCFEB1A73040C0004B2F4 /* SILBeaconRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconRegistry.m; sourceTree = ""; }; - E6CCCFEC1A73040C0004B2F4 /* SILBeaconRegistryEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILBeaconRegistryEntry.h; sourceTree = ""; }; - E6CCCFED1A73040C0004B2F4 /* SILBeaconRegistryEntry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILBeaconRegistryEntry.m; sourceTree = ""; }; - E6CCCFEE1A73040C0004B2F4 /* SILRetailBeaconAppViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SILRetailBeaconAppViewController.h; sourceTree = ""; }; - E6CCCFEF1A73040C0004B2F4 /* SILRetailBeaconAppViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SILRetailBeaconAppViewController.m; sourceTree = ""; }; E6CCCFF41A7304210004B2F4 /* NSError+SILHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+SILHelpers.h"; sourceTree = ""; }; E6CCCFF51A7304210004B2F4 /* NSError+SILHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+SILHelpers.m"; sourceTree = ""; }; E6CCCFF61A7304210004B2F4 /* UIImage+SILImages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+SILImages.h"; sourceTree = ""; }; @@ -2454,24 +2403,12 @@ 49BB94B11E68F2DC0068670E /* SILAdvertisementDataViewModel.m */, 49BB94B31E68FB4B0068670E /* SILDiscoveredPeripheralDisplayDataViewModel.h */, 49BB94B41E68FB4B0068670E /* SILDiscoveredPeripheralDisplayDataViewModel.m */, - 07B1AD761BFD043000D4D454 /* SILBeaconViewModel.h */, - 07B1AD771BFD043000D4D454 /* SILBeaconViewModel.m */, - 07B1AD791BFD05CA00D4D454 /* SILBGBeaconViewModel.h */, - 07B1AD7A1BFD05CA00D4D454 /* SILBGBeaconViewModel.m */, - 07B1AD7C1BFD0A2100D4D454 /* SILIBeaconViewModel.h */, - 07B1AD7D1BFD0A2100D4D454 /* SILIBeaconViewModel.m */, - 07B1AD821BFD0B4F00D4D454 /* SILAltBeaconViewModel.h */, - 07B1AD831BFD0B4F00D4D454 /* SILAltBeaconViewModel.m */, - 495BF34E1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.h */, - 495BF34F1E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m */, 494F93BF1E77170B0057C1E0 /* SILKeyValueViewModel.h */, 494F93C01E77170B0057C1E0 /* SILKeyValueViewModel.m */, 494F93C21E7717540057C1E0 /* SILOTAFirmwareUpdateViewModel.h */, 494F93C31E7717540057C1E0 /* SILOTAFirmwareUpdateViewModel.m */, 1EFC76EB26AEE1000035594E /* SILOTAProgressViewModel.h */, 1EFC76EC26AEE1010035594E /* SILOTAProgressViewModel.m */, - DCE784DD1E7AF0700038C87A /* SILBeaconRegistryEntryViewModel.h */, - 1EFC76E426AEE0DF0035594E /* SILBeaconRegistryEntryViewModel.m */, DC4E508F1E92E526004FD829 /* SILOTAHUDPeripheralViewModel.h */, DC4E508D1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m */, 0CC658131F96C3C200953CDC /* SILDeviceSelectionViewModel.h */, @@ -2954,7 +2891,6 @@ 1E26ECA525501A0A002FFAAB /* SILAdvertiserAdTypeCellViewModel.swift */, 1E26ECA625501A0A002FFAAB /* SILAdvertiserHomeViewModel.swift */, 1E26ECA725501A0A002FFAAB /* LocalNameSetting */, - 1E26ECA925501A0A002FFAAB /* RemoveWarning */, ); path = Home; sourceTree = ""; @@ -2967,14 +2903,6 @@ path = LocalNameSetting; sourceTree = ""; }; - 1E26ECA925501A0A002FFAAB /* RemoveWarning */ = { - isa = PBXGroup; - children = ( - 1E26ECAA25501A0A002FFAAB /* SILAdvertiserRemoveWarningViewModel.swift */, - ); - path = RemoveWarning; - sourceTree = ""; - }; 1E26ECAB25501A0A002FFAAB /* Details */ = { isa = PBXGroup; children = ( @@ -3291,6 +3219,8 @@ 1E5FEAA4261F37BD00D00E5F /* UI */ = { isa = PBXGroup; children = ( + 80DBE36E282E4B0D00F7C579 /* SILIOPInfoPopup.swift */, + 80DBE36D282E4B0D00F7C579 /* SILIOPInfoPopup.xib */, 1E4D8F4726035FE000924430 /* SILIOPTesterViewController.swift */, 1E4D8F4826035FE000924430 /* SILIOPTestScenarioCellView.swift */, 1E0E2E0226275C5300B7B7FD /* SILIOPTestStatusView.swift */, @@ -3672,7 +3602,6 @@ children = ( 1E26EC58255019CA002FFAAB /* SILAppAdvertiser.storyboard */, 1E26EC59255019CA002FFAAB /* SILAppAdvertiserDetails.storyboard */, - 1EC92EF623A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard */, 1EC92EF823A3959200FD2219 /* SILAppBluetoothBrowser.storyboard */, 4C95EB7623FEC8600091FB5A /* SILAppBluetoothBrowserDetails.storyboard */, 1E4DB34B266A714900405BD2 /* SILAppGATTConfigurator.storyboard */, @@ -3830,7 +3759,6 @@ 1EFC767C26AEDFA40035594E /* Home */, 1EFC768126AEDFA40035594E /* SILGattConfiguratorSetting.swift */, 1EFC768226AEDFA40035594E /* Details */, - 1EFC769826AEDFA40035594E /* SILGattConfiguratorFileWriter.swift */, ); name = GattConfigurator; path = SiliconLabsApp/ViewModels/GattConfigurator; @@ -4190,6 +4118,28 @@ path = DataModels; sourceTree = ""; }; + 8011D02627BFEFDC00F08754 /* GraphView */ = { + isa = PBXGroup; + children = ( + 8011D02727BFF01000F08754 /* SILGraphView.xib */, + 8011D02C27BFF09200F08754 /* SILGraphView.swift */, + 04B1F49327DF74DA0083CB3C /* RSSIGraphYAxisRenderer.swift */, + 04B1F49527DF76B40083CB3C /* RSSIGraphXAxisRenderer.swift */, + 8044F5FB28118A9300AF5ABC /* RSSIGraphViewLineChartView.swift */, + ); + path = GraphView; + sourceTree = ""; + }; + 8011D02E27CE585B00F08754 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 8011D02F27CE587F00F08754 /* SILRSSIGraphViewModel.swift */, + 8011D03127CE670700F08754 /* SILRSSIGraphCentralManager.swift */, + 80FB2ED027FDA77A00241470 /* SILPeripheralDataFilterHelper.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; 8037D08B25E63BE800F0998F /* Helpers */ = { isa = PBXGroup; children = ( @@ -4268,6 +4218,26 @@ path = EncodingField; sourceTree = ""; }; + 809BEA2727BEDB52006BC2D9 /* RSSIGraph */ = { + isa = PBXGroup; + children = ( + 809BEA2827BEDB52006BC2D9 /* UI */, + 8011D02E27CE585B00F08754 /* ViewModel */, + ); + path = RSSIGraph; + sourceTree = ""; + }; + 809BEA2827BEDB52006BC2D9 /* UI */ = { + isa = PBXGroup; + children = ( + 8011D02627BFEFDC00F08754 /* GraphView */, + 809BEA2927BEDB52006BC2D9 /* SILRSSIGraphViewController.swift */, + 809BEA2A27BEDB52006BC2D9 /* SILAppRSSIGraph.storyboard */, + 80FFFFAB27F1FBF7004F139F /* SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift */, + ); + path = UI; + sourceTree = ""; + }; 80AF189326779C89002A58DB /* Recovered References */ = { isa = PBXGroup; children = ( @@ -4374,6 +4344,7 @@ E621B33D1A65562400223C5A /* ViewControllers */ = { isa = PBXGroup; children = ( + 809BEA2727BEDB52006BC2D9 /* RSSIGraph */, 80B5FF93275F912F008F08A8 /* WifiCommissioning */, 1E973CF02672693400B5FC71 /* Blinky */, 1E4D8F4326035FE000924430 /* IOP Test App */, @@ -4381,7 +4352,6 @@ 1E4DB38C266A720600405BD2 /* GattConfigurator */, 1E14580A266E05CF009792C2 /* Throughput */, 1E2D9CA923BA48B100816EC0 /* BluetoothBrowser */, - E6D45D311A6E8A80002D9EC4 /* RetailBeaconApp */, 07D7622A1BBC25E400338D3B /* DebugApp */, 79C036111EC54A1400EF179B /* HomeKitDebugApp */, E607A34B1A8D1D5F00DAAFD3 /* Calibration */, @@ -4479,10 +4449,6 @@ 494F93C61E7717A90057C1E0 /* SILOTAFirmwareUpdate.m */, 494F93C81E7719D40057C1E0 /* SILOTAFirmwareFile.h */, 494F93C91E7719D40057C1E0 /* SILOTAFirmwareFile.m */, - AA0FF40C1EFC62C7007C14D3 /* SILBeaconDataModel.h */, - AA0FF40D1EFC62C7007C14D3 /* SILBeaconDataModel.m */, - AA0FF40F1EFC65F5007C14D3 /* SILBeaconDataViewModel.h */, - AA0FF4101EFC65F5007C14D3 /* SILBeaconDataViewModel.m */, 1E1907B323FD8B8600DD014C /* SILBeaconTypeRealmModel.h */, 1E1907B423FD8B9900DD014C /* SILBeaconTypeRealmModel.m */, 1E1907B223FD851C00DD014C /* SILSavedSearchesRealmModel.h */, @@ -4522,7 +4488,6 @@ E6CCCFF51A7304210004B2F4 /* NSError+SILHelpers.m */, E666CBD91A767DCB00676C5C /* UIColor+SILColors.h */, E666CBDA1A767DCB00676C5C /* UIColor+SILColors.m */, - 4C126075241118A20086951A /* UIColor+SILColors.swift */, E666CBDC1A76809A00676C5C /* UIImage+SILHelpers.h */, E666CBDD1A76809A00676C5C /* UIImage+SILHelpers.m */, E6CCCFF61A7304210004B2F4 /* UIImage+SILImages.h */, @@ -4559,6 +4524,8 @@ 2FD579D6257933DD001D7E9E /* NSObject+SILAssociatedObject.m */, 2FD579DC2579352C001D7E9E /* UIViewController+SILContext.swift */, 1E8529822591F6480064F419 /* UIViewController+Alert.swift */, + 80B3E2D927D77AE800838066 /* BehaviorRelay+Collections.swift */, + 80FB2ED227FF29CD00241470 /* UIViewController+ShowExportFiles.swift */, ); path = Categories; sourceTree = ""; @@ -4586,6 +4553,7 @@ DC380D4F1E8A9FAA00898C12 /* EXTKeyPathCoding.h */, DC380D501E8A9FFD00898C12 /* metamacros.h */, 1E1AE4AD247FCB7F00E5F238 /* SILRealmConfiguration.swift */, + 1EFC769826AEDFA40035594E /* SILFileWriter.swift */, ); path = Helpers; sourceTree = ""; @@ -4632,16 +4600,14 @@ 79C0361A1EC5576100EF179B /* SILHomeKitManager.m */, 79E62B4D1ECCA29400344CAA /* SILBluetoothSearch.h */, 79E62B4E1ECCA29400344CAA /* SILBluetoothSearch.m */, - E6B773DB1A670F8100B93058 /* SILDiscoveredPeripheral.h */, - E6B773DC1A670F8100B93058 /* SILDiscoveredPeripheral.m */, + 80280F0E27DA12DA008D6F60 /* SILDiscoveredPeripheral.swift */, 1E113D07244ECD6600442752 /* SILDiscoveredPeripheralIdentifierProvider.swift */, E6A37E451A82C1C400510E39 /* SILConstants.h */, E6A37E461A82C1C400510E39 /* SILConstants.m */, - E642661B1A8527C0006C6B2F /* SILRSSIMeasurementTable.h */, - E642661C1A8527C0006C6B2F /* SILRSSIMeasurementTable.m */, - E64266181A84F1D6006C6B2F /* SILRSSIMeasurement.h */, - E64266191A84F1D6006C6B2F /* SILRSSIMeasurement.m */, + 80280F0A27D8BC24008D6F60 /* SILRSSIMeasurementTable.swift */, 1E8529C025920C3B0064F419 /* SILBluetoothDisabledAlert.swift */, + 80B3E2D527D1044400838066 /* CBCentralManager+Reactive.swift */, + 80280F1027DA25B2008D6F60 /* CLLocationManager+Reactive.swift */, ); path = BluetoothControllers; sourceTree = ""; @@ -4747,25 +4713,6 @@ path = HealthThermometerApp; sourceTree = ""; }; - E6D45D311A6E8A80002D9EC4 /* RetailBeaconApp */ = { - isa = PBXGroup; - children = ( - E6CCCFEA1A73040C0004B2F4 /* SILBeaconRegistry.h */, - E6CCCFEB1A73040C0004B2F4 /* SILBeaconRegistry.m */, - E6CCCFEC1A73040C0004B2F4 /* SILBeaconRegistryEntry.h */, - E6CCCFED1A73040C0004B2F4 /* SILBeaconRegistryEntry.m */, - E6CCCFEE1A73040C0004B2F4 /* SILRetailBeaconAppViewController.h */, - E6CCCFEF1A73040C0004B2F4 /* SILRetailBeaconAppViewController.m */, - 07B1AD881BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.h */, - 07B1AD891BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m */, - AA5F62E91F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.h */, - AA5F62EA1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m */, - AA0FF4091EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.h */, - AA0FF40A1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m */, - ); - path = RetailBeaconApp; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -4984,9 +4931,11 @@ 0C2FCBA51F9A542300F4F259 /* SILDebugCharacteristicToggleFieldTableViewCell.xib in Resources */, 1E6CBCFA2588AE3500B11648 /* SILExitAdvertiserPopupViewController.xib in Resources */, 1E4C37A42429095300C822E4 /* Roboto-Bold.ttf in Resources */, + 809BEA2C27BEDB53006BC2D9 /* SILAppRSSIGraph.storyboard in Resources */, 4C9EAB6424042FB00023FFB1 /* Roboto-ThinItalic.ttf in Resources */, 80671C3E271EE2A4009B6284 /* EFR23_Chip_SiLabs_Logo.jpg in Resources */, 0C2FCBA61F9A542300F4F259 /* SILOTAHUDView.xib in Resources */, + 80DBE36F282E4B0D00F7C579 /* SILIOPInfoPopup.xib in Resources */, 80671C38271EE2A4009B6284 /* BRD4184A_LowPoly.obj in Resources */, 0C2FCBA71F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.xib in Resources */, 0F34409620AF0E250067397C /* SILAppTypeRangeTest.storyboard in Resources */, @@ -5009,7 +4958,6 @@ 1E4DB3AE266A720600405BD2 /* SILCreateGattCharacteristicViewController.xib in Resources */, 80671C3F271EE2A4009B6284 /* BRD4184A_LowPoly.dae in Resources */, 807B139B2523355A0056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.xib in Resources */, - 1EC92EF723A3957D00FD2219 /* SILAppTypeRetailBeacon.storyboard in Resources */, 0C2FCBAF1F9A542300F4F259 /* SILDebugSpacerTableViewCell.xib in Resources */, 20513235270365A800C27B92 /* ConnectedDeviceBarView.xib in Resources */, 1EC92EFB23A395B200FD2219 /* SILAppTypeHomeKitDebug.storyboard in Resources */, @@ -5061,6 +5009,7 @@ 1E26EC87255019E0002FFAAB /* SILLocalNameSettingViewController.xib in Resources */, 4C9EAB6124042FB00023FFB1 /* Roboto-MediumItalic.ttf in Resources */, 4C9EAB5D24042FB00023FFB1 /* Roboto-Italic.ttf in Resources */, + 8011D02827BFF01000F08754 /* SILGraphView.xib in Resources */, 0C2FCBC91F9A542300F4F259 /* SILDebugHeaderView.xib in Resources */, 0C2FCBCA1F9A542300F4F259 /* SILTextFieldEntryCell.xib in Resources */, 0C2FCBCB1F9A542300F4F259 /* LaunchScreen.xib in Resources */, @@ -5658,16 +5607,14 @@ 0C2FCB0D1F9A542300F4F259 /* SILSettings.m in Sources */, 1E26EC93255019E0002FFAAB /* SILCellView.swift in Sources */, 1EC1F22E260CEACB00508552 /* SILGATT4_9TestCase.swift in Sources */, - 1EFC76E526AEE0E00035594E /* SILBeaconRegistryEntryViewModel.m in Sources */, 0C2FCB0E1F9A542300F4F259 /* SILWeakTargetWrapper.m in Sources */, 1E26ECD225529C20002FFAAB /* SILScanResponseTitleCellView.swift in Sources */, + 80B3E2DA27D77AE800838066 /* BehaviorRelay+Collections.swift in Sources */, 1E4DB2EB266A701C00405BD2 /* UITextView+AddHyperLinksToText.swift in Sources */, 1E26ECBA25501A0A002FFAAB /* SILLocalNameSettingViewModel.swift in Sources */, 2051328B270365A800C27B92 /* DispatchBlockHelpers.swift in Sources */, - 0C2FCB101F9A542300F4F259 /* SILBeaconRegistry.m in Sources */, 20513253270365A800C27B92 /* BrightnessCell.swift in Sources */, 1EBC4E3D2452DFBF005B8767 /* CBAttribute+UUID.swift in Sources */, - 4C126076241118A20086951A /* UIColor+SILColors.swift in Sources */, 1E4DB3AF266A720600405BD2 /* SILCreateGattCharacteristicViewController.swift in Sources */, 1EFC76A326AEDFA40035594E /* SILGattConfiguratorCharacteristicButtonCellViewModel.swift in Sources */, 1EC1F19B260CE2D500508552 /* SILTestScenario.swift in Sources */, @@ -5677,7 +5624,6 @@ 80B5FF8C275F911E008F08A8 /* SILWifiCommissioningViewModel.swift in Sources */, 0C2FCB121F9A542300F4F259 /* SILDebugAdvDetailsViewController.m in Sources */, 0C39082B1FA233F900934AD1 /* SILDeviceSelectionViewModel.m in Sources */, - 0C2FCB151F9A542300F4F259 /* SILBeaconViewModel.m in Sources */, 0C2FCB171F9A542300F4F259 /* SILCharacteristicFieldValueResolver.m in Sources */, 809656842524A71C000E98F5 /* SILDebugCharacteristicEncodingFieldView.m in Sources */, 1E26EC9A255019E0002FFAAB /* SILAdvertisingDataValueCellView.swift in Sources */, @@ -5690,7 +5636,7 @@ 1E4DB3A8266A720600405BD2 /* SILGattConfiguratorCellView.swift in Sources */, 1EFC770426AEE1990035594E /* SILBrowserConnectionsViewModel.m in Sources */, 0C2FCB181F9A542300F4F259 /* SILBluetoothBitFieldModel.m in Sources */, - 1EFC76AE26AEDFA40035594E /* SILGattConfiguratorFileWriter.swift in Sources */, + 1EFC76AE26AEDFA40035594E /* SILFileWriter.swift in Sources */, 205131E0270365A800C27B92 /* ThunderBoardTypes.swift in Sources */, 0C2FCB191F9A542300F4F259 /* SILDiscoveredPeripheralDisplayDataViewModel.m in Sources */, 1E26EC33254B14EB002FFAAB /* UIView+SILShadow.m in Sources */, @@ -5701,6 +5647,7 @@ 1EFC76D626AEE05C0035594E /* SILExitWarningViewModel.swift in Sources */, 1E4DB3B2266A720600405BD2 /* SILGattConfiguratorCharacteristicButtonCellView.swift in Sources */, 1EC1F282260CECAF00508552 /* SILThroughputTestCase.swift in Sources */, + 80DBE370282E4B0D00F7C579 /* SILIOPInfoPopup.swift in Sources */, 0C2FCB1B1F9A542300F4F259 /* DebugDeviceFilterViewController.swift in Sources */, 1E51563D26B81B9300CCB2CA /* SILGattProjectMarker.swift in Sources */, 20513244270365A800C27B92 /* MotionDemoView.swift in Sources */, @@ -5739,7 +5686,6 @@ 1E90FFE426861F01008EFADA /* SILIOPFileWriter.swift in Sources */, 0C2FCB221F9A542300F4F259 /* RSSISliderTableViewCell.swift in Sources */, 1EFC766326AEDF770035594E /* SILGattPropertyMarker.swift in Sources */, - 0C2FCB241F9A542300F4F259 /* SILBGBeaconViewModel.m in Sources */, 20513219270365A800C27B92 /* RoundView.swift in Sources */, 80671D73272008F5009B6284 /* SILThunderboardDeviceSelectionViewController.swift in Sources */, 1E26EC97255019E0002FFAAB /* SILIntrinsicTableView.swift in Sources */, @@ -5751,10 +5697,12 @@ 20513208270365A800C27B92 /* SettingsNavigationController.swift in Sources */, 1EC1F220260CEAB400508552 /* SILGATT4_7TestCase.swift in Sources */, 4C2CB433240E5A340079040D /* SILCharacteristicMap.swift in Sources */, + 04B1F49427DF74DA0083CB3C /* RSSIGraphYAxisRenderer.swift in Sources */, 20513207270365A800C27B92 /* Spinner.swift in Sources */, 1EFC76ED26AEE1010035594E /* SILOTAProgressViewModel.m in Sources */, 2FD579D7257933DD001D7E9E /* NSObject+SILAssociatedObject.m in Sources */, 0C2FCB281F9A542300F4F259 /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */, + 8011D02D27BFF09200F08754 /* SILGraphView.swift in Sources */, 1EDCA5B823E1C05B00F78B14 /* SILBrowserConnectionsTableViewCell.m in Sources */, 0C2FCB291F9A542300F4F259 /* UIImage+SILImages.m in Sources */, 1EC1F292260CED3900508552 /* SILSecurity_7_3TestCase.swift in Sources */, @@ -5772,6 +5720,7 @@ 205131FC270365A800C27B92 /* IoDemoViewController.swift in Sources */, 1E4DB3BC266A720600405BD2 /* SILCheckBox.swift in Sources */, 1E4D8F5C26035FE000924430 /* SILIOPTestScenarioCellView.swift in Sources */, + 80B3E2D627D1044400838066 /* CBCentralManager+Reactive.swift in Sources */, 1E26ECC925529B93002FFAAB /* SILScanResponseViewModelBuilder.swift in Sources */, 20513270270365A800C27B92 /* BleDevice.swift in Sources */, 1E4DB3B9266A720600405BD2 /* SILGattConfiguratorDetailsViewController.swift in Sources */, @@ -5785,18 +5734,21 @@ 1E26EC88255019E0002FFAAB /* SILLocalNameSettingViewController.swift in Sources */, 1EDCA5AF23E1A6CE00F78B14 /* SILBrowserLogFilterViewController.m in Sources */, 1E4DB2FF266A706500405BD2 /* SILBluetoothServiceDescriptorProperties.swift in Sources */, + 80FB2ED127FDA77A00241470 /* SILPeripheralDataFilterHelper.swift in Sources */, 1E26ECCB25529BB1002FFAAB /* SILScanResponseTitleCellViewModel.swift in Sources */, - 0C2FCB301F9A542300F4F259 /* SILBeaconDataModel.m in Sources */, 1E4DB376266A71AE00405BD2 /* SILHomeTabBarController.swift in Sources */, 2051325F270365A800C27B92 /* Device.swift in Sources */, 1EFC76A426AEDFA40035594E /* SILGattConfiguratorServiceCellViewModel.swift in Sources */, 802A2997275778AF00E701A1 /* DemoViewController.swift in Sources */, 1EFC76C926AEE0420035594E /* SILSortViewModel.swift in Sources */, 0F3EB6B820C524C30062A7C1 /* SILRangeTestAppViewModel.swift in Sources */, + 8044F5FC28118A9300AF5ABC /* RSSIGraphViewLineChartView.swift in Sources */, 0C2FCB311F9A542300F4F259 /* SILDebugCharacteristicPropertyView.m in Sources */, 1EFC76CA26AEE0420035594E /* SILFilterBarViewModel.swift in Sources */, + 80280F0F27DA12DB008D6F60 /* SILDiscoveredPeripheral.swift in Sources */, 0C2FCB321F9A542300F4F259 /* CBPeripheral+Services.m in Sources */, 2051320D270365A800C27B92 /* MotionDemoViewController.swift in Sources */, + 809DE3CC27F4AC1E00BD71E0 /* SILRSSIGraphCentralManager.swift in Sources */, 20513266270365A800C27B92 /* MotionDemoInteraction.swift in Sources */, 0C2FCB331F9A542300F4F259 /* SILDebugProperty.m in Sources */, 2051321F270365A800C27B92 /* DeviceExtensions.swift in Sources */, @@ -5807,7 +5759,6 @@ 1EC1F1E2260CE96800508552 /* SILDiscoverTestConnectionParameters.swift in Sources */, 1EC1F243260CEAF900508552 /* SILGATT5_2TestCase.swift in Sources */, 1EEFB2072521CDDE00DD2DD7 /* SILRefreshImageView.m in Sources */, - 0C2FCB361F9A542300F4F259 /* SILRetailBeaconDetailsHeaderView.m in Sources */, 0C2FCB371F9A542300F4F259 /* SILOTAHUDPeripheralViewModel.m in Sources */, 0C2FCB381F9A542300F4F259 /* SILDebugAdvDetailTableViewCell.m in Sources */, 1EFC766226AEDF770035594E /* SILGattConfigurationCharacteristicEntity+SILGattXMLExportable.swift in Sources */, @@ -5841,11 +5792,10 @@ 1E145AC1266E5E0C009792C2 /* SILGattConfiguratorServiceInfoCellView.swift in Sources */, 1E4DB3B3266A720600405BD2 /* SILGattConfiguratorDescriptorShadowCellView.swift in Sources */, 1E1A43C324728F770052DE8D /* SILAdTypeEddystoneDecoder.swift in Sources */, - 0C2FCB401F9A542300F4F259 /* SILRetailBeaconDetailsViewController.m in Sources */, + 80FFFFAC27F1FBF7004F139F /* SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift in Sources */, 205131F3270365A800C27B92 /* SimulatedDevice.swift in Sources */, 0C2FCB411F9A542300F4F259 /* SILCollectionViewRightAlignedFlowLayout.m in Sources */, 0C2FCB421F9A542300F4F259 /* UIColor+SILColors.m in Sources */, - 0C2FCB431F9A542300F4F259 /* SILBeaconRegistryEntryCell.m in Sources */, 1E1457E2266E0522009792C2 /* Data+Helpers.swift in Sources */, 0C2FCB441F9A542300F4F259 /* SILAppSelectionInfoViewController.m in Sources */, 0C2FCB451F9A542300F4F259 /* SILTemperatureType.m in Sources */, @@ -5859,7 +5809,6 @@ 1EFC769D26AEDFA40035594E /* SILGattConfiguratorSetting.swift in Sources */, 1EFC765926AEDF770035594E /* SILGattConfiguratorSetRepository.swift in Sources */, 1EFC76FC26AEE1610035594E /* SILBrowserLogViewModel.m in Sources */, - 0C2FCB481F9A542300F4F259 /* SILEddystoneBeaconViewModel.m in Sources */, 0C2FCB491F9A542300F4F259 /* SILBluetoothDescriptorModel.m in Sources */, 0C2FCB4A1F9A542300F4F259 /* SILRoundedViewBehaviour.m in Sources */, 1EC1F206260CEA7500508552 /* SILGATT4_3TestCase.swift in Sources */, @@ -5875,7 +5824,6 @@ 0FD15EBB20BD3D0D00ED43BB /* SILRangeTestPeripheral.swift in Sources */, 1E26ECD425529C3F002FFAAB /* SILScanResponseValueCellView.swift in Sources */, 1E26ECBD25501A0A002FFAAB /* SILAdvertiserDetailsViewModel.swift in Sources */, - 0C2FCB4C1F9A542300F4F259 /* SILAltBeaconViewModel.m in Sources */, 1EFC765A26AEDF770035594E /* SILGattConfiguratorServiceHelper.swift in Sources */, 1E26EC9F255019E0002FFAAB /* SILRemoveServiceListWarningDialogViewController.swift in Sources */, 1E26ECB925501A0A002FFAAB /* SILAdvertiserHomeViewModel.swift in Sources */, @@ -5904,7 +5852,6 @@ 1E26EC37254B1528002FFAAB /* SILShadowView.m in Sources */, 4C97A51B23E88184000C6894 /* SILBrowserButton.swift in Sources */, 0F44BCAF20EE5E0E00CD27B3 /* SILRangeTestCharacteristic.swift in Sources */, - 1E26ECBB25501A0A002FFAAB /* SILAdvertiserRemoveWarningViewModel.swift in Sources */, 0C2FCB4F1F9A542300F4F259 /* SILTemperatureMeasurement.m in Sources */, 1EFC76D726AEE05C0035594E /* SILRemoveWarningViewModel.swift in Sources */, 1E26ECC225501A0A002FFAAB /* SILCellViewModel.swift in Sources */, @@ -5914,8 +5861,6 @@ 1EC1F1FF260CEA6900508552 /* SILGATT4_2TestCase.swift in Sources */, 1EFC76B626AEE0120035594E /* SILBlinkyViewModel.swift in Sources */, 1E26ECB825501A0A002FFAAB /* SILAdvertiserAdTypeCellViewModel.swift in Sources */, - 0C2FCB501F9A542300F4F259 /* SILRetailBeaconAppViewController.m in Sources */, - 0C2FCB511F9A542300F4F259 /* SILBeaconDataViewModel.m in Sources */, 1EFC766626AEDF770035594E /* SILGattDescriptorMarker.swift in Sources */, 0C2FCB521F9A542300F4F259 /* SILOTAFirmwareUpdate.m in Sources */, 20513268270365A800C27B92 /* RGBCell.swift in Sources */, @@ -5969,6 +5914,7 @@ 133D394B2554072B00BFB484 /* SILBaseWireframe+ContextMenu.swift in Sources */, 0C2FCB591F9A542300F4F259 /* SILBeacon.m in Sources */, 1EC1F1E9260CE9D000508552 /* SILDiscoverGATTTestCase.swift in Sources */, + 80FB2ED327FF29CD00241470 /* UIViewController+ShowExportFiles.swift in Sources */, 0C2FCB5C1F9A542300F4F259 /* SILOTASetupViewController.m in Sources */, 1E7CD33A260DDE740022FE80 /* SILIOPTesterCentralManager.swift in Sources */, 80AF191E2677A068002A58DB /* SILGattConfiguratorCheckBoxCellView.swift in Sources */, @@ -6011,6 +5957,7 @@ 2051327F270365A800C27B92 /* DeviceScanner.swift in Sources */, 1E113D08244ECD6600442752 /* SILDiscoveredPeripheralIdentifierProvider.swift in Sources */, 1E1457F4266E0555009792C2 /* SILThroughputConnectionParametersDecoder.swift in Sources */, + 80280F1127DA25B2008D6F60 /* CLLocationManager+Reactive.swift in Sources */, 1EC1F23C260CEAEE00508552 /* SILGATT5_1TestCase.swift in Sources */, 205131FE270365A800C27B92 /* BleEnvironmentalDemoConnection.swift in Sources */, 1E4DB3B5266A720600405BD2 /* SILGattConfiguratorServiceCellView.swift in Sources */, @@ -6021,7 +5968,6 @@ 133D39C32554514600BFB484 /* SILAdvertiserNotification.swift in Sources */, 1E3FC7D8252B5E40002F740D /* UITextField+DoneButton.swift in Sources */, 1E56E9EB2416974500D5B92A /* SILStoryboard+Constants.m in Sources */, - 0C2FCB661F9A542300F4F259 /* SILRSSIMeasurementTable.m in Sources */, 2051323D270365A800C27B92 /* NSDataExtensions.swift in Sources */, 0C2FCB671F9A542300F4F259 /* SILConstants.m in Sources */, 1EABF868251B7511006358C8 /* SILCharacteristicWriteViewController.swift in Sources */, @@ -6034,6 +5980,7 @@ 1EFC76AD26AEDFA40035594E /* SILGattAssignedNumberInfo.swift in Sources */, 0C2FCB6B1F9A542300F4F259 /* SILBitFieldFieldModel.m in Sources */, 20513204270365A800C27B92 /* LightsCell.swift in Sources */, + 80280F0B27D8BC24008D6F60 /* SILRSSIMeasurementTable.swift in Sources */, 2051324D270365A800C27B92 /* ConnectedDeviceBarView.swift in Sources */, 1E0E2E5126284D0600B7B7FD /* SILIOPTestReport.swift in Sources */, 20513261270365A800C27B92 /* IoDemoConnection.swift in Sources */, @@ -6073,7 +6020,6 @@ 0C2FCB7A1F9A542300F4F259 /* SILServiceTableModel.m in Sources */, 0C2FCB7B1F9A542300F4F259 /* SILBluetoothSearch.m in Sources */, 0C2FCB7D1F9A542300F4F259 /* SILBluetoothCharacteristicModel.m in Sources */, - 0C2FCB7E1F9A542300F4F259 /* SILDiscoveredPeripheral.m in Sources */, 1EC1F27B260CEC5D00508552 /* SILOTANonAckTestCase.swift in Sources */, 1EC1F25F260CEB2800508552 /* SILGATT5_6TestCase.swift in Sources */, 80B5FF9E275F912F008F08A8 /* SILWifiCommissioningViewController.swift in Sources */, @@ -6090,14 +6036,13 @@ 1E26EC96255019E0002FFAAB /* SILAdvertiserDetailsViewController.swift in Sources */, 0C2FCB841F9A542300F4F259 /* DebugDeviceViewModel.swift in Sources */, 1E4DB2EC266A701C00405BD2 /* Realm+CascadeDeleting.swift in Sources */, + 04B1F49627DF76B40083CB3C /* RSSIGraphXAxisRenderer.swift in Sources */, 0C2FCB851F9A542300F4F259 /* SILDebugPopoverViewController.m in Sources */, - 0C2FCB861F9A542300F4F259 /* SILRSSIMeasurement.m in Sources */, 1E26EC55255019A1002FFAAB /* SILAdvertisingSetRepository.swift in Sources */, 1EFC765F26AEDF770035594E /* SILGattConfiguratorXmlDatabase.swift in Sources */, 1E90F5EA2473EA650013AABD /* SILDebugServicesMenuViewControllerDelegate.swift in Sources */, 1E6CBD402588D23700B11648 /* SILSegmentedControl.swift in Sources */, 1EC1F28B260CED2400508552 /* SILSecurity_7_2TestCase.swift in Sources */, - 0C2FCB871F9A542300F4F259 /* SILIBeaconViewModel.m in Sources */, 1E4DB3B7266A720600405BD2 /* SILGattConfiguratorCharacteristicShadowCellView.swift in Sources */, 4C2CB435240E725C0079040D /* SILMap.swift in Sources */, 1E4DB3AA266A720600405BD2 /* SILGattConfiguratorHomeViewController.swift in Sources */, @@ -6129,9 +6074,9 @@ 0C2FCB8F1F9A542300F4F259 /* SILDescriptorTableModel.m in Sources */, 80671D8127217A54009B6284 /* SILDeviceSelectionViewController.swift in Sources */, 80671D8D27218401009B6284 /* SILAbstractDeviceSelectionViewController.swift in Sources */, - 0C2FCB901F9A542300F4F259 /* SILBeaconRegistryEntry.m in Sources */, 1E26EC8F255019E0002FFAAB /* SILPassthroughView.swift in Sources */, 0C2FCB911F9A542300F4F259 /* SILBarGraphCollectionViewCell.m in Sources */, + 8011D03027CE587F00F08754 /* SILRSSIGraphViewModel.swift in Sources */, 802A28A327566F7800E701A1 /* SILBlinkyPeripheralGATTDatabase.swift in Sources */, 20513280270365A800C27B92 /* Data+Extensions.swift in Sources */, 1EC1F2DE260CF46100508552 /* SILIOPTester_Test4.swift in Sources */, @@ -6149,6 +6094,7 @@ 0C2FCB931F9A542300F4F259 /* NSError+SILHelpers.m in Sources */, 1E26EC95255019E0002FFAAB /* SILContextMenuTransitioningDelegate.swift in Sources */, 1E14580E266E05CF009792C2 /* SILThroughputGaugeView.swift in Sources */, + 809BEA2B27BEDB53006BC2D9 /* SILRSSIGraphViewController.swift in Sources */, 1EEFB21125220C3700DD2DD7 /* SILCharacteristicWriteRadioButtonState.swift in Sources */, 1EB959BD23FAB3F600E47F01 /* SILBrowserFilterViewModel.m in Sources */, 0C2FCB941F9A542300F4F259 /* SILAdvertisementDataViewModel.m in Sources */, @@ -6178,11 +6124,9 @@ 9B44736325139C5900355E0A /* SILExitPopupViewController.m in Sources */, 0F4E510021525FDC00F58ACE /* SILSettings.m in Sources */, 0F4E510121525FDC00F58ACE /* SILWeakTargetWrapper.m in Sources */, - 0F4E510321525FDC00F58ACE /* SILBeaconRegistry.m in Sources */, 0F4E510421525FDC00F58ACE /* SILDebugServiceTableViewCell.m in Sources */, 0F4E510521525FDC00F58ACE /* SILDebugAdvDetailsViewController.m in Sources */, 0F4E510621525FDC00F58ACE /* SILDeviceSelectionViewModel.m in Sources */, - 0F4E510821525FDC00F58ACE /* SILBeaconViewModel.m in Sources */, 0F4E510A21525FDC00F58ACE /* SILCharacteristicFieldValueResolver.m in Sources */, 0F4E510B21525FDC00F58ACE /* SILBluetoothBitFieldModel.m in Sources */, 0F4E510C21525FDC00F58ACE /* SILDiscoveredPeripheralDisplayDataViewModel.m in Sources */, @@ -6195,7 +6139,6 @@ 0F4E511221525FDC00F58ACE /* SILDoubleKeyDictionaryPair.m in Sources */, 0F4E511321525FDC00F58ACE /* SILBluetoothEnumerationModel.m in Sources */, 0F4E511421525FDC00F58ACE /* RSSISliderTableViewCell.swift in Sources */, - 0F4E511521525FDC00F58ACE /* SILBGBeaconViewModel.m in Sources */, 0F4E511821525FDC00F58ACE /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */, 0F4E511921525FDC00F58ACE /* UIImage+SILImages.m in Sources */, 0F4E511B21525FDC00F58ACE /* TLMData.swift in Sources */, @@ -6203,7 +6146,6 @@ 0F4E511D21525FDC00F58ACE /* SILTextFieldEntryCell.m in Sources */, 0F4E511E21525FDC00F58ACE /* SILAdvertisementDataModel.m in Sources */, 0F4E511F21525FDC00F58ACE /* SILCharacteristicTableModel.m in Sources */, - 0F4E512021525FDC00F58ACE /* SILBeaconDataModel.m in Sources */, 0F4E512121525FDC00F58ACE /* SILRangeTestAppViewModel.swift in Sources */, 0F4E512221525FDC00F58ACE /* SILDebugCharacteristicPropertyView.m in Sources */, 0F4E512321525FDC00F58ACE /* CBPeripheral+Services.m in Sources */, @@ -6211,7 +6153,6 @@ 0F4E512521525FDC00F58ACE /* SILPopoverViewController.m in Sources */, 0F4E512621525FDC00F58ACE /* SILThermometerSegmentedControl.m in Sources */, 1E6CBD422588D23700B11648 /* SILSegmentedControl.swift in Sources */, - 0F4E512721525FDC00F58ACE /* SILRetailBeaconDetailsHeaderView.m in Sources */, 0F4E512821525FDC00F58ACE /* SILOTAHUDPeripheralViewModel.m in Sources */, 0F4E512921525FDC00F58ACE /* SILDebugAdvDetailTableViewCell.m in Sources */, 0F4E512A21525FDC00F58ACE /* EddystoneScanner.swift in Sources */, @@ -6221,31 +6162,25 @@ 0F4E512E21525FDC00F58ACE /* SILApp+AttributedProfiles.m in Sources */, 0F4E512F21525FDC00F58ACE /* SILOTAFirmwareFile.m in Sources */, 0F4E513021525FDC00F58ACE /* SILBitRowModel.m in Sources */, - 0F4E513121525FDC00F58ACE /* SILRetailBeaconDetailsViewController.m in Sources */, 0F4E513221525FDC00F58ACE /* SILCollectionViewRightAlignedFlowLayout.m in Sources */, 0F4E513321525FDC00F58ACE /* UIColor+SILColors.m in Sources */, 807B13AA252335A50056CFCC /* SILDebugCharacteristicEncodingFieldEntryCell.m in Sources */, - 0F4E513421525FDC00F58ACE /* SILBeaconRegistryEntryCell.m in Sources */, 0F4E513521525FDC00F58ACE /* SILAppSelectionInfoViewController.m in Sources */, 0F4E513621525FDC00F58ACE /* SILTemperatureType.m in Sources */, 0F4E513821525FDC00F58ACE /* SILCentralManager.m in Sources */, 80C6706B25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */, 0F4E513921525FDC00F58ACE /* CBService+Categories.m in Sources */, - 0F4E513A21525FDC00F58ACE /* SILEddystoneBeaconViewModel.m in Sources */, 0F4E513B21525FDC00F58ACE /* SILBluetoothDescriptorModel.m in Sources */, 0F4E513C21525FDC00F58ACE /* SILRoundedViewBehaviour.m in Sources */, 0F4E513D21525FDC00F58ACE /* SILRangeTestSettingValue.swift in Sources */, 0F4E513E21525FDC00F58ACE /* EddystoneBeacon.swift in Sources */, 0F4E513F21525FDC00F58ACE /* SILRangeTestModeSelectionViewController.swift in Sources */, 0F4E514021525FDC00F58ACE /* SILRangeTestPeripheral.swift in Sources */, - 0F4E514121525FDC00F58ACE /* SILAltBeaconViewModel.m in Sources */, 0F4E514221525FDC00F58ACE /* SILKeyValueViewModel.m in Sources */, 0F4E514321525FDC00F58ACE /* SILDebugCharacteristicEnumerationFieldTableViewCell.m in Sources */, 0F4E514421525FDC00F58ACE /* SILRangeTestCharacteristic.swift in Sources */, 13ACBA81256FA10D00D3EE11 /* SILRangeTestSelectDeviceViewController.swift in Sources */, 0F4E514521525FDC00F58ACE /* SILTemperatureMeasurement.m in Sources */, - 0F4E514621525FDC00F58ACE /* SILRetailBeaconAppViewController.m in Sources */, - 0F4E514721525FDC00F58ACE /* SILBeaconDataViewModel.m in Sources */, 13ACBA89256FB1D400D3EE11 /* SILRangeTestAppContainerViewController.swift in Sources */, 0F4E514821525FDC00F58ACE /* SILOTAFirmwareUpdate.m in Sources */, 0F4E514921525FDC00F58ACE /* SILBodySensorLocation.m in Sources */, @@ -6264,7 +6199,6 @@ 0F4E515821525FDC00F58ACE /* SILOTAFirmwareUpdateManager.m in Sources */, 0F4E515921525FDC00F58ACE /* SILOTAProgressViewController.m in Sources */, 0F4E515A21525FDC00F58ACE /* SILDebugCharacteristicToggleFieldTableViewCell.m in Sources */, - 0F4E515B21525FDC00F58ACE /* SILRSSIMeasurementTable.m in Sources */, 0F4E515C21525FDC00F58ACE /* SILConstants.m in Sources */, 0F4E515D21525FDC00F58ACE /* SILCalibrationViewController.m in Sources */, 0F4E515F21525FDC00F58ACE /* SILBitFieldFieldModel.m in Sources */, @@ -6284,15 +6218,12 @@ 0F4E516F21525FDC00F58ACE /* SILServiceTableModel.m in Sources */, 0F4E517021525FDC00F58ACE /* SILBluetoothSearch.m in Sources */, 0F4E517121525FDC00F58ACE /* SILBluetoothCharacteristicModel.m in Sources */, - 0F4E517221525FDC00F58ACE /* SILDiscoveredPeripheral.m in Sources */, 0F4E517321525FDC00F58ACE /* SILBluetoothServiceModel.m in Sources */, 0F4E517421525FDC00F58ACE /* SILWeakNotificationPair.m in Sources */, 0F4E517521525FDC00F58ACE /* GradientSlider.swift in Sources */, 0F4E517721525FDC00F58ACE /* main.m in Sources */, 0F4E517821525FDC00F58ACE /* DebugDeviceViewModel.swift in Sources */, 0F4E517921525FDC00F58ACE /* SILDebugPopoverViewController.m in Sources */, - 0F4E517A21525FDC00F58ACE /* SILRSSIMeasurement.m in Sources */, - 0F4E517B21525FDC00F58ACE /* SILIBeaconViewModel.m in Sources */, 1394F9602566BD4400795E5A /* SILBigButton.swift in Sources */, 0F4E517C21525FDC00F58ACE /* SILBluetoothBitModel.m in Sources */, 0F4E517E21525FDC00F58ACE /* SILRangeTestTXValueUpdater.swift in Sources */, @@ -6300,7 +6231,6 @@ 0F4E518021525FDC00F58ACE /* GradientView.swift in Sources */, 0F4E518121525FDC00F58ACE /* KZBehaviour.m in Sources */, 0F4E518221525FDC00F58ACE /* SILDescriptorTableModel.m in Sources */, - 0F4E518321525FDC00F58ACE /* SILBeaconRegistryEntry.m in Sources */, 0F4E518421525FDC00F58ACE /* SILBarGraphCollectionViewCell.m in Sources */, 0F4E518621525FDC00F58ACE /* SILRangeTestManufacturerData.swift in Sources */, 0F4E518721525FDC00F58ACE /* NSError+SILHelpers.m in Sources */, @@ -6365,12 +6295,10 @@ 07BBA74E1BD5858F00C2B07E /* SILBluetoothModelManager.m in Sources */, E607A3511A8D4FD100DAAFD3 /* SILSettings.m in Sources */, E64266201A855DA8006C6B2F /* SILWeakTargetWrapper.m in Sources */, - E6CCCFF11A73040C0004B2F4 /* SILBeaconRegistry.m in Sources */, 07B8A8D41BCD6E3B001948C1 /* SILDebugServiceTableViewCell.m in Sources */, 078138491BE99502001EFE7E /* SILDebugAdvDetailsViewController.m in Sources */, 80C6706A25516A450083D20C /* SILTimeLimitRadioButtonState.swift in Sources */, 79C0361E1EC5F10F00EF179B /* HMAccessory+SILHelpers.m in Sources */, - 07B1AD781BFD043000D4D454 /* SILBeaconViewModel.m in Sources */, 0702AB2C1BDFEE35009527B0 /* SILCharacteristicFieldValueResolver.m in Sources */, 07BBA75F1BD687CC00C2B07E /* SILBluetoothBitFieldModel.m in Sources */, 49BB94B51E68FB4B0068670E /* SILDiscoveredPeripheralDisplayDataViewModel.m in Sources */, @@ -6387,7 +6315,6 @@ AA4D435A1F291C17001EE0D2 /* RSSISliderTableViewCell.swift in Sources */, 4D9E26202212BFB200617DBA /* SILRangeTestBoardFeatures.swift in Sources */, 79C40C701EC9F6A800B729BF /* SILHomeKitDebugDeviceTableViewCell.m in Sources */, - 07B1AD7B1BFD05CA00D4D454 /* SILBGBeaconViewModel.m in Sources */, 0F69656920ECD32A0083C32A /* SILRangeTestManufacturerData.swift in Sources */, 7985D6621EC60C33000FA0FB /* HMCharacteristic+SILHelpers.m in Sources */, 0702AB331BE10E07009527B0 /* SILDebugCharacteristicValueFieldTableViewCell.m in Sources */, @@ -6398,14 +6325,12 @@ 078138591BE9B014001EFE7E /* SILTextFieldEntryCell.m in Sources */, 0755DD071BD086180003886D /* SILAdvertisementDataModel.m in Sources */, 07B8A8871BC47790001948C1 /* SILCharacteristicTableModel.m in Sources */, - AA0FF40E1EFC62C7007C14D3 /* SILBeaconDataModel.m in Sources */, 07B8A8D11BCD6E3B001948C1 /* SILDebugCharacteristicPropertyView.m in Sources */, 4924BA6B1E7057F800AE9E56 /* CBPeripheral+Services.m in Sources */, 07B8A89C1BC5F872001948C1 /* SILDebugProperty.m in Sources */, 49FB4BEC1E7A2EC200223F3E /* SILPopoverViewController.m in Sources */, 0F34409B20AF18BA0067397C /* SILRangeTestAppViewController.swift in Sources */, E666CBEB1A77C75400676C5C /* SILThermometerSegmentedControl.m in Sources */, - AA5F62EB1F0A84ED007EDAAC /* SILRetailBeaconDetailsHeaderView.m in Sources */, DC4E508E1E92E507004FD829 /* SILOTAHUDPeripheralViewModel.m in Sources */, 0C3908301FA8AC7A00934AD1 /* SILConnectedLightingViewController.m in Sources */, 0781384A1BE99502001EFE7E /* SILDebugAdvDetailTableViewCell.m in Sources */, @@ -6419,24 +6344,18 @@ E666CBE11A76B67900676C5C /* SILApp+AttributedProfiles.m in Sources */, 494F93CA1E7719D40057C1E0 /* SILOTAFirmwareFile.m in Sources */, 0752B77C1BDED93F0064CBF0 /* SILBitRowModel.m in Sources */, - AA0FF40B1EFC368A007C14D3 /* SILRetailBeaconDetailsViewController.m in Sources */, 1E6CBD412588D23700B11648 /* SILSegmentedControl.swift in Sources */, E6C8FF0F1A71A9A900DF062F /* SILCollectionViewRightAlignedFlowLayout.m in Sources */, E666CBDB1A767DCB00676C5C /* UIColor+SILColors.m in Sources */, - 07B1AD8B1BFD4C8F00D4D454 /* SILBeaconRegistryEntryCell.m in Sources */, E666CBE51A76C0E800676C5C /* SILAppSelectionInfoViewController.m in Sources */, E6CDEB3C1A698F4900AC7B33 /* SILTemperatureType.m in Sources */, 4924BA6E1E70585100AE9E56 /* CBService+Categories.m in Sources */, - 495BF3501E5F6A81002B3F8D /* SILEddystoneBeaconViewModel.m in Sources */, 07BBA7591BD5CDB400C2B07E /* SILBluetoothDescriptorModel.m in Sources */, E6B3EC9C1AADD9BD005A687F /* SILRoundedViewBehaviour.m in Sources */, 49E96F3F1E64618E00516DDC /* EddystoneBeacon.swift in Sources */, - 07B1AD841BFD0B4F00D4D454 /* SILAltBeaconViewModel.m in Sources */, 494F93C11E77170B0057C1E0 /* SILKeyValueViewModel.m in Sources */, 0702AB3D1BE1844E009527B0 /* SILDebugCharacteristicEnumerationFieldTableViewCell.m in Sources */, E6CDEB3F1A69911D00AC7B33 /* SILTemperatureMeasurement.m in Sources */, - E6CCCFF31A73040C0004B2F4 /* SILRetailBeaconAppViewController.m in Sources */, - AA0FF4111EFC65F5007C14D3 /* SILBeaconDataViewModel.m in Sources */, 494F93C71E7717A90057C1E0 /* SILOTAFirmwareUpdate.m in Sources */, E6B773F01A684B9700B93058 /* SILBodySensorLocation.m in Sources */, AA4D43581F27EA65001EE0D2 /* TextFieldTableViewCell.swift in Sources */, @@ -6457,7 +6376,6 @@ 49C10EEF1E71E0EC00C67256 /* SILOTAFirmwareUpdateManager.m in Sources */, 49FB4BF11E7A33C900223F3E /* SILOTAProgressViewController.m in Sources */, 0702AB381BE179EE009527B0 /* SILDebugCharacteristicToggleFieldTableViewCell.m in Sources */, - E642661D1A8527C0006C6B2F /* SILRSSIMeasurementTable.m in Sources */, E6A37E471A82C1C400510E39 /* SILConstants.m in Sources */, E607A34E1A8D1F3000DAAFD3 /* SILCalibrationViewController.m in Sources */, 7928D85E1ECA10FD0075FFCB /* SILHomeKitDebugServicesViewController.m in Sources */, @@ -6478,7 +6396,6 @@ 79C0361B1EC5576100EF179B /* SILHomeKitManager.m in Sources */, 0CC658151F96C3C200953CDC /* SILDeviceSelectionViewModel.m in Sources */, 07BBA7561BD5C89700C2B07E /* SILBluetoothCharacteristicModel.m in Sources */, - E6B773DD1A670F8100B93058 /* SILDiscoveredPeripheral.m in Sources */, 07BBA7531BD5BD3500C2B07E /* SILBluetoothServiceModel.m in Sources */, E6C667241B0A5BD90083C248 /* SILWeakNotificationPair.m in Sources */, AA4D435E1F2A3F15001EE0D2 /* GradientSlider.swift in Sources */, @@ -6486,8 +6403,6 @@ E6CAA2261A64011900A49DAF /* main.m in Sources */, AAA154A81F20F3D8002078B5 /* DebugDeviceViewModel.swift in Sources */, 078138371BE97370001EFE7E /* SILDebugPopoverViewController.m in Sources */, - E642661A1A84F1D6006C6B2F /* SILRSSIMeasurement.m in Sources */, - 07B1AD7E1BFD0A2100D4D454 /* SILIBeaconViewModel.m in Sources */, 7985D65F1EC6020F000FA0FB /* HMHomeManager+SILHelpers.m in Sources */, 1394F95F2566BD4400795E5A /* SILBigButton.swift in Sources */, 07BBA75C1BD6862A00C2B07E /* SILBluetoothBitModel.m in Sources */, @@ -6496,7 +6411,6 @@ 7928D8671ECA38AE0075FFCB /* SILHomeKitServiceTableModel.m in Sources */, E64C95BC1AB20DB60029E23A /* KZBehaviour.m in Sources */, 07B8A88A1BC47834001948C1 /* SILDescriptorTableModel.m in Sources */, - E6CCCFF21A73040C0004B2F4 /* SILBeaconRegistryEntry.m in Sources */, 9B44736225139C5900355E0A /* SILExitPopupViewController.m in Sources */, E6C8FF0C1A71869A00DF062F /* SILBarGraphCollectionViewCell.m in Sources */, E6CCCFFA1A7304210004B2F4 /* NSError+SILHelpers.m in Sources */, @@ -6572,7 +6486,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 54; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 52444FG85C; DISPLAY_NAME = "EFR Connect"; @@ -6582,7 +6496,7 @@ INFOPLIST_FILE = "$(SRCROOT)/SiliconLabsApp/SupportingFiles/BlueGecko/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.4.3; + MARKETING_VERSION = 2.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -6606,7 +6520,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 54; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 52444FG85C; DISPLAY_NAME = "EFR Connect"; @@ -6615,11 +6529,12 @@ INFOPLIST_FILE = "$(SRCROOT)/SiliconLabsApp/SupportingFiles/BlueGecko/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.4.3; + MARKETING_VERSION = 2.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.silabs.BlueGeckoDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "SiliconLabsApp-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/SiliconLabsApp/BluetoothControllers/CBCentralManager+Reactive.swift b/SiliconLabsApp/BluetoothControllers/CBCentralManager+Reactive.swift new file mode 100644 index 00000000..aef60b70 --- /dev/null +++ b/SiliconLabsApp/BluetoothControllers/CBCentralManager+Reactive.swift @@ -0,0 +1,85 @@ +// +// CBCentralManager+Reactive.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 03/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import RxSwift +import RxCocoa +import CoreBluetooth + +typealias DiscoverData = (peripheral: CBPeripheral, advertisementData: [String : Any], RSSI: NSNumber) + +extension CBCentralManager: HasDelegate { + public typealias Delegate = CBCentralManagerDelegate +} + +class CBCentralManagerDelegateProxy: DelegateProxy, DelegateProxyType, CBCentralManagerDelegate { + + init(parentObject: CBCentralManager) { + super.init(parentObject: parentObject, delegateProxy: CBCentralManagerDelegateProxy.self) + } + + deinit { + _didUpdateState.onCompleted() + } + + static func registerKnownImplementations() { + register { CBCentralManagerDelegateProxy(parentObject: $0) } + } + + static func currentDelegate(for object: CBCentralManager) -> CBCentralManagerDelegate? { + return object.delegate + } + + static func setCurrentDelegate(_ delegate: CBCentralManagerDelegate?, to object: CBCentralManager) { + object.delegate = delegate + } + + func centralManagerDidUpdateState(_ central: CBCentralManager) { + _didUpdateState.onNext(central.state) + } + + fileprivate let _didUpdateState = BehaviorSubject(value: .unknown) +} + +extension Reactive where Base: CBCentralManager { + + var delegate: CBCentralManagerDelegateProxy { + return CBCentralManagerDelegateProxy.proxy(for: base) + } + + var state: BehaviorSubject { + return delegate._didUpdateState + } + + var didUpdateState: Observable { + return delegate._didUpdateState.map { _ in } + } + + var didDiscover: Observable { + return delegate.methodInvoked(#selector(CBCentralManagerDelegate.centralManager(_:didDiscover:advertisementData:rssi:))) + .map { ($0[1] as! CBPeripheral, $0[2] as! [String: Any], $0[3] as! NSNumber)} + .share() + } + + var didConnect: Observable { + return delegate.methodInvoked(#selector(CBCentralManagerDelegate.centralManager(_:didConnect:))) + .map { $0[1] as! CBPeripheral } + .share() + } + + var didFailToConnect: Observable<(peripheral: CBPeripheral, error: Error?)> { + return delegate.methodInvoked(#selector(CBCentralManagerDelegate.centralManager(_:didFailToConnect:error:))) + .map { ($0[1] as! CBPeripheral, $0[2] as? Error )} + .share() + } + + var didDisconnect: Observable<(peripheral: CBPeripheral, error: Error?)> { + return delegate.methodInvoked(#selector(CBCentralManagerDelegate.centralManager(_:didDisconnectPeripheral:error:))) + .map { ($0[1] as! CBPeripheral, $0[2] as? Error )} + .share() + } +} diff --git a/SiliconLabsApp/BluetoothControllers/CLLocationManager+Reactive.swift b/SiliconLabsApp/BluetoothControllers/CLLocationManager+Reactive.swift new file mode 100644 index 00000000..dbb01a30 --- /dev/null +++ b/SiliconLabsApp/BluetoothControllers/CLLocationManager+Reactive.swift @@ -0,0 +1,78 @@ +// +// CLLocationManager+Reactive.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 10/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import RxSwift +import RxCocoa +import CoreLocation + +extension CLLocationManager: HasDelegate { + public typealias Delegate = CLLocationManagerDelegate +} + +class CLLocationManagerDelegateProxy: DelegateProxy, DelegateProxyType, CLLocationManagerDelegate { + + init(parentObject: CLLocationManager) { + super.init(parentObject: parentObject, delegateProxy: CLLocationManagerDelegateProxy.self) + } + + static func registerKnownImplementations() { + register { CLLocationManagerDelegateProxy(parentObject: $0) } + } + + static func currentDelegate(for object: CLLocationManager) -> CLLocationManagerDelegate? { + return object.delegate + } + + static func setCurrentDelegate(_ delegate: CLLocationManagerDelegate?, to object: CLLocationManager) { + object.delegate = delegate + } + + internal lazy var didUpdateLocationsSubject = PublishSubject<[CLLocation]>() + internal lazy var didFailWithErrorSubject = PublishSubject() + + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + _forwardToDelegate?.locationManager?(manager, didUpdateLocations: locations) + didUpdateLocationsSubject.onNext(locations) + } + + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + _forwardToDelegate?.locationManager?(manager, didFailWithError: error) + didFailWithErrorSubject.onNext(error) + } + + deinit { + self.didUpdateLocationsSubject.on(.completed) + self.didFailWithErrorSubject.on(.completed) + } +} + +extension Reactive where Base: CLLocationManager { + + var delegate: CLLocationManagerDelegateProxy { + return CLLocationManagerDelegateProxy.proxy(for: base) + } + + var didEnterRegion: Observable { + return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didEnterRegion:))) + .map { $0[1] as! CLRegion } + .share() + } + + var didExitRegion: Observable { + return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didExitRegion:))) + .map { $0[1] as! CLRegion } + .share() + } + + var didRangeBeaconsInRegion: Observable<(beacons: [CLBeacon], region: CLBeaconRegion)> { + return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didRangeBeacons:in:))) + .map { ($0[1] as! [CLBeacon], $0[2] as! CLBeaconRegion)} + .share() + } +} diff --git a/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift b/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift index 85e956d4..b55b99a2 100644 --- a/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift +++ b/SiliconLabsApp/BluetoothControllers/SILBluetoothDisabledAlert.swift @@ -22,6 +22,7 @@ enum SILBluetoothDisabledAlert: Int { case motion case environment case wifiCommissioning + case rssiGraph var title: String { "Bluetooth Disabled" @@ -55,6 +56,8 @@ enum SILBluetoothDisabledAlert: Int { return "\(backMsg) \(turnOnMsg) use Environment" case .wifiCommissioning: return "\(backMsg) \(turnOnMsg) use WiFi Commissioning" + case .rssiGraph: + return "\(backMsg) \(turnOnMsg) use RSSI Graph" } } } diff --git a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m index b2bdb177..8b1b54dc 100644 --- a/SiliconLabsApp/BluetoothControllers/SILCentralManager.m +++ b/SiliconLabsApp/BluetoothControllers/SILCentralManager.m @@ -10,10 +10,8 @@ #import #import "SILCentralManager.h" #import "SILBrowserConnectionsViewModel.h" -#import "SILDiscoveredPeripheral.h" #import "NSError+SILHelpers.h" #import "SILWeakTargetWrapper.h" -#import "SILRSSIMeasurementTable.h" #import "SILLogDataModel.h" #import "SILWeakNotificationPair.h" #import "SILConstants.h" @@ -186,7 +184,7 @@ - (void)didFireDiscoveryTimeoutTimer:(NSTimer *)timer { - (void)filterDiscoveredPeripheralByTimeout { BOOL didRemovePeripherals = NO; for (SILDiscoveredPeripheral *discoveredPeripheral in [self discoveredPeripherals]) { - if (![discoveredPeripheral.RSSIMeasurementTable hasRSSIMeasurementInPastTimeInterval:SILCentralManagerDiscoveryTimeoutThreshold]) { + if (![discoveredPeripheral.rssiMeasurementTable hasRSSIMeasurementInPastTimeInterval:SILCentralManagerDiscoveryTimeoutThreshold]) { didRemovePeripherals = YES; [self.discoveredPeripheralMapping removeObjectForKey:discoveredPeripheral.identityKey]; } diff --git a/SiliconLabsApp/BluetoothControllers/SILConstants.h b/SiliconLabsApp/BluetoothControllers/SILConstants.h index c71f8afe..5d5795f6 100644 --- a/SiliconLabsApp/BluetoothControllers/SILConstants.h +++ b/SiliconLabsApp/BluetoothControllers/SILConstants.h @@ -29,6 +29,9 @@ extern NSString * const SILCharacteristicNumberDMPLightState; extern NSString * const SILCharacteristicNumberDMPSwitchSource; extern NSString * const SILCharacteristicNumberDMPSourceAddress; +extern NSString * const SILDiscoveredPeripheralConnectableDevice; +extern NSString * const SILDiscoveredPeripheralNonConnectableDevice; + extern NSInteger const SILConstantsStrongSignalThreshold; extern NSInteger const SILConstantsMediumSignalThreshold; diff --git a/SiliconLabsApp/BluetoothControllers/SILConstants.m b/SiliconLabsApp/BluetoothControllers/SILConstants.m index add617bf..b5e8289b 100644 --- a/SiliconLabsApp/BluetoothControllers/SILConstants.m +++ b/SiliconLabsApp/BluetoothControllers/SILConstants.m @@ -29,6 +29,9 @@ NSString * const SILCharacteristicNumberDMPSwitchSource = @"2F16EE52-0BFD-4597-85D4-A5141FDBAE15"; NSString * const SILCharacteristicNumberDMPSourceAddress = @"82A1CB54-3921-4C9C-BA34-34F78BAB9A1B"; +NSString * const SILDiscoveredPeripheralConnectableDevice = @"Connectable"; +NSString * const SILDiscoveredPeripheralNonConnectableDevice = @"Non-connectable"; + NSInteger const SILConstantsStrongSignalThreshold = -50; NSInteger const SILConstantsMediumSignalThreshold = -80; diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h deleted file mode 100644 index 4fd7bbd9..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// SILDiscoveredPeripheral.h -// SiliconLabsApp -// -// Created by Colden Prime on 1/14/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import -#import -@class SILRSSIMeasurementTable; -@class SILDiscoveredPeripheral; -@class SILBeacon; - -@protocol SILDiscoveredPeripheralDelegate - -- (void)peripheral:(SILDiscoveredPeripheral*)peripheral didUpdateWithAdvertisementData:(NSDictionary*)dictionary andRSSI:(NSNumber*)rssi; - -@end - -@interface SILDiscoveredPeripheral : NSObject - -@property (class, nonatomic, assign, readonly) NSString* connectableDevice; -@property (class, nonatomic, assign, readonly) NSString* nonConnectableDevice; - -@property (strong, nonatomic) NSString* identityKey; -@property (strong, nonatomic) NSUUID* uuid; -@property (strong, nonatomic) CBPeripheral *peripheral; -@property (strong, nonatomic) SILRSSIMeasurementTable *RSSIMeasurementTable; -@property (strong, nonatomic) NSString *advertisedLocalName; -@property (strong, nonatomic) NSArray* advertisedServiceUUIDs; -@property (strong, nonatomic) NSNumber *txPowerLevel; -@property (strong, nonatomic) NSData *manufacturerData; -@property (strong, nonatomic) NSArray* solicitedServiceUUIDs; -@property (strong, nonatomic) NSDictionary* dataServiceData; -@property (strong, nonatomic) SILBeacon* beacon; -@property (nonatomic) BOOL isFavourite; -@property double advertisingInterval; - -@property (nonatomic, weak) id delegate; - -@property (nonatomic) BOOL isConnectable; -@property (nonatomic, readonly) BOOL isDMPConnectedLightConnect; -@property (nonatomic, readonly) BOOL isDMPConnectedLightProprietary; -@property (nonatomic, readonly) BOOL isDMPConnectedLightThread; -@property (nonatomic, readonly) BOOL isDMPConnectedLightZigbee; -@property (nonatomic, readonly) BOOL isRangeTest; - -- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI - andDiscoveringTimestamp:(double)timestamp; -- (void)updateWithAdvertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI - andDiscoveringTimestamp:(double)timestamp; -- (instancetype)initWithIBeacon:(CLBeacon*)iBeacon andDiscoveringTimestamp:(double)timestamp; -- (void)updateWithIBeacon:(CLBeacon*)iBeacon andDiscoveringTimestamp:(double)timestamp; -- (NSString *)rssiDescription; -- (void)resetLastTimestampValue; -- (NSNumber *)rssiValue; - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m deleted file mode 100644 index ac38edb2..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.m +++ /dev/null @@ -1,213 +0,0 @@ -// -// SILDiscoveredPeripheral.m -// SiliconLabsApp -// -// Created by Colden Prime on 1/14/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import "SILDiscoveredPeripheral.h" -#import "SILRSSIMeasurementTable.h" -#import "SILBluetoothBrowser+Constants.h" -#import "SILConstants.h" - -@interface SILDiscoveredPeripheral () - -@property (strong, nonatomic) NSMutableArray *RSSIMeasurements; -@property (nonatomic) double lastTimestamp; -@property (nonatomic) long long packetReceivedCount; - -@end - -@implementation SILDiscoveredPeripheral - -NSString* const RSSIAppendingString = @" RSSI"; -NSString* const ConnectableDevice = @"Connectable"; -NSString* const NonConnectableDevice = @"Non-connectable"; -NSString* const EddystoneService = @"FEAA"; -+ (NSString *)connectableDevice { return ConnectableDevice; } -+ (NSString *)nonConnectableDevice { return NonConnectableDevice; } - -- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral - advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI - andDiscoveringTimestamp:(double)timestamp { - self = [super init]; - if (self) { - self.peripheral = peripheral; - self.uuid = self.peripheral.identifier; - self.identityKey = [SILDiscoveredPeripheralIdentifierProvider provideKeyForCBPeripheral:self.peripheral]; - self.RSSIMeasurementTable = [[SILRSSIMeasurementTable alloc] init]; - self.advertisingInterval = 0; - self.packetReceivedCount = 0; - self.isConnectable = NO; - self.lastTimestamp = timestamp; - [self updateWithAdvertisementData:advertisementData RSSI:RSSI andDiscoveringTimestamp:timestamp]; - self.advertisedLocalName = DefaultDeviceName; - } - return self; -} - -- (void)updateWithAdvertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI - andDiscoveringTimestamp:(double)timestamp { - self.advertisedLocalName = advertisementData[CBAdvertisementDataLocalNameKey] ?: self.peripheral.name; - self.advertisedServiceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey]; - self.txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey]; - if (!self.isConnectable) { - self.isConnectable = [advertisementData[CBAdvertisementDataIsConnectable] boolValue]; - } - self.manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey]; - self.solicitedServiceUUIDs = advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey]; - self.dataServiceData = advertisementData[CBAdvertisementDataServiceDataKey]; - self.beacon = [self parseBeaconData:advertisementData]; - if ([self isCorrectAdvertisingPacket:timestamp]) { - self.packetReceivedCount++; - [self calculateAdvertisingIntervalWith:timestamp]; - self.lastTimestamp = timestamp; - } - [self.RSSIMeasurementTable addRSSIMeasurement:RSSI]; - [self.delegate peripheral:self didUpdateWithAdvertisementData:advertisementData andRSSI:RSSI]; -} - -- (BOOL)isCorrectAdvertisingPacket:(double)currentTimestamp { - double currentInterval = currentTimestamp - self.lastTimestamp; - if (currentInterval < 0.005) { - return NO; - } - return YES; -} - -- (void)calculateAdvertisingIntervalWith:(double)currentTimestamp { - const double Add3MsToInterval = 0.003; - const double MultiplierForBegginingCalculating = 0.0007; - const double MultiplierForAverageInterval = 0.0014; - const int ReliableForCalculatingPacketAmount = 29; - const int MinimumCount = 10; - - double currentInterval = currentTimestamp - self.lastTimestamp; - - if (currentInterval <= 0) { - return; - } - - if (self.advertisingInterval == 0) { - self.advertisingInterval = currentInterval; - } else if ((currentInterval < self.advertisingInterval * MultiplierForBegginingCalculating) && self.packetReceivedCount < MinimumCount) { - self.advertisingInterval = currentInterval; - } else if (currentInterval < self.advertisingInterval + Add3MsToInterval) { - long long limitedCount = MIN(self.packetReceivedCount, MinimumCount); - self.advertisingInterval = (((self.advertisingInterval * (limitedCount - 1) + currentInterval)) / limitedCount); - } else if (currentInterval < self.advertisingInterval * MultiplierForAverageInterval) { - self.advertisingInterval = (((self.advertisingInterval * ReliableForCalculatingPacketAmount + currentInterval)) / (ReliableForCalculatingPacketAmount + 1)); - } -} - -- (void)resetLastTimestampValue { - self.lastTimestamp = 0.0; -} - -- (SILBeacon*)parseBeaconData:(NSDictionary*)adverisement { - if (self.manufacturerData) { - NSError* error = nil; - SILBeacon* beacon = [SILBeacon beaconWithAdvertisment:adverisement name:self.advertisedLocalName error:&error]; - if (error == nil) { - return beacon; - } - } else if ([self hasEddystoneService]) { - SILBeacon* eddystoneBeacon = [SILBeacon beaconWithEddystone:self.dataServiceData[[CBUUID UUIDWithString:EddystoneService]]]; - return eddystoneBeacon; - } - - SILBeacon* unknownBeacon = [[SILBeacon alloc] init]; - unknownBeacon.name = @"Unspecified"; - unknownBeacon.type = SILBeaconTypeUnspecified; - - return unknownBeacon; -} - -- (instancetype)initWithIBeacon:(CLBeacon*)iBeacon andDiscoveringTimestamp:(double)timestamp { - self = [super init]; - if (self) { - self.peripheral = nil; - if (@available(iOS 13.0, *)) { - self.uuid = iBeacon.UUID; - } else { - self.uuid = iBeacon.proximityUUID; - } - self.identityKey = [SILDiscoveredPeripheralIdentifierProvider provideKeyForCLBeacon:iBeacon]; - self.beacon = [SILBeacon beaconWithIBeacon:iBeacon]; - self.beacon.name = SILBeaconIBeacon; - self.beacon.beacon = iBeacon; - self.RSSIMeasurementTable = [[SILRSSIMeasurementTable alloc] init]; - self.advertisingInterval = 0; - self.packetReceivedCount = 0; - self.isConnectable = NO; - self.lastTimestamp = timestamp; - [self updateWithIBeacon:iBeacon andDiscoveringTimestamp:timestamp]; - self.advertisedLocalName = DefaultDeviceName; - } - return self; -} - -- (void)updateWithIBeacon:(CLBeacon*)iBeacon andDiscoveringTimestamp:(double)timestamp { - self.advertisedServiceUUIDs = nil; - self.txPowerLevel = nil; - self.isConnectable = NO; - self.manufacturerData = nil; - if ([self isCorrectAdvertisingPacket:timestamp]) { - self.packetReceivedCount++; - [self calculateAdvertisingIntervalWith:timestamp]; - self.lastTimestamp = timestamp; - } - NSNumber* rssi = [NSNumber numberWithLong:iBeacon.rssi]; - [self.RSSIMeasurementTable addRSSIMeasurement:rssi]; - [self.delegate peripheral:self didUpdateWithAdvertisementData:nil andRSSI:rssi]; -} - -- (BOOL)isDMPConnectedLightConnect { - return [self isContainService:SILServiceNumberConnectedLightingConnect]; -} - -- (BOOL)isDMPConnectedLightProprietary { - return [self isContainService:SILServiceNumberConnectedLightingProprietary]; -} - -- (BOOL)isDMPConnectedLightThread { - return [self isContainService:SILServiceNumberConnectedLightingThread]; -} - -- (BOOL)isDMPConnectedLightZigbee { - return [self isContainService:SILServiceNumberConnectedLightingZigbee]; -} - -- (BOOL)isRangeTest { - return [self isContainService:SILServiceNumberRangeTest]; -} - -- (BOOL)hasEddystoneService { - return [self isContainService:EddystoneService]; -} - -- (BOOL)isContainService:(NSString *)serviceUUID { - CBUUID * const service = [CBUUID UUIDWithString:serviceUUID]; - return [self.advertisedServiceUUIDs containsObject:service]; -} - -- (NSString *)rssiDescription { - NSString *rssi = [self.RSSIMeasurementTable.lastRSSIMeasurement stringValue]; - - if (rssi != nil) { - NSMutableString* rssiDescription = [[NSMutableString alloc] initWithString:rssi]; - [rssiDescription appendString:RSSIAppendingString]; - return rssiDescription; - } else { - return @"N/A"; - } -} - -- (NSNumber *)rssiValue { - return self.RSSIMeasurementTable.lastRSSIMeasurement; -} - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.swift b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.swift new file mode 100644 index 00000000..3dca7d83 --- /dev/null +++ b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheral.swift @@ -0,0 +1,208 @@ +// +// SILDiscoveredPeripheral.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 09/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import CoreBluetooth + +protocol SILDiscoveredPeripheralDelegate: class { + func peripheral(_ peripheral: SILDiscoveredPeripheral, didUpdateWithAdvertisementData dictionary: [String: Any]?, andRSSI: NSNumber) +} + +@objcMembers +class SILDiscoveredPeripheral: NSObject { + + fileprivate let RSSIAppendingString = " RSSI" + fileprivate let EddystoneService = "FEAA" + + var identityKey: String + var uuid: UUID + var beacon: SILBeacon! + var peripheral: CBPeripheral? + var rssiMeasurementTable = SILRSSIMeasurementTable() + + var advertisedLocalName: String? + var advertisedServiceUUIDs: [CBUUID]? + var txPowerLevel: NSNumber? + var manufacturerData: NSData? + var solicitedServiceUUIDs: [CBUUID]? + var dataServiceData: [CBUUID : NSData]? + var advertisingInterval = 0.0 + weak var delegate: SILDiscoveredPeripheralDelegate? + + var isFavourite = false + var isConnectable = false + + private var lastTimestamp = 0.0 + private var packetReceivedCount: Int64 = 0 + + var isDMPConnectedLightConnect: Bool { + return isContainService(SILServiceNumberConnectedLightingConnect) + } + + var isDMPConnectedLightProprietary: Bool { + return isContainService(SILServiceNumberConnectedLightingProprietary) + } + + var isDMPConnectedLightThread: Bool { + return isContainService(SILServiceNumberConnectedLightingThread) + } + + var isDMPConnectedLightZigbee: Bool { + return isContainService(SILServiceNumberConnectedLightingZigbee) + } + + var isRangeTest: Bool { + return isContainService(SILServiceNumberRangeTest) + } + + var hasEddystoneService: Bool { + return isContainService(EddystoneService) + } + + @objc(initWithPeripheral:advertisementData:RSSI:andDiscoveringTimestamp:) + init(peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber, andDiscoveringTimestamp timestamp: Double + ) { + identityKey = SILDiscoveredPeripheralIdentifierProvider.provideKeyForCBPeripheral(peripheral) + uuid = peripheral.identifier + super.init() + self.peripheral = peripheral + commonInit(timestamp: timestamp) + update(withAdvertisementData: advertisementData, rssi: RSSI, andDiscoveringTimestamp: timestamp) + } + + init(iBeacon: CLBeacon, andDiscoveringTimestamp timestamp: Double) { + identityKey = SILDiscoveredPeripheralIdentifierProvider.provideKeyForCLBeacon(iBeacon) + if #available(iOS 13.0, *) { + uuid = iBeacon.uuid + } else { + uuid = iBeacon.proximityUUID + } + beacon = SILBeacon(iBeacon: iBeacon) + beacon.name = SILBeaconIBeacon + super.init() + peripheral = nil + commonInit(timestamp: timestamp) + update(withIBeacon: iBeacon, andDiscoveringTimestamp: timestamp) + } + + private func commonInit(timestamp: Double) { + rssiMeasurementTable = SILRSSIMeasurementTable() + advertisingInterval = 0 + packetReceivedCount = 0 + isConnectable = false + lastTimestamp = timestamp + advertisedLocalName = DefaultDeviceName + } + + @objc(updateWithAdvertisementData:RSSI:andDiscoveringTimestamp:) + func update(withAdvertisementData advertisementData: [String : Any], + rssi RSSI: NSNumber, + andDiscoveringTimestamp timestamp: Double + ) { + advertisedLocalName = advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? peripheral!.name + advertisedServiceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] + txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber + if !isConnectable, let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber { + self.isConnectable = isConnectable.boolValue + } + manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? NSData + solicitedServiceUUIDs = advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID] + dataServiceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: NSData] + beacon = parseBeaconData(advertisementData) + if isCorrectAdvertisingPacket(timestamp) { + packetReceivedCount += 1 + calculateAdvertisingInterval(with: timestamp) + lastTimestamp = timestamp + } + rssiMeasurementTable.addRSSIMeasurement(RSSI) + delegate?.peripheral(self, didUpdateWithAdvertisementData: advertisementData, andRSSI: RSSI) + } + + private func isCorrectAdvertisingPacket(_ currentTimestamp: Double) -> Bool { + let currentInterval = currentTimestamp - lastTimestamp + return currentInterval >= 0.005 + } + + private func calculateAdvertisingInterval(with currentTimestamp: Double) { + let add3MsToInterval: Double = 0.003 + let multiplierForBegginingCalculating: Double = 0.0007 + let multiplierForAverageInterval: Double = 0.0014 + let reliableForCalculatingPacketAmount: Int64 = 29 + let minimumCount: Int64 = 10 + + let currentInterval = currentTimestamp - lastTimestamp + + if currentInterval <= 0 { + return + } + + if advertisingInterval == 0 { + advertisingInterval = currentInterval + } else if (currentInterval < advertisingInterval * multiplierForBegginingCalculating) && packetReceivedCount < minimumCount { + advertisingInterval = currentInterval + } else if currentInterval < advertisingInterval + add3MsToInterval { + let limitedCount = min(packetReceivedCount, minimumCount) + advertisingInterval = (advertisingInterval * Double(limitedCount - 1) + currentInterval) / Double(limitedCount) + } else if currentInterval < advertisingInterval * multiplierForAverageInterval { + advertisingInterval = Double(advertisingInterval * Double(reliableForCalculatingPacketAmount) + currentInterval) / Double(reliableForCalculatingPacketAmount + 1) + } + } + + private func parseBeaconData(_ advertisement: [String : Any]?) -> SILBeacon { + if let _ = manufacturerData, let beacon = try? SILBeacon(advertisment: advertisement, name: advertisedLocalName) { + return beacon + } else if hasEddystoneService, let dataServiceData = dataServiceData, let data = dataServiceData[CBUUID(string: EddystoneService)] as Data? { + return SILBeacon(eddystone: data) + } + + let unknownBeacon = SILBeacon() + unknownBeacon.name = "Unspecified" + unknownBeacon.type = .unspecified + + return unknownBeacon + } + + func resetLastTimestampValue() { + lastTimestamp = 0.0 + } + + func update(withIBeacon iBeacon: CLBeacon, andDiscoveringTimestamp timestamp: Double) { + advertisedServiceUUIDs = nil + txPowerLevel = nil + isConnectable = false + manufacturerData = nil + if isCorrectAdvertisingPacket(timestamp) { + packetReceivedCount += 1 + calculateAdvertisingInterval(with: timestamp) + lastTimestamp = timestamp + } + let rssi = NSNumber(value: iBeacon.rssi) + rssiMeasurementTable.addRSSIMeasurement(rssi) + delegate?.peripheral(self, didUpdateWithAdvertisementData: nil, andRSSI: rssi) + } + + private func isContainService(_ serviceUUID: String) -> Bool { + let service = CBUUID(string: serviceUUID) + guard let advertisedServiceUUIDs = advertisedServiceUUIDs else { + return false + } + return advertisedServiceUUIDs.contains(service) + } + + func rssiDescription() -> String { + if let lastMeasurement = rssiMeasurementTable.lastRSSIMeasurement()?.stringValue { + return lastMeasurement + RSSIAppendingString + } else { + return "N/A" + } + } + + func rssiValue() -> NSNumber? { + return rssiMeasurementTable.lastRSSIMeasurement() + } +} diff --git a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheralIdentifierProvider.swift b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheralIdentifierProvider.swift index 3b6eb94c..60a30d90 100644 --- a/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheralIdentifierProvider.swift +++ b/SiliconLabsApp/BluetoothControllers/SILDiscoveredPeripheralIdentifierProvider.swift @@ -36,7 +36,7 @@ class SILDiscoveredPeripheralIdentifierProvider : NSObject { // for example E2C56DB5-DFFB-48D2-B060-D0F5A71096E0 -> 36 characters let UUIDLength = 36 let identityKey = mapping.identityKey - guard let uuid = identityKey?.prefix(UUIDLength) else { return NSUUID() } + let uuid = identityKey.prefix(UUIDLength) let uuidString = String(uuid) guard let nsuuid = NSUUID(uuidString: uuidString) else { return NSUUID() } return nsuuid diff --git a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.h b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.h deleted file mode 100644 index 4ccc4e82..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// SILRSSIMeasurement.h -// SiliconLabsApp -// -// Created by Colden Prime on 2/6/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import - -@interface SILRSSIMeasurement : NSObject - -@property (strong, nonatomic) NSNumber *RSSI; -@property (strong, nonatomic) NSDate *date; - -- (instancetype)initWithRSSI:(NSNumber *)RSSI date:(NSDate *)date; - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.m b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.m deleted file mode 100644 index eb8d8de8..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurement.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// SILRSSIMeasurement.m -// SiliconLabsApp -// -// Created by Colden Prime on 2/6/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import "SILRSSIMeasurement.h" - -@implementation SILRSSIMeasurement - -- (instancetype)initWithRSSI:(NSNumber *)RSSI date:(NSDate *)date { - self = [super init]; - if (self) { - self.RSSI = RSSI; - self.date = date; - } - return self; -} - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.h b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.h deleted file mode 100644 index fc72f23c..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// SILRSSIMeasurementTable.h -// SiliconLabsApp -// -// Created by Colden Prime on 2/6/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import - -@interface SILRSSIMeasurementTable : NSObject - -- (void)addRSSIMeasurement:(NSNumber *)RSSI; -- (NSNumber *)lastRSSIMeasurement; -- (BOOL)hasRSSIMeasurementInPastTimeInterval:(NSTimeInterval)timeInterval; -- (NSNumber *)averageRSSIMeasurementInPastTimeInterval:(NSTimeInterval)timeInterval; - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.m b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.m deleted file mode 100644 index ea5b292f..00000000 --- a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// SILRSSIMeasurementTable.m -// SiliconLabsApp -// -// Created by Colden Prime on 2/6/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import "SILRSSIMeasurementTable.h" -#import "SILRSSIMeasurement.h" - -@interface SILRSSIMeasurementTable () - -@property (strong, nonatomic) NSMutableArray *RSSIMeasurements; - -@end - -@implementation SILRSSIMeasurementTable - -- (instancetype)init { - self = [super init]; - if (self) { - self.RSSIMeasurements = [NSMutableArray array]; - } - return self; -} - -- (void)addRSSIMeasurement:(NSNumber *)RSSI { - NSInteger RSSIInteger = [RSSI integerValue]; - if (RSSIInteger <= 20 && RSSIInteger >= -100) { - [self.RSSIMeasurements addObject:[[SILRSSIMeasurement alloc] initWithRSSI:RSSI date:[NSDate date]]]; - } -} - -- (NSNumber *)lastRSSIMeasurement { - return [[self.RSSIMeasurements lastObject] RSSI]; -} - -- (BOOL)hasRSSIMeasurementInPastTimeInterval:(NSTimeInterval)timeInterval { - NSDate *referenceDate = [NSDate date]; - NSDate *lastRSSIMeasurementDate = [[self.RSSIMeasurements lastObject] date]; - return [referenceDate timeIntervalSinceDate:lastRSSIMeasurementDate] < timeInterval; -} - -- (NSNumber *)averageRSSIMeasurementInPastTimeInterval:(NSTimeInterval)timeInterval { - NSDate *date = [NSDate date]; - NSArray *filtered = [self.RSSIMeasurements filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SILRSSIMeasurement *measurement, NSDictionary *bindings) { - NSDate *measurementDate = measurement.date; - return [date timeIntervalSinceDate:measurementDate] < timeInterval; - }]]; - - if (filtered.count > 0) { - return [filtered valueForKeyPath:@"@avg.RSSI.shortValue"]; - } else { - return [NSNumber numberWithShort:0]; - } -} - -@end diff --git a/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.swift b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.swift new file mode 100644 index 00000000..1ad96a54 --- /dev/null +++ b/SiliconLabsApp/BluetoothControllers/SILRSSIMeasurementTable.swift @@ -0,0 +1,54 @@ +// +// SILRSSIMeasurementTable.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 09/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import RxCocoa +import RxSwift + +struct SILRSSIMeasurement { + let rssi: NSNumber + let date: Date +} + +@objcMembers +class SILRSSIMeasurementTable: NSObject { + + let rssiMeasurements = BehaviorRelay<[SILRSSIMeasurement]>(value: []) + + lazy var lastMeasurement: Observable = rssiMeasurements.asObservable() + .filter { !$0.isEmpty } + .map { $0.last! } + + func addRSSIMeasurement(_ RSSI: NSNumber) { + if -100...20 ~= RSSI.intValue { + rssiMeasurements.addElement(element: SILRSSIMeasurement(rssi: RSSI, date: Date())) + } + } + + func lastRSSIMeasurement() -> NSNumber? { + return rssiMeasurements.value.last?.rssi + } + + func hasRSSIMeasurement(inPastTimeInterval timeInterval: TimeInterval) -> Bool { + guard let lastRSSIMeasurementDate = rssiMeasurements.value.last?.date else { + return false + } + return Date().timeIntervalSince(lastRSSIMeasurementDate) < timeInterval + } + + func averageRSSIMeasurement(inPastTimeInterval timeInterval: TimeInterval) -> NSNumber { + let filtered = rssiMeasurements.value.filter { Date().timeIntervalSince($0.date) < timeInterval } + + if filtered.count > 0 { + let rssiSum = filtered.reduce(0) { $0 + $1.rssi.intValue } + return NSNumber(value: Double(rssiSum) / Double(filtered.count)) + } else { + return NSNumber(value: 0) + } + } +} diff --git a/SiliconLabsApp/Categories/BehaviorRelay+Collections.swift b/SiliconLabsApp/Categories/BehaviorRelay+Collections.swift new file mode 100644 index 00000000..98852248 --- /dev/null +++ b/SiliconLabsApp/Categories/BehaviorRelay+Collections.swift @@ -0,0 +1,28 @@ +// +// BehaviorRelay+Collections.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 08/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import RxCocoa + +extension BehaviorRelay { + func addElement(element: T) where Element == [T] { + accept(value + [element]) + } + + func removeElement(element: T) where Element == [T], T: Equatable { + accept(value.filter {$0 != element}) + } + + func addElement(key: T, value: U) where Element == [T: U] { + accept(self.value.merging([key: value]) { (_, new) in new }) + } + + func removeElement(key: T) where Element == [T: U], U: Any { + accept(self.value.filter {dictElement in dictElement.key != key}) + } +} diff --git a/SiliconLabsApp/Categories/UIColor+SILColors.swift b/SiliconLabsApp/Categories/UIColor+SILColors.swift deleted file mode 100644 index e2025a09..00000000 --- a/SiliconLabsApp/Categories/UIColor+SILColors.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// UIColor+SILColors.swift -// BlueGecko -// -// Created by Jan Wisniewski on 05/03/2020. -// Copyright © 2020 SiliconLabs. All rights reserved. -// -// -//import Foundation -// -// -//extension UIColor { -// -// class func tb_hex(_ hex: Int) -> UIColor { -// return UIColor( -// red: CGFloat((hex & 0xFF0000) >> 16) / 255.0, -// green: CGFloat((hex & 0x00FF00) >> 8) / 255.0, -// blue: CGFloat((hex & 0x0000FF) >> 0) / 255.0, -// alpha: 1.0) -// } -// -// class var vileRed: UIColor { -// get { return UIColor.tb_hex(0xD91E2A) } -// } -//} diff --git a/SiliconLabsApp/Categories/UIViewController+ShowExportFiles.swift b/SiliconLabsApp/Categories/UIViewController+ShowExportFiles.swift new file mode 100644 index 00000000..9edbb223 --- /dev/null +++ b/SiliconLabsApp/Categories/UIViewController+ShowExportFiles.swift @@ -0,0 +1,28 @@ +// +// UIViewController+ShowExportFiles.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 07/04/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import UIKit + +extension UIViewController { + func showSharingExportFiles(filesToShare: [URL], subject: String, sourceView: UIView, sourceRect: CGRect, completionWithItemsHandler: UIActivityViewController.CompletionWithItemsHandler?) { + let filesToShare = filesToShare + let gattConfiguratorSubject = subject + + let activityViewController = UIActivityViewController(activityItems: filesToShare, applicationActivities: nil) + UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.sil_regularBlue()], for: .normal) + UINavigationBar.appearance().tintColor = UIColor.sil_regularBlue() + activityViewController.setValue(gattConfiguratorSubject, forKey: "Subject") + activityViewController.completionWithItemsHandler = completionWithItemsHandler + + activityViewController.popoverPresentationController?.sourceView = sourceView + activityViewController.popoverPresentationController?.sourceRect = sourceRect + self.present(activityViewController, animated: true, completion: nil) + } +} + diff --git a/SiliconLabsApp/ViewModels/GattConfigurator/SILGattConfiguratorFileWriter.swift b/SiliconLabsApp/Helpers/SILFileWriter.swift similarity index 93% rename from SiliconLabsApp/ViewModels/GattConfigurator/SILGattConfiguratorFileWriter.swift rename to SiliconLabsApp/Helpers/SILFileWriter.swift index 6a1a13cf..1eb0cc8e 100644 --- a/SiliconLabsApp/ViewModels/GattConfigurator/SILGattConfiguratorFileWriter.swift +++ b/SiliconLabsApp/Helpers/SILFileWriter.swift @@ -8,10 +8,13 @@ import Foundation -class SILGattConfiguratorFileWriter : NSObject { +class SILFileWriter : NSObject { fileprivate var fileHandle: FileHandle? - override init() { + private let exportDirName: String + + init(exportDirName: String) { + self.exportDirName = exportDirName super.init() } @@ -33,13 +36,13 @@ class SILGattConfiguratorFileWriter : NSObject { } } - func getFilePath(withName name: String) -> String { - return (self.exportDirPath as NSString).appendingPathComponent(name).appending(".xml") + func getFilePath(withName name: String, fileExtension: String = "xml") -> String { + return (self.exportDirPath as NSString).appendingPathComponent(name).appending(".\(fileExtension)") } private var exportDirPath: String { let documentDirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] - let exportDir = (documentDirPath as NSString).appendingPathComponent("SILGattConfiguratorExport") + let exportDir = (documentDirPath as NSString).appendingPathComponent(exportDirName) return exportDir } diff --git a/SiliconLabsApp/Models/GattConfigurator/SILGattConfiguratorServiceHelper.swift b/SiliconLabsApp/Models/GattConfigurator/SILGattConfiguratorServiceHelper.swift index c6685f44..53f03274 100644 --- a/SiliconLabsApp/Models/GattConfigurator/SILGattConfiguratorServiceHelper.swift +++ b/SiliconLabsApp/Models/GattConfigurator/SILGattConfiguratorServiceHelper.swift @@ -279,4 +279,8 @@ extension Data { var hexString: String { return map { String(format: "%02hhx", $0) }.joined() } + + var reversedBytesHexString: String { + return reversed().map { String(format: "%02hhx", $0) }.joined() + } } diff --git a/SiliconLabsApp/Models/SILAdTypeCBPeripheralDecoder.swift b/SiliconLabsApp/Models/SILAdTypeCBPeripheralDecoder.swift index 3710cf60..a7c4776c 100644 --- a/SiliconLabsApp/Models/SILAdTypeCBPeripheralDecoder.swift +++ b/SiliconLabsApp/Models/SILAdTypeCBPeripheralDecoder.swift @@ -20,36 +20,28 @@ class SILAdTypeCBPeripheralDecoder : NSObject { func decode() -> [SILAdvertisementDataModel] { var advertisementDataModels = [SILAdvertisementDataModel]() - if peripheral.advertisedServiceUUIDs != nil { - advertisementDataModels += decodeAdvertisedSericeUUIDs() + if let advertisedServiceUUIDs = peripheral.advertisedServiceUUIDs { + advertisementDataModels += decodeAdvertisedSericeUUIDs(advertisedServiceUUIDs) } - if peripheral.solicitedServiceUUIDs != nil { - advertisementDataModels += decodeSolicitedServiceUUIDs() + if let solicitedServiceUUIDs = peripheral.solicitedServiceUUIDs { + advertisementDataModels += decodeSolicitedServiceUUIDs(solicitedServiceUUIDs) } - if peripheral.txPowerLevel != nil { - if let txPowerLevelModel = SILAdvertisementDataModel(value: decodeTXPowerLevel(), type: .txPowerLevel) { + if let txPowerLevel = peripheral.txPowerLevel, let txPowerLevelModel = SILAdvertisementDataModel(value: decodeTXPowerLevel(txPowerLevel), type: .txPowerLevel) { advertisementDataModels.append(txPowerLevelModel) - } } - if peripheral.advertisedLocalName != nil { - if let advertisementLocalNameModel = SILAdvertisementDataModel(value: decodeAdvertisementLocalName(), type: .completeLocalName) { + if let advertisedLocalName = peripheral.advertisedLocalName, let advertisementLocalNameModel = SILAdvertisementDataModel(value: decodeAdvertisementLocalName(advertisedLocalName), type: .completeLocalName) { advertisementDataModels.append(advertisementLocalNameModel) - } } - if peripheral.manufacturerData != nil { - if let manufacturerDataModel = SILAdvertisementDataModel(value: decodeManufacturerData(), type: .manufacturerData) { + if let manufacturerData = peripheral.manufacturerData, let manufacturerDataModel = SILAdvertisementDataModel(value: decodeManufacturerData(manufacturerData as Data), type: .manufacturerData) { advertisementDataModels.append(manufacturerDataModel) - } } - if peripheral.dataServiceData != nil { - if let dataServiceDataModel = SILAdvertisementDataModel(value: decodeDataServiceData(), type: .dataServiceData) { + if let dataServiceData = peripheral.dataServiceData, let dataServiceDataModel = SILAdvertisementDataModel(value: decodeDataServiceData(dataServiceData), type: .dataServiceData) { advertisementDataModels.append(dataServiceDataModel) - } } if peripheral.beacon.type == .altBeacon { @@ -67,9 +59,9 @@ class SILAdTypeCBPeripheralDecoder : NSObject { return advertisementDataModels } - private func decodeAdvertisedSericeUUIDs() -> [SILAdvertisementDataModel] { + private func decodeAdvertisedSericeUUIDs(_ advertisedServicesUUIDs: [CBUUID]) -> [SILAdvertisementDataModel] { var advertisedServicesDataModels = [SILAdvertisementDataModel]() - let splitServicesArray = splitServices(services: peripheral.advertisedServiceUUIDs) + let splitServicesArray = splitServices(services: advertisedServicesUUIDs) if !splitServicesArray[0].isEmpty { if let advertisedServiceUUIDsModel = SILAdvertisementDataModel(value: decodeAdvertisedServiceUUIDs16Bit(services16Bit: splitServicesArray[0]), type: .advertisedServiceUUIDs16Bit) { @@ -122,9 +114,9 @@ class SILAdTypeCBPeripheralDecoder : NSObject { return decode128BitServices(services128Bit) } - private func decodeSolicitedServiceUUIDs() -> [SILAdvertisementDataModel] { + private func decodeSolicitedServiceUUIDs(_ solicitedServiceUUIDs: [CBUUID]) -> [SILAdvertisementDataModel] { var solicitedServiceDataModels = [SILAdvertisementDataModel]() - let splitServicesArray = splitServices(services: peripheral.solicitedServiceUUIDs) + let splitServicesArray = splitServices(services: solicitedServiceUUIDs) if !splitServicesArray[0].isEmpty { if let solicitedServiceUUIDsModel = SILAdvertisementDataModel(value: decodeSolicitedServiceUUIDs16Bit(services16Bit: splitServicesArray[0]), type: .solicitedServiceUUIDs16Bit) { @@ -149,18 +141,18 @@ class SILAdTypeCBPeripheralDecoder : NSObject { return decode128BitServices(services128Bit) } - private func decodeTXPowerLevel() -> String { - return self.peripheral.txPowerLevel.stringValue + private func decodeTXPowerLevel(_ txPowerLevel: NSNumber) -> String { + return txPowerLevel.stringValue } - private func decodeAdvertisementLocalName() -> String { - return peripheral.advertisedLocalName + private func decodeAdvertisementLocalName(_ advertisedLocalName: String) -> String { + return advertisedLocalName } - private func decodeManufacturerData() -> String { + private func decodeManufacturerData(_ manufacturerData: Data) -> String { var manufacturerDataString = "" - let parsedBytes = hexEncodedString(data: self.peripheral.manufacturerData) + let parsedBytes = hexEncodedString(data: manufacturerData) if parsedBytes.count < 4 { manufacturerDataString += "PARSING ERROR: " @@ -190,10 +182,10 @@ class SILAdTypeCBPeripheralDecoder : NSObject { return manufacturerDataString } - private func decodeDataServiceData() -> String { + private func decodeDataServiceData(_ dataServiceDataDict: [CBUUID : NSData]) -> String { var dataServiceData = "" var isFirst = true - for (_, data) in self.peripheral.dataServiceData.enumerated() { + for (_, data) in dataServiceDataDict.enumerated() { if isFirst { isFirst = false } else { @@ -203,7 +195,7 @@ class SILAdTypeCBPeripheralDecoder : NSObject { dataServiceData += "UUID: 0x" dataServiceData += data.key.uuidString - let parsedBytes = hexEncodedString(data: data.value) + let parsedBytes = hexEncodedString(data: data.value as Data) if parsedBytes.count > 0 { dataServiceData += " Data: 0x" diff --git a/SiliconLabsApp/Models/SILApp.h b/SiliconLabsApp/Models/SILApp.h index 2d34a2e1..dfe2d2a9 100644 --- a/SiliconLabsApp/Models/SILApp.h +++ b/SiliconLabsApp/Models/SILApp.h @@ -22,7 +22,8 @@ typedef NS_ENUM(NSInteger, SILAppType) { SILAppTypeThroughput, SILAppTypeMotion, SILAppTypeEnvironment, - SILAppTypeWifiCommissioning + SILAppTypeWifiCommissioning, + SILAppTypeRSSIGraph }; @interface SILApp : NSObject diff --git a/SiliconLabsApp/Models/SILApp.m b/SiliconLabsApp/Models/SILApp.m index 5ec7c6a9..df60e9d2 100644 --- a/SiliconLabsApp/Models/SILApp.m +++ b/SiliconLabsApp/Models/SILApp.m @@ -30,6 +30,7 @@ + (NSArray *)developApps { [self advertiserApp], [self gattConfiguratorApp], [self iopTestApp], + [self rssiGraph] ]; } @@ -97,6 +98,14 @@ + (SILApp *)advertiserApp { imageName:SILImageNameHomeAdvertiser]; } ++ (SILApp *)rssiGraph { + return [[SILApp alloc] initWithAppType:SILAppTypeRSSIGraph + title:@"RSSI Graph" + description:@"Draw a plot with RSSI data from discovered devices." + showcasedProfiles:@{} + imageName:SILImageNameHomeRangeTestDemo]; +} + + (SILApp *)iopTestApp { return [[SILApp alloc] initWithAppType:SILAppIopTest title:@"Interoperability Test" diff --git a/SiliconLabsApp/Models/SILBeaconDataModel.h b/SiliconLabsApp/Models/SILBeaconDataModel.h deleted file mode 100644 index 958d48c7..00000000 --- a/SiliconLabsApp/Models/SILBeaconDataModel.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// SILBeaconDataModel.h -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import - -typedef NS_ENUM(NSInteger, BeaconModelType) { - BeaconModelTypeURL, - BeaconModelTypeUUID, - BeaconModelTypeInstance, - BeaconModelTypeVersion, - BeaconModelTypeVoltage, - BeaconModelTypeTemperature, - BeaconModelTypeAdvertisementCount, - BeaconModelTypeOnTime -}; - -@interface SILBeaconDataModel : NSObject - -@property (strong, nonatomic) NSString *value; -@property (nonatomic) BeaconModelType type; - -- (instancetype)initWithValue:(NSString *)value type:(BeaconModelType)type; - -@end diff --git a/SiliconLabsApp/Models/SILBeaconDataModel.m b/SiliconLabsApp/Models/SILBeaconDataModel.m deleted file mode 100644 index 5b1df2a7..00000000 --- a/SiliconLabsApp/Models/SILBeaconDataModel.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// SILBeaconDataModel.m -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILBeaconDataModel.h" - -@implementation SILBeaconDataModel - -- (instancetype)initWithValue:(NSString *)value type:(BeaconModelType)type { - self = [super init]; - if (self) { - self.value = value; - self.type = type; - } - return self; -} - -@end diff --git a/SiliconLabsApp/Models/SILBeaconDataViewModel.h b/SiliconLabsApp/Models/SILBeaconDataViewModel.h deleted file mode 100644 index fc109943..00000000 --- a/SiliconLabsApp/Models/SILBeaconDataViewModel.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// SILBeaconDataViewModel.h -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import - -@class SILBeaconDataModel; - -@interface SILBeaconDataViewModel : NSObject - -@property (strong, nonatomic, readonly) NSString *valueString; -@property (strong, nonatomic, readonly) NSString *typeString; - -- (instancetype)initWithBeaconDataModel:(SILBeaconDataModel *)dataModel; - -@end diff --git a/SiliconLabsApp/Models/SILBeaconDataViewModel.m b/SiliconLabsApp/Models/SILBeaconDataViewModel.m deleted file mode 100644 index 7a20d733..00000000 --- a/SiliconLabsApp/Models/SILBeaconDataViewModel.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// SILBeaconDataViewModel.m -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILBeaconDataViewModel.h" -#import "SILBeaconDataModel.h" - -//NSString * const kAdModelTypeUUID = @"UUID"; - -@interface SILBeaconDataViewModel () - -@property (strong, nonatomic) SILBeaconDataModel *beaconDataModel; -@property (strong, nonatomic, readwrite) NSString *valueString; -@property (strong, nonatomic, readwrite) NSString *typeString; - -@end - -@implementation SILBeaconDataViewModel - -#pragma mark - Initializers - -- (instancetype)initWithBeaconDataModel:(SILBeaconDataModel *)dataModel { - self = [super init]; - if (self) { - self.beaconDataModel = dataModel; - } - return self; -} - -#pragma mark - Properties - -- (NSString *)valueString { - if (_valueString == nil) { - _valueString = [self valueStringForType:self.beaconDataModel.type]; - } - return _valueString; -} - -- (NSString *)typeString { - if (_typeString == nil) { - _typeString = [self typeStringForType:self.beaconDataModel.type]; - } - return _typeString; -} - -#pragma mark - Helpers - -- (NSString *)valueStringForType:(BeaconModelType)type { - return self.beaconDataModel.value; -} - -- (NSString *)typeStringForType:(BeaconModelType)type { - NSString *typeString; - switch (type) { - case BeaconModelTypeURL: - typeString = @"URL"; - break; - case BeaconModelTypeUUID: - typeString = @"UUID"; - break; - case BeaconModelTypeInstance: - typeString = @"Instance"; - break; - case BeaconModelTypeVersion: - typeString = @"Version"; - break; - case BeaconModelTypeVoltage: - typeString = @"Voltage"; - break; - case BeaconModelTypeTemperature: - typeString = @"Temperature"; - break; - case BeaconModelTypeOnTime: - typeString = @"On time"; - break; - case BeaconModelTypeAdvertisementCount: - typeString = @"Advertisement count"; - break; - } - return typeString; -} - -@end diff --git a/SiliconLabsApp/Models/SILOTAFirmwareUpdateManager.m b/SiliconLabsApp/Models/SILOTAFirmwareUpdateManager.m index 7b39419d..1d3021e7 100644 --- a/SiliconLabsApp/Models/SILOTAFirmwareUpdateManager.m +++ b/SiliconLabsApp/Models/SILOTAFirmwareUpdateManager.m @@ -9,7 +9,6 @@ #import "SILOTAFirmwareUpdateManager.h" #import "CBPeripheral+Services.h" #import "CBService+Categories.h" -#import "SILDiscoveredPeripheral.h" #import "SILUUIDProvider.h" #import "SILCharacteristicTableModel.h" #import "NSError+SILHelpers.h" @@ -128,7 +127,7 @@ - (void)cycleDeviceWithInitiationByteSequence:(BOOL)initiatingByteSequence self.fileCompletion = completion; - if (initiatingByteSequence) { + if (initiatingByteSequence && ![self.peripheral hasOTADataCharacteristic]) { [self writeSingleByteValue:kSILInitiateDFUData toCharacteristic:[self.peripheral otaControlCharacteristic]]; } else { [self disconnectConnectedPeripheral]; diff --git a/SiliconLabsApp/Storyboards/DevelopApps/SILAppGATTConfigurator.storyboard b/SiliconLabsApp/Storyboards/DevelopApps/SILAppGATTConfigurator.storyboard index cd9cdc99..4b97445b 100644 --- a/SiliconLabsApp/Storyboards/DevelopApps/SILAppGATTConfigurator.storyboard +++ b/SiliconLabsApp/Storyboards/DevelopApps/SILAppGATTConfigurator.storyboard @@ -1,9 +1,9 @@ - + - + @@ -30,7 +30,7 @@ - + @@ -106,29 +106,29 @@ - + - + - + - + @@ -229,7 +229,7 @@ - + - + - + @@ -270,7 +270,7 @@ - + @@ -279,7 +279,7 @@ - + @@ -335,7 +335,7 @@ - + @@ -349,7 +349,7 @@ - + @@ -412,7 +412,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -536,10 +536,10 @@ - + - + @@ -551,7 +551,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift b/SiliconLabsApp/ViewControllers/IOP Test App/UI/SILIOPTesterViewController.swift index bc76ff04..d94c52f3 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, UIGestureRecognizerDelegate { +class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate, SILIOPPopupDelegate, WYPopoverControllerDelegate { @IBOutlet weak var allSpace: UIStackView! @IBOutlet weak var navigationBarView: UIView! @IBOutlet weak var navigationBarTitleLabel: UILabel! @@ -22,6 +22,9 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab @IBOutlet weak var totalTestCases: UILabel! @IBOutlet weak var shareButton: UIButton! @IBOutlet weak var runTestButton: UIButton! + @IBOutlet weak var infoImage: UIImageView! + + private var infoPopoverController: WYPopoverController? private var viewModel: SILIOPTesterViewModel! var deviceNameToSearch: String? @@ -49,8 +52,8 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab self.registerNotifications() } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) self.navigationController?.navigationBar.prefersLargeTitles = false } @@ -67,9 +70,42 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab runTestButton.setTitle("Run Tests", for: .normal) runTestButton.setTitle("Waiting...", for: .disabled) + self.setupInfoImage() self.setupNavigationBar() } + private func setupInfoImage() { + let image = UIImage(named: "help_white")?.withRenderingMode(.alwaysTemplate) + infoImage.image = image + + let tap = UITapGestureRecognizer(target: self, action: #selector(tappedInfoImage(_:))) + self.infoImage.addGestureRecognizer(tap) + } + + @objc private func tappedInfoImage(_ gestureRecognizer: UIGestureRecognizer) { + self.presentInfoPopup(animated: true) + } + + private func presentInfoPopup(animated: Bool) { + let infoPopup = SILIOPInfoPopup() + infoPopup.delegate = self + self.infoPopoverController = WYPopoverController.sil_presentCenterPopover(withContentViewController: infoPopup, presenting: self, delegate: self, animated: true) + } + + func didTappedCancelButton() { + dismissPopup() + } + + func popoverControllerDidDismissPopover(_ popoverController: WYPopoverController!) { + dismissPopup() + } + + private func dismissPopup() { + self.infoPopoverController?.dismissPopover(animated: true) { + self.infoPopoverController = nil + } + } + func setupNavigationBar() { navigationBarTitleLabel.adjustsFontSizeToFitWidth = true navigationBarView.addShadow() @@ -106,6 +142,7 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab guard let weakSelf = weakSelf else { return } weakSelf.currentTestState = status weakSelf.updateInfoView(newState: status) + weakSelf.updateInfoImage(newState: status) }) disposeBag.add(token: testStateStatusSubscription) @@ -118,6 +155,16 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab disposeBag.add(token: bluetoothStateSubscription) } + private func updateInfoImage(newState: SILIOPTesterViewModel.TestState) { + if newState == .running { + infoImage.isUserInteractionEnabled = false + infoImage.tintColor = UIColor.sil_lineGrey() + } else { + infoImage.isUserInteractionEnabled = true + infoImage.tintColor = .white + } + } + private func updateInfoView(newState: SILIOPTesterViewModel.TestState) { switch newState { case .initiated: @@ -198,11 +245,9 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab } func showDocumentPickerView() { - let documentPickerView = UIDocumentPickerViewController(documentTypes: ["public.gbl"], in: .import) - documentPickerView.delegate = self - UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.sil_regularBlue()], for: .normal) - UINavigationBar.appearance().tintColor = UIColor.sil_regularBlue() - self.present(documentPickerView, animated: false, completion: nil) + let documentPickerViewController = SILDocumentPickerViewController(documentTypes: ["public.gbl"], in: .import) + documentPickerViewController.setupDocumentPickerView() + self.present(documentPickerViewController, animated: false, completion: nil) } //MARK: UITableViewDelegate @@ -236,8 +281,12 @@ class SILIOPTesterViewController: UIViewController, UITableViewDataSource, UITab } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - showPopupAlert() - return false + let isRunning = self.currentTestState == .running + if isRunning { + showPopupAlert() + } + + return !isRunning } } diff --git a/SiliconLabsApp/ViewControllers/Popup/Warning/SILWarningViewController.swift b/SiliconLabsApp/ViewControllers/Popup/Warning/SILWarningViewController.swift index 21141b2d..ce8d710b 100644 --- a/SiliconLabsApp/ViewControllers/Popup/Warning/SILWarningViewController.swift +++ b/SiliconLabsApp/ViewControllers/Popup/Warning/SILWarningViewController.swift @@ -44,6 +44,6 @@ class SILWarningViewController: UIViewController { } @IBAction func cancelButtonWasTapped(_ sender: UIButton) { - viewModel.onCancel() + viewModel.onCancel(with: confirmSwitch.isOn) } } diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphViewLineChartView.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphViewLineChartView.swift new file mode 100644 index 00000000..dfb9d636 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphViewLineChartView.swift @@ -0,0 +1,204 @@ +// +// RSSIGraphViewLineChartView.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 21/04/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Charts + +class RSSIGraphLineChartView: LineChartView { + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupChart() + } + + deinit { + debugPrint("RSSIGraphLineChartView deinit") + } + + private func setupChart() { + disableLegend() + setupOffsets() + setupScaling() + setupYAxis() + setupXAxis() + setupLabels() + + addYAxisLines(withColor: RSSIConstants.axisRed) + addXAxisLines(withColor: RSSIConstants.axisRed) + + resetChart() + } + + private func setupScaling() { + scaleXEnabled = true + scaleYEnabled = true + doubleTapToZoomEnabled = false + highlightPerTapEnabled = false + highlightPerDragEnabled = true + } + + private func disableLegend() { + chartDescription?.enabled = false + legend.enabled = false + rightAxis.enabled = false + } + + private func setupOffsets() { + minOffset = 0 + extraTopOffset = 30 + extraLeftOffset = 0 + extraBottomOffset = 30 + extraRightOffset = 15 + } + + private func setupYAxis() { + leftAxis.valueFormatter = RSSIGraphYAxisValueFormatter() + leftAxis.axisMinimum = RSSIConstants.startYAxisMinimum + leftAxis.axisMaximum = RSSIConstants.startYAxisMaximum + leftAxis.granularity = RSSIConstants.yAxisGranularity + leftAxis.drawGridLinesEnabled = false + leftAxis.drawAxisLineEnabled = false + leftYAxisRenderer = RSSIGraphYAxisRenderer(viewPortHandler: leftYAxisRenderer.viewPortHandler, + yAxis: leftAxis, + transformer: leftYAxisRenderer.transformer) + } + + private func setupXAxis() { + xAxis.valueFormatter = RSSIGraphXAxisValueFormatter() + xAxis.axisMinimum = RSSIConstants.startXAxisMinimum + xAxis.axisMaximum = RSSIConstants.startXAxisMaximum + xAxis.granularity = RSSIConstants.xAxisGranularity + xAxis.drawGridLinesEnabled = false + xAxis.drawAxisLineEnabled = false + xAxisRenderer = RSSIGraphXAxisRenderer(viewPortHandler: xAxisRenderer.viewPortHandler, + xAxis: xAxis, + transformer: xAxisRenderer.transformer) + } + + private func setupLabels() { + xAxis.labelPosition = .bottom + xAxis.setLabelCount((RSSIConstants.maxNumberOfVisibleXValuesInt / 5) + 1, force: false) + } + + private func addYAxisLines(withColor color: UIColor) { + let axisLine = createAxisLine(withColor: color) + + leftAxis.addLimitLine(axisLine) + + for i in stride(from: leftAxis.axisMinimum, through: leftAxis.axisMaximum, by: leftAxis.granularity) { + guard i != 0 else { continue } + + let gridLine = createGridLine(withColor: color, andPosition: i) + + leftAxis.addLimitLine(gridLine) + } + } + + private func addXAxisLines(withColor color: UIColor) { + xAxis.removeAllLimitLines() + for i in stride(from: xAxis.axisMinimum, through: xAxis.axisMaximum, by: xAxis.granularity) { + let gridLine = createGridLine(withColor: color, andPosition: i) + + xAxis.addLimitLine(gridLine) + } + } + + private func createAxisLine(withColor color: UIColor) -> ChartLimitLine { + let axisLine = ChartLimitLine(limit: 0, label: "") + + axisLine.lineColor = color + axisLine.lineWidth = 1 + + return axisLine + } + + private func createGridLine(withColor color: UIColor, andPosition position: Double) -> ChartLimitLine { + let gridLine = ChartLimitLine(limit: position, label: "") + + gridLine.lineColor = color + gridLine.lineWidth = 1 + gridLine.lineDashPhase = 5 + gridLine.lineDashLengths = [3] + + return gridLine + } + + // MARK: Public methods + + func resetChart() { + leftAxis.axisMinimum = RSSIConstants.startYAxisMinimum + leftAxis.axisMaximum = RSSIConstants.startYAxisMaximum + xAxis.axisMinimum = RSSIConstants.startXAxisMinimum + xAxis.axisMaximum = RSSIConstants.startXAxisMaximum + + lineData?.clearValues() + data = LineChartData() + setVisibleXRangeMinimum(RSSIConstants.maxNumberOfVisibleXValues) + setVisibleXRangeMaximum(RSSIConstants.maxNumberOfVisibleXValues) + setVisibleYRangeMinimum(RSSIConstants.maxNumberOfVisibleYValues, axis: .left) + setVisibleYRangeMaximum(RSSIConstants.maxNumberOfVisibleYValues, axis: .left) + } + + func updateViewPosition() { + var xLeft: Double + + let axisMaximum = xAxis.axisMaximum + if !(highestVisibleX + RSSIConstants.approximationError < axisMaximum) { + xLeft = axisMaximum - visibleXRange + } else { + xLeft = highestVisibleX - visibleXRange + } + + let top = valueForTouchPoint(point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), + axis: .left).y + + let bottom = valueForTouchPoint(point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom), + axis: .left).y + + let yCenter = (top + bottom) / 2.0 + moveViewTo(xValue: xLeft, yValue: yCenter, axis: .left) + } + + func updateXAxisGridLines() { + for i in stride(from: xAxis.axisMinimum, through: xAxis.axisMaximum, by: xAxis.granularity) { + if let _ = xAxis.limitLines.first(where: { Int($0.limit) == Int(i) }) { + continue + } + let gridLine = createGridLine(withColor: RSSIConstants.axisRed, andPosition: i) + + xAxis.addLimitLine(gridLine) + } + } + + func checkIfDataSetExist(withLabel label: String) -> Bool { + guard let data = self.data else { return false } + return !data.dataSets.contains(where: { $0.label == label }) + } + + func addDataSetFor(_ entries: [ChartDataEntry], identifier: String, color: UIColor) { + let dataSet = LineChartDataSet(entries: entries, label: identifier) + cofigureDataSet(dataSet: dataSet) + dataSet.colors = [color] + + lineData?.addDataSet(dataSet) + } + + private func cofigureDataSet(dataSet: LineChartDataSet) { + dataSet.mode = .linear + dataSet.drawCirclesEnabled = false + dataSet.lineWidth = RSSIConstants.unselectedLineWidth + dataSet.drawFilledEnabled = false + dataSet.drawValuesEnabled = false + dataSet.fillAlpha = 3 + dataSet.drawHorizontalHighlightIndicatorEnabled = false + dataSet.drawVerticalHighlightIndicatorEnabled = false + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphXAxisRenderer.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphXAxisRenderer.swift new file mode 100644 index 00000000..75634392 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphXAxisRenderer.swift @@ -0,0 +1,68 @@ +// +// RSSIGraphXAxisRenderer.swift +// BlueGecko +// +// Created by Anastazja Gradowska on 14/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import Charts + +class RSSIGraphXAxisRenderer: XAxisRenderer { + + override func computeAxisValues(min: Double, max: Double) { + // sometimes rounding of float numbers causes disappearing the last label, we must ensure to have range 30.0 + super.computeAxisValues(min: min, max: min + RSSIConstants.maxNumberOfVisibleXValues) + } + + override func renderLimitLines(context: CGContext) { + guard let xAxis = self.axis as? XAxis, + let transformer = self.transformer + else { return } + let trans = transformer.valueToPixelMatrix + + for limitLine in xAxis.limitLines { + if !limitLine.isEnabled { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + let position = CGPoint(x: limitLine.limit, y: 0).applying(trans) + + drawLine(forLimitLine: limitLine, inContext: context, onPosition: position) + renderLimitLineLabel(context: context, limitLine: limitLine, position: position, yOffset: limitLine.yOffset) + } + } + + private func drawLine(forLimitLine limitLine: ChartLimitLine, inContext context: CGContext, onPosition position: CGPoint) { + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.x -= limitLine.lineWidth / 2.0 + clippingRect.size.width += limitLine.lineWidth + context.resetClip() + context.clip(to: clippingRect) + + context.beginPath() + context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) + + context.setStrokeColor(limitLine.lineColor.cgColor) + context.setLineWidth(limitLine.lineWidth) + + if limitLine.lineDashLengths != nil { + context.setLineDash(phase: limitLine.lineDashPhase, lengths: limitLine.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + } +} + +class RSSIGraphXAxisValueFormatter: IAxisValueFormatter { + func stringForValue(_ value: Double, axis: AxisBase?) -> String { + return "\(Int(value))s" + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphYAxisRenderer.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphYAxisRenderer.swift new file mode 100644 index 00000000..1be02947 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/RSSIGraphYAxisRenderer.swift @@ -0,0 +1,88 @@ +// +// RSSIGraphYAxisRenderer.swift +// BlueGecko +// +// Created by Anastazja Gradowska on 14/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import Charts + +class RSSIGraphYAxisRenderer : YAxisRenderer { + private let arrowWidth = CGFloat(6) + private let arrowHeight = CGFloat(7) + + override func renderLimitLines(context: CGContext) { + guard let yAxis = self.axis as? YAxis, + let transformer = self.transformer + else { return } + let trans = transformer.valueToPixelMatrix + + for limitLine in yAxis.limitLines { + if !limitLine.isEnabled { + continue + } + + context.saveGState() + defer { context.restoreGState() } + + let position = CGPoint(x: 0, y: limitLine.limit).applying(trans) + + drawLine(forLimitLine: limitLine, inContext: context, onPosition: position) + drawArrow(forLimitLine: limitLine, inContext: context, onPosition: position) + } + } + + private func drawLine(forLimitLine limitLine: ChartLimitLine, inContext context: CGContext, onPosition position: CGPoint) { + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= limitLine.lineWidth / 2.0 + clippingRect.size.height += limitLine.lineWidth + context.resetClip() + context.clip(to: clippingRect) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: position.y)) + + let lineX = viewPortHandler.contentRight + (limitLine.limit != 0 ? 0 : -arrowHeight) + context.addLine(to: CGPoint(x: lineX, y: position.y)) + + context.setStrokeColor(limitLine.lineColor.cgColor) + context.setLineWidth(limitLine.lineWidth) + + if limitLine.lineDashLengths != nil { + context.setLineDash(phase: limitLine.lineDashPhase, lengths: limitLine.lineDashLengths!) + } else { + context.setLineDash(phase: 0.0, lengths: []) + } + + context.strokePath() + } + + private func drawArrow(forLimitLine limitLine: ChartLimitLine, inContext context: CGContext, onPosition position: CGPoint) { + guard limitLine.limit == 0 else { return } + + var clippingRect = viewPortHandler.contentRect + clippingRect.origin.y -= arrowWidth/2 + clippingRect.size.height += arrowWidth + context.resetClip() + context.clip(to: clippingRect) + + context.beginPath() + context.move(to: CGPoint(x: viewPortHandler.contentRight - arrowHeight, y: position.y - arrowWidth/2)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight - arrowHeight, y: position.y + arrowWidth/2)) + context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: position.y)) + context.closePath() + + context.setFillColor(limitLine.lineColor.cgColor) + context.fillPath() + } +} + +class RSSIGraphYAxisValueFormatter: IAxisValueFormatter { + func stringForValue(_ value: Double, axis: AxisBase?) -> String { + let sign = value > 0 ? "+" : "" + return String(format: "%@%g.0 dBm", sign, value) + } +} + diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.swift new file mode 100644 index 00000000..cf5dae49 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.swift @@ -0,0 +1,229 @@ +// +// SILGraphView.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 18/02/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import UIKit +import Charts +import RxSwift +import RxRelay + +class SILGraphView: UIView { + + private var referenceDate = Date() + + var refresh: PublishRelay = PublishRelay() + var input: PublishRelay<[SILRSSIGraphDiscoveredPeripheralData]> = PublishRelay() + + private var disposeBag = DisposeBag() + + @IBOutlet weak var contentView: UIView! + @IBOutlet weak var chartView: RSSIGraphLineChartView! + + private let rightArrowButton = SILBigButton() + private let leftArrowButton = SILBigButton() + + private var minimumYValue: Double = RSSIConstants.startYAxisMinimum + private var maximumYValue: Double = RSSIConstants.startYAxisMaximum + + deinit { + debugPrint("SILGraphView deinit") + } + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonInit() + } + + private func commonInit() { + Bundle.main.loadNibNamed("SILGraphView", owner: self, options: nil) + addSubview(contentView) + contentView.backgroundColor = .clear + contentView.frame = bounds + contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + setupInput() + setupLeftArrowButton() + setupRightArrowButton() + } + + private func setupInput() { + input.asObservable() + .flatMap { Observable.from($0) } + .bind(with: self) { _self, cellData in + if let dataSet = _self.chartView.lineData?.dataSets.first(where: { $0.label == cellData.uuid }) as? LineChartDataSet { + dataSet.setColor(cellData.color) + dataSet.lineWidth = cellData.isSelected ? RSSIConstants.selectedLineWidth : RSSIConstants.unselectedLineWidth + if cellData.isSelected { + // set selected dataSet to be drawn on the top + _self.chartView.lineData?.removeDataSet(dataSet) + _self.chartView.lineData?.addDataSet(dataSet) + } + } + } + .disposed(by: disposeBag) + + input.asObservable() + .flatMap { Observable.from($0) } + .filter { [weak self] (dataSet) in + guard let self = self else { return false } + return self.chartView.checkIfDataSetExist(withLabel: dataSet.uuid) + } + .flatMap { [weak self] data -> Observable<(String, SILRSSIMeasurement, UIColor)> in + guard let self = self else { return .never() } + data.peripheral.rssiMeasurementTable.rssiMeasurements.value.forEach { self.addOrUpdateDataForPeripheral(data.uuid, measurement: $0, withColor: data.color) } + return Observable.combineLatest( + Observable.just(data.uuid), + data.peripheral.rssiMeasurementTable.lastMeasurement.asObservable(), + Observable.just(data.color) + ) + } + .map { (uuid: $0, measurement: $1, color: $2) } + .bind(with: self) { _self, val in + let (uuid, measurement, color) = val + _self.addOrUpdateDataForPeripheral(uuid, measurement: measurement, withColor: color) + } + .disposed(by: disposeBag) + + refresh.asObservable() + .bind(with: self) { _self, _ in + _self.refreshGraph() + } + .disposed(by: disposeBag) + } + + func startChart() { + self.resetTime() + self.chartView.resetChart() + } + + func redrawChart() { + self.chartView.resetChart() + self.disposeBag = DisposeBag() + self.setupInput() + } + + private func resetTime() { + self.referenceDate = Date() + } + + func refreshGraph() { + let now = Double(Date().timeIntervalSince(referenceDate)) + + chartView.xAxis.axisMinimum = RSSIConstants.startXAxisMinimum + chartView.xAxis.axisMaximum = max(now, RSSIConstants.maxNumberOfVisibleXValues) + + chartView.leftAxis.axisMinimum = minimumYValue + chartView.leftAxis.axisMaximum = maximumYValue + chartView.setVisibleXRangeMinimum(RSSIConstants.minNumberOfVisibleXValues) + chartView.setVisibleXRangeMaximum(RSSIConstants.maxNumberOfVisibleXValues) + chartView.setVisibleYRangeMinimum(RSSIConstants.minNumberOfVisibleYValues, axis: .left) + chartView.setVisibleYRangeMaximum(RSSIConstants.maxNumberOfVisibleYValues, axis: .left) + + chartView.updateXAxisGridLines() + chartView.updateViewPosition() + + let axisMaximum = chartView.xAxis.axisMaximum + + rightArrowButton.isHidden = !(chartView.highestVisibleX + RSSIConstants.approximationError < axisMaximum) + leftArrowButton.isHidden = chartView.lowestVisibleX == chartView.xAxis.axisMinimum + + chartView.lineData?.notifyDataChanged() + chartView.notifyDataSetChanged() + } + + private func setupRightArrowButton() { + addSubview(rightArrowButton) + let arrowSystemImage = UIImage(named: "right_chevron") + rightArrowButton.isHidden = true + rightArrowButton.setImage(arrowSystemImage, for: .normal) + rightArrowButton.translatesAutoresizingMaskIntoConstraints = false + rightArrowButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true + rightArrowButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true + + rightArrowButton.extendLeft = RSSIConstants.extendedButtonOffset + + rightArrowButton.addTarget(self, action: #selector(backToCurrentPosition), for: .touchUpInside) + } + + private func setupLeftArrowButton() { + addSubview(leftArrowButton) + let arrowSystemImage = UIImage(named: "left_chevron") + leftArrowButton.isHidden = true + leftArrowButton.setImage(arrowSystemImage, for: .normal) + leftArrowButton.translatesAutoresizingMaskIntoConstraints = false + leftArrowButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true + leftArrowButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true + + leftArrowButton.extendRight = RSSIConstants.extendedButtonOffset + + leftArrowButton.addTarget(self, action: #selector(backToOriginPosition), for: .touchUpInside) + } + + @objc private func backToCurrentPosition() { + let now = Double(Date().timeIntervalSince(referenceDate)) + chartView.moveViewToX(now) + } + + @objc private func backToOriginPosition() { + chartView.moveViewToX(0.0) + } + + func addOrUpdateDataForPeripheral(_ identifier: String, measurement: SILRSSIMeasurement, withColor color: UIColor) { + let yValue = measurement.rssi.doubleValue + let entry = ChartDataEntry(x: measurement.date.timeIntervalSince(referenceDate), y: yValue ) + + self.maximumYValue = yValue > maximumYValue ? yValue : maximumYValue + self.minimumYValue = yValue < minimumYValue ? yValue : minimumYValue + if let dataSet = chartView.lineData?.dataSets.first(where: { $0.label == identifier }) as? LineChartDataSet { + dataSet.append(entry) + } else { + chartView.addDataSetFor([entry], identifier: identifier, color: color) + } + } +} + +struct RSSIConstants { + static let axisRed = UIColor(red:0.84, green:0.14, blue:0.19, alpha:1.00) + + static let graphLineDisabled = UIColor.lightGray + + static func randomColor() -> UIColor { + return UIColor( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1), + alpha: 1.0 + ) + } + + static let minNumberOfVisibleXValues: Double = xAxisGranularity + static let maxNumberOfVisibleXValues: Double = 30 + static let maxNumberOfVisibleXValuesInt: Int = Int(maxNumberOfVisibleXValues) + + static let minNumberOfVisibleYValues: Double = 20 + static let maxNumberOfVisibleYValues: Double = 100 + + static let unselectedLineWidth = 1.0 + static let selectedLineWidth = 3.0 + + static let startYAxisMinimum = -100.0 + static let startYAxisMaximum = 0.0 + static let yAxisGranularity = 20.0 + + static let startXAxisMinimum = 0.0 + static let startXAxisMaximum = 30.0 + static let xAxisGranularity = 5.0 + static let xAxisGranularityInt = Int(yAxisGranularity) + + static let approximationError: Double = 2.0 + static let extendedButtonOffset: Double = 15 +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.xib b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.xib new file mode 100644 index 00000000..7ee69e48 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/GraphView/SILGraphView.xib @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILAppRSSIGraph.storyboard b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILAppRSSIGraph.storyboard new file mode 100644 index 00000000..dd201749 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILAppRSSIGraph.storyboard @@ -0,0 +1,796 @@ + + + + + + + + + + + + + + + Roboto-Bold + + + Roboto-Medium + + + Roboto-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift new file mode 100644 index 00000000..53f1ae1c --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift @@ -0,0 +1,28 @@ +// +// SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 28/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import UIKit + +class SILRSSIGraphDiscoveredDeviceCellCollectionViewCell: UICollectionViewCell { + + @IBOutlet weak var dotView: UIView! + @IBOutlet weak var stackView: UIStackView! + @IBOutlet weak var deviceNameLabel: UILabel! + @IBOutlet weak var uuidLabel: UILabel! + + var color: UIColor = .clear { + didSet { + self.dotView.backgroundColor = color + } + } + + override func layoutSubviews() { + super.layoutSubviews() + dotView.layer.cornerRadius = dotView.bounds.height / 2.0 + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphViewController.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphViewController.swift new file mode 100644 index 00000000..d391292b --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/UI/SILRSSIGraphViewController.swift @@ -0,0 +1,227 @@ +// +// SILRSSIGraphViewController.swift +// BlueGecko +// +// Created by Anastazja Gradowska on 11/02/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import UIKit +import RxSwift +import RxCocoa +import SVProgressHUD + +class SILRSSIGraphViewController: UIViewController, UIGestureRecognizerDelegate { + + @IBOutlet var navigationBar: UIView! + @IBOutlet weak var bottomBarView: UIView! + @IBOutlet weak var scanningButton: UIButton! + @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet weak var sortButton: UIButton! + @IBOutlet weak var filterButton: UIButton! + @IBOutlet weak var exportButton: SILPrimaryButton! + + @IBOutlet weak var chartContainerView: UIView! + + private let TitleForScanningButtonDuringScanning = "Stop Scanning" + private let TitleForScanningButtonWhenIsNotScanning = "Start Scanning" + + private let viewModel = SILRSSGraphViewModel() + + private var disposeBag = DisposeBag() + + @IBOutlet weak var chartView: SILGraphView! + + private let cornerRadius: CGFloat = 16.0 + + deinit { + debugPrint("SILRSSIGraphViewController deinit") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupAppearance() + setupCollectionView() + subscribeChartToViewModel() + setupScanningAction() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.viewModel.isScanning.accept(false) + } + + private func setupCollectionView() { + collectionView.rx.setDelegate(self).disposed(by: disposeBag) + viewModel.peripherals.asDriver(onErrorJustReturn: []) + .throttle(.milliseconds(300)) + .drive(collectionView.rx.items(cellIdentifier: "rssiGraphPeripheralCell", + cellType: SILRSSIGraphDiscoveredDeviceCellCollectionViewCell.self)) { _, data, cell in + cell.color = data.color + cell.deviceNameLabel.text = data.name + cell.uuidLabel.text = data.uuid + } + .disposed(by: disposeBag) + + collectionView.rx.modelSelected(SILRSSIGraphDiscoveredPeripheralData.self) + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) + .scan([SILRSSIGraphDiscoveredPeripheralData]()) { prev, current in + if prev.contains(where: { $0.uuid == current.uuid }) { + return [] + } + return [current] + } + .bind(to: viewModel.selected) + .disposed(by: disposeBag) + } + + private func setupAppearance() { + chartContainerView.addShadow() + chartContainerView.layer.cornerRadius = cornerRadius + bottomBarView.addShadow() + let sortImage = sortButton.currentImage?.withRenderingMode(.alwaysTemplate) + sortButton.setImage(sortImage, for: .normal) + let filterImage = filterButton.currentImage?.withRenderingMode(.alwaysTemplate) + filterButton.setImage(filterImage, for: .normal) + } + + private func subscribeChartToViewModel() { + viewModel.refresh.asObservable() + .bind(to: chartView.refresh) + .disposed(by: disposeBag) + + viewModel.peripherals.asObservable() + .bind(to: chartView.input) + .disposed(by: disposeBag) + } + + private func setupScanningAction() { + scanningButton.rx.tap + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) + .scan(viewModel.isScanning.value) { lastState, _ in + return !lastState + } + .bind(to: viewModel.isScanning) + .disposed(by: disposeBag) + + exportButton.rx.tap + .subscribe(onNext: { [weak self] _ in + guard let self = self else { return } + self.showProgressView(status: "Exporting") + self.viewModel.export(onFinish: { [weak self] filesToShare in + guard let self = self else { return } + self.hideProgressView() + self.showSharingExportFiles(filesToShare: filesToShare) + }) + }) + .disposed(by: disposeBag) + + viewModel.isScanning + .bind(with: self) { _self, scaninngState in + if scaninngState { + _self.setStopScanningButton() + _self.chartView.startChart() + } else { + _self.setStartScanningButton() + } + } + .disposed(by: disposeBag) + + viewModel.exportEnable + .bind(with: self) { _self, state in + _self.exportButton.isEnabled = state + } + .disposed(by: disposeBag) + + viewModel.blutoothDisabled + .startWith(false) + .bind(with: self) { _self, isBluetoothDisabled in + if isBluetoothDisabled { + _self.showBluetoothDisabledDialog() + } + } + .disposed(by: disposeBag) + } + + override func viewDidAppear(_ animated: Bool) { + self.navigationController?.interactivePopGestureRecognizer?.delegate = self + } + + @IBAction func backButtonTapped() -> Void { + self.navigationController?.popViewController(animated: true) + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "showSortVC", let sortVC = segue.destination as? SILSortViewController { + sortVC.delegate = self + } else if segue.identifier == "showFilterVC", let filterVC = segue.destination as? SILBrowserFilterViewController { + filterVC.delegate = self + } + } + + private func setStopScanningButton() { + scanningButton.backgroundColor = UIColor.sil_siliconLabsRed() + scanningButton.setTitle(TitleForScanningButtonDuringScanning, for: .normal) + } + + private func setStartScanningButton() { + scanningButton.backgroundColor = UIColor.sil_regularBlue() + scanningButton.setTitle(TitleForScanningButtonWhenIsNotScanning, for: .normal) + } + + private func showProgressView(status: String) { + SVProgressHUD.show(withStatus: status) + } + + private func hideProgressView() { + SVProgressHUD.dismiss() + } + + private func showSharingExportFiles(filesToShare: [URL]) { + let filesToShare = filesToShare + let rssiGraphSubject = "RSSI Graph Export" + self.showSharingExportFiles(filesToShare: filesToShare, + subject: rssiGraphSubject, + sourceView: self.exportButton, + sourceRect: self.exportButton.bounds, + completionWithItemsHandler: nil) + } + + private func showBluetoothDisabledDialog() { + let bluetoothDisabledAlert = SILBluetoothDisabledAlert.rssiGraph + self.alertWithOKButton(title: bluetoothDisabledAlert.title, message: bluetoothDisabledAlert.message) { _ in + self.navigationController?.popViewController(animated: true) + } + } +} + +extension SILRSSIGraphViewController: SILSortViewControllerDelegate { + func sortOptionWasSelected(with option: SILSortOption) { + viewModel.sortOption.accept(option) + } +} + +extension SILRSSIGraphViewController: SILBrowserFilterViewControllerDelegate { + func backButtonWasTapped() { + self.navigationController?.dismiss(animated: true) + } + + func searchButtonWasTapped(_ vm: SILBrowserFilterViewModel) { + self.chartView.redrawChart() + viewModel.applyFilter(vm) + self.navigationController?.dismiss(animated: true) + } +} + +extension SILRSSIGraphViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let height = collectionView.bounds.height + let width = height + 20 + return CGSize(width: width, height: height) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0) + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILPeripheralDataFilterHelper.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILPeripheralDataFilterHelper.swift new file mode 100644 index 00000000..99e499ab --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILPeripheralDataFilterHelper.swift @@ -0,0 +1,58 @@ +// +// SILPeripheralDataFilterHelper.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 06/04/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation + +typealias PeripheralData = SILRSSIGraphDiscoveredPeripheralData +typealias FilterClosure = (PeripheralData) -> Bool + +struct PeripheralDataFilterHelper { + + enum FilterType: Hashable { + case deviceName(_ name: String) + case rssiMinimum(_ minRSSI: Int) + case beaconTypes(_ beaconTypes: [SILBrowserBeaconType]) + case isFavourite(_ isFavourite: Bool) + case isConnectable(_ isConnectable: Bool) + case none + } + + let noneFilter: FilterClosure = { _ in return true } + + func getFilterOfType(_ type: FilterType) -> FilterClosure { + switch type { + case let .deviceName(name): + return !name.isEmpty ? { $0.name.contains(name) } : noneFilter + case let .rssiMinimum(minRSSI): + return { $0.lastRSSIMeasurement > minRSSI } + case let .beaconTypes(beaconTypes): + let selectedBeaconTypes = beaconTypes.filter { $0.isSelected } + var selectedBeaconTypesFilters: [FilterClosure] = [noneFilter] + for beaconType in selectedBeaconTypes { + selectedBeaconTypesFilters.append({ peripheralData in + peripheralData.peripheral.beacon.name.contains(beaconType.beaconName) + }) + } + return orFilters(selectedBeaconTypesFilters) + case let .isFavourite(isFavourite): + return isFavourite ? { $0.peripheral.isFavourite } : noneFilter + case let .isConnectable(isConnectable): + return isConnectable ? { $0.peripheral.isConnectable } : noneFilter + case .none: + return noneFilter + } + } + + func orFilters(_ closures: [FilterClosure]) -> FilterClosure { + return { listElement in closures.reduce(false, { $0 || $1(listElement) }) } + } + + func andFilters(_ closures: [FilterClosure]) -> FilterClosure { + return { listElement in closures.reduce(true, { $0 && $1(listElement) }) } + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphCentralManager.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphCentralManager.swift new file mode 100644 index 00000000..9af8f007 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphCentralManager.swift @@ -0,0 +1,243 @@ +// +// SILRSSIGraphCentralManager.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 01/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import RxSwift +import RxCocoa +import CoreBluetooth +import CoreLocation + +fileprivate struct Constants { + static let kIBeaconUUIDString = "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0" + static let kIBeaconIdentifier = "com.silabs.retailbeacon" +} + +class SILRSSIGraphCentralManager: NSObject { + + private let centralManager = CBCentralManager() + private let locationManager = CLLocationManager() + + private var regions = [CLBeaconRegion]() + + lazy var discoveredPeripherals: Observable<[SILDiscoveredPeripheral]> = + discoveredPeripheralsMapping.asObservable() + .map { Array($0.values) } + .do(onSubscribe: { [weak self] in + guard let sSelf = self else { return } + sSelf.startScanning() + sSelf.hasSubscribers = true + }, onDispose: { [weak self] in + guard let sSelf = self else { return } + sSelf.hasSubscribers = false + sSelf.stopScanning() + }) + .share() + + lazy var newestDiscoveredPeripherals: Observable<[SILDiscoveredPeripheral]> = + discoveredPeripherals + .map { $0.filter { $0.rssiMeasurementTable.hasRSSIMeasurement(inPastTimeInterval: 5.0) } } + + var newDiscoveredPeripheral: PublishRelay = PublishRelay() + + lazy var state: Observable = centralManager.rx.state.asObservable() + + private let discoveredPeripheralsMapping = BehaviorRelay<[String: SILDiscoveredPeripheral]>(value: [:]) + + private var isScanning = false + private var hasSubscribers = false + + private let disposeBag = DisposeBag() + private var scanningDisposeBag = DisposeBag() + + override init() { + super.init() + + setupBluetoothSubsciptions() + setupBeaconMonitoring() + } + + deinit { + self.stopScanning() + debugPrint("RSSIGraphCentralManager deinit") + } + + private func setupBluetoothSubsciptions() { + centralManager.rx.didDiscover + .bind(with: self) { (_self, discoverData) in + let (peripheral, advertisementData, RSSI) = discoverData + if !_self.isProbablyIBeacon(advertisementData: advertisementData) { + _self.insertOrUpdateDiscoveredPeripheral(peripheral, + advertisementData: advertisementData, + rssi: RSSI, + andDiscoveringTimestamp: _self.getTimestampWithAdvertisementData(advertisementData)) + } + } + .disposed(by: disposeBag) + centralManager.rx.state + .bind(with: self) { (_self, state) in + if state == .poweredOn && _self.hasSubscribers { + _self.startScanning() + } + if state == .poweredOff { + _self.stopScanning() + } + } + .disposed(by: disposeBag) + } + + private func startScanning() { + if centralManager.state == .poweredOn && !self.isScanning { + self.removeAllDiscoveredPeripherals() + self.isScanning = true + debugPrint("SILRSSICentralManager: scan has run") + self.centralManager.scanForPeripherals( + withServices: nil, + options: [CBCentralManagerScanOptionAllowDuplicatesKey: true] + ) + self.startRanging() + } + } + + private func stopScanning() { + if isScanning { + self.scanningDisposeBag = DisposeBag() + if centralManager.state == .poweredOn { + centralManager.stopScan() + } + self.stopRanging() + debugPrint("SILRSSICentralManager: scan has stopped") + + isScanning = false + } + } + + private func getTimestampWithAdvertisementData(_ advertisementData: [String : Any]) -> Double { + if let stringValue = advertisementData["kCBAdvDataTimestamp"] as? String, + let timestamp = Double(stringValue) { + return timestamp + } + return Date().timeIntervalSinceReferenceDate + } + + func discoveredPeripheral(for peripheral: CBPeripheral) -> SILDiscoveredPeripheral? { + let peripheralIdentifier = SILDiscoveredPeripheralIdentifierProvider.provideKeyForCBPeripheral(peripheral) + return self.discoveredPeripheralsMapping.value[peripheralIdentifier] + } + + private func isProbablyIBeacon(advertisementData: [String: Any]) -> Bool { + if let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool, isConnectable { + return false + } + + let nonIBeaconKeys = Set([ + CBAdvertisementDataManufacturerDataKey, + CBAdvertisementDataLocalNameKey, + CBAdvertisementDataServiceDataKey, + CBAdvertisementDataServiceUUIDsKey, + CBAdvertisementDataOverflowServiceUUIDsKey, + CBAdvertisementDataTxPowerLevelKey, + CBAdvertisementDataSolicitedServiceUUIDsKey + ]) + + return nonIBeaconKeys.intersection(Set(advertisementData.keys)).isEmpty + } + + private func insertOrUpdateDiscoveredPeripheral(_ peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber, andDiscoveringTimestamp timestamp: Double) { + let key = SILDiscoveredPeripheralIdentifierProvider.provideKeyForCBPeripheral(peripheral) + if let discoveredPeripheral = self.discoveredPeripheralsMapping.value.first(where: { $0.key == key }) { + discoveredPeripheral.value.update(withAdvertisementData: advertisementData, rssi: RSSI, andDiscoveringTimestamp: timestamp) + } else { + let discoveredPeripheral = SILDiscoveredPeripheral(peripheral: peripheral, advertisementData: advertisementData, rssi: RSSI, andDiscoveringTimestamp: timestamp) + self.discoveredPeripheralsMapping.addElement(key: key, value: discoveredPeripheral) + self.newDiscoveredPeripheral.accept(discoveredPeripheral) + } + } + + func remove(discoveredPeripheral: SILDiscoveredPeripheral) { + self.discoveredPeripheralsMapping.removeElement(key: discoveredPeripheral.identityKey) + } + + func removeAllDiscoveredPeripherals() { + self.discoveredPeripheralsMapping.accept([:]) + } +} + +extension SILRSSIGraphCentralManager { + private func setupBeaconMonitoring() { + locationManager.requestAlwaysAuthorization() + let iBeaconUUID = UUID(uuidString: Constants.kIBeaconUUIDString)! + let beaconRegion = CLBeaconRegion(proximityUUID: iBeaconUUID, identifier: Constants.kIBeaconIdentifier) + + regions.append(beaconRegion) + setupBeaconSubscriptions() + } + + private func setupBeaconSubscriptions() { + locationManager.rx.didEnterRegion + .subscribe(onNext: { [unowned self] (region) in + for beaconRegion in self.regions { + if beaconRegion == region { + self.locationManager.startRangingBeacons(in: beaconRegion) + } + } + }) + .disposed(by: disposeBag) + locationManager.rx.didExitRegion + .subscribe(onNext: { [unowned self] (region) in + for beaconRegion in self.regions { + if beaconRegion == region { + self.locationManager.stopRangingBeacons(in: beaconRegion) + } + } + }) + .disposed(by: disposeBag) + locationManager.rx.didRangeBeaconsInRegion + .subscribe(onNext: { [unowned self] (beacons, region) in + for foundBeacon in beacons { + if foundBeacon.rssi != 0 { + self.insertOrUpdateDiscoveredIBeacon(foundBeacon) + } + } + }) + .disposed(by: disposeBag) + } + + private func startRanging() { + for beaconRegion in regions { + locationManager.startRangingBeacons(in: beaconRegion) + } + } + + private func stopRanging() { + for beaconRegion in regions { + locationManager.stopRangingBeacons(in: beaconRegion) + } + } + + private func insertOrUpdateDiscoveredIBeacon(_ iBeacon: CLBeacon) { + let iBeaconIdentifier = SILDiscoveredPeripheralIdentifierProvider.provideKeyForCLBeacon(iBeacon) + let timestamp = getTimestamptForIBeacons(iBeacon) + + if let discoveredPeripheral = self.discoveredPeripheralsMapping.value.first(where: { $0.key == iBeaconIdentifier }) { + discoveredPeripheral.value.update(withIBeacon: iBeacon, andDiscoveringTimestamp: timestamp) + } else { + let discoveredPeripheral = SILDiscoveredPeripheral(iBeacon: iBeacon, andDiscoveringTimestamp: timestamp) + + self.discoveredPeripheralsMapping.addElement(key: iBeaconIdentifier, value: discoveredPeripheral) + self.newDiscoveredPeripheral.accept(discoveredPeripheral) + } + } + + private func getTimestamptForIBeacons(_ iBeacon: CLBeacon) -> Double { + if #available(iOS 13.0, *) { + return iBeacon.timestamp.timeIntervalSinceReferenceDate + } else { + return Date().timeIntervalSinceReferenceDate + } + } +} diff --git a/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphViewModel.swift b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphViewModel.swift new file mode 100644 index 00000000..db408140 --- /dev/null +++ b/SiliconLabsApp/ViewControllers/RSSIGraph/ViewModel/SILRSSIGraphViewModel.swift @@ -0,0 +1,201 @@ +// +// SILRSSIGraphViewModel.swift +// BlueGecko +// +// Created by Grzegorz Janosz on 01/03/2022. +// Copyright © 2022 SiliconLabs. All rights reserved. +// + +import Foundation +import RxSwift +import RxRelay +import RxCocoa + +struct SILRSSIGraphDiscoveredPeripheralData { + var color: UIColor + let peripheral: SILDiscoveredPeripheral + var isSelected: Bool + let uuid: String + let name: String + + var lastRSSIMeasurement: Int { + return peripheral.rssiMeasurementTable.lastRSSIMeasurement()?.intValue ?? 0 + } +} + +class SILRSSGraphViewModel { + + private var filterHelper = PeripheralDataFilterHelper() + + lazy var filter: BehaviorRelay = BehaviorRelay(value: filterHelper.getFilterOfType(.none)) + + lazy var sortOption: BehaviorRelay = BehaviorRelay(value: .none) + + lazy var selected: PublishRelay<[PeripheralData]> = PublishRelay() + + lazy var refresh = PublishRelay() + + private lazy var _peripherals = BehaviorRelay<[PeripheralData]>(value: []) + + lazy var peripherals = Observable.combineLatest(_peripherals, selected.startWith([]), sortOption, filter) + .map { (peripherals, selectedPeripherals, sortOption, filter) -> [PeripheralData] in + // filter + var peripherals = peripherals.filter { filter($0) } + + // map selected + if let selected = selectedPeripherals.first { + peripherals = peripherals.map { PeripheralData(color: $0.uuid == selected.uuid ? $0.color : RSSIConstants.graphLineDisabled, + peripheral: $0.peripheral, + isSelected: $0.uuid == selected.uuid, uuid: $0.uuid, name: $0.name) + } + } + + // sort + switch sortOption { + case .ascendingRSSI: + return peripherals.sorted() { $0.lastRSSIMeasurement < $1.lastRSSIMeasurement } + case .descendingRSSI: + return peripherals.sorted() { $0.lastRSSIMeasurement > $1.lastRSSIMeasurement } + case .AZ: + return peripherals.sorted() { $0.name < $1.name } + case .ZA: + return peripherals.sorted() { $0.name > $1.name } + default: + break + } + return peripherals + } + + private lazy var newPeripherals: Observable = centralManager + .newDiscoveredPeripheral + .map { + PeripheralData(color: RSSIConstants.randomColor(), peripheral: $0, isSelected: false, uuid: $0.identityKey, + name: $0.advertisedLocalName ?? DefaultDeviceName) + } + .asObservable() + + lazy var exportEnable: Observable = Observable.combineLatest(isScanning, _peripherals) + .map { !($0 || $1.isEmpty)} + + var isScanning = BehaviorRelay(value: false) + + lazy var blutoothDisabled: Observable = centralManager.state + .skip(1) + .map { $0 != .poweredOn } + + private let centralManager: SILRSSIGraphCentralManager + private let fileWriter = SILFileWriter(exportDirName: "SILRSSIGraphExport") + + private var disposeBag = DisposeBag() + private var scanningDisposeBag = DisposeBag() + + deinit { + debugPrint("SILRSSGraphViewModel deinit") + } + + init() { + self.centralManager = SILRSSIGraphCentralManager() + + isScanning.asObservable() + .bind(with: self) { _self, isScanning in + if isScanning { + _self.startScanning() + } else { + _self.stopScanning() + } + } + .disposed(by: disposeBag) + + selected.map { _ in () } + .bind(to: refresh) + .disposed(by: disposeBag) + + fileWriter.clearExportDir() + } + + func applyFilter(_ filterViewModel: SILBrowserFilterViewModel) { + var filters: [FilterClosure] = [{ _ in true }] + if filterViewModel.isFilterActive() { + let deviceName = filterViewModel.searchByDeviceName + let minRSSI = filterViewModel.dBmValue + let beaconTypes = filterViewModel.beaconTypes as! [SILBrowserBeaconType] + let isFavourite = filterViewModel.isFavouriteFilterSet + let isConnectable = filterViewModel.isConnectableFilterSet + + filters.append(contentsOf: [ + filterHelper.getFilterOfType(.deviceName(deviceName)), + filterHelper.getFilterOfType(.rssiMinimum(minRSSI)), + filterHelper.getFilterOfType(.beaconTypes(beaconTypes)), + filterHelper.getFilterOfType(.isFavourite(isFavourite)), + filterHelper.getFilterOfType(.isConnectable(isConnectable)) + ]) + } + self.filter.accept(filterHelper.andFilters(filters)) + } + + private func startScanning() { + _peripherals.accept([]) + + centralManager + .discoveredPeripherals + .flatMap { + Observable + .from( $0.map { peripheral in peripheral.rssiMeasurementTable.rssiMeasurements.asObservable() }) + .merge() + .map { _ in Void() } + } + .throttle(.milliseconds(300), scheduler: MainScheduler.instance) + .bind(to: refresh) + .disposed(by: scanningDisposeBag) + + newPeripherals.asObservable() + .filter { newPeripheral in !self._peripherals.value.contains(where: { $0.uuid == newPeripheral.uuid }) } + .scan([PeripheralData]()) { $0 + [$1] } + .bind(to: _peripherals) + .disposed(by: scanningDisposeBag) + + peripherals.subscribe(onNext: { debugPrint($0.count) }).disposed(by: disposeBag) + } + + private func stopScanning() { + self.scanningDisposeBag = DisposeBag() + } + + func export(onFinish: @escaping ([URL]) -> ()) { + var fileUrls = [URL]() + + let csvString = createCSVString() + let filePath = fileWriter.getFilePath(withName: getFileName(), fileExtension: "csv") + + if fileWriter.createEmptyFile(atPath: filePath) { + if fileWriter.openFile(filePath: filePath) { + _ = fileWriter.append(text: csvString) + fileWriter.closeFile() + fileUrls.append(fileWriter.getFileUrl(filePath: filePath)) + } + } + _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { timer in + timer.invalidate() + + onFinish(fileUrls) + }) + } + + private func getFileName() -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd_HH:mm" + return "export_rssi_\(dateFormatter.string(from: Date()))" + } + + private func createCSVString() -> String { + let discoveredPeripherals = _peripherals.value.map { $0.peripheral } + var csvString = "peripheral name,peripheral uuid,timestamp,value\n" + + for peripheral in discoveredPeripherals { + for measurement in peripheral.rssiMeasurementTable.rssiMeasurements.value { + csvString.append(contentsOf: "\(peripheral.advertisedLocalName ?? DefaultDeviceName),\(peripheral.uuid),\(measurement.date),\(measurement.rssi)\n") + } + } + return csvString + } +} diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift index afd20f4c..c940bde5 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/Peripheral/SILRangeTestPeripheral.swift @@ -690,10 +690,10 @@ extension SILRangeTestPeripheral: SILDiscoveredPeripheralDelegate { } } - func peripheral(_ peripheral: SILDiscoveredPeripheral!, didUpdateWithAdvertisementData dictionary: [AnyHashable : Any]!, andRSSI rssi: NSNumber!) { + func peripheral(_ peripheral: SILDiscoveredPeripheral, didUpdateWithAdvertisementData dictionary: [String : Any]?, andRSSI rssi: NSNumber) { var manufacturerData: SILRangeTestManufacturerData? = nil - if let data = peripheral.manufacturerData { + if let data = peripheral.manufacturerData as Data? { manufacturerData = SILRangeTestManufacturerData(manufacturerData: data) self.manufacturerData = manufacturerData } diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift index c488443c..36475f36 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestAppContainerViewController.swift @@ -78,11 +78,11 @@ class SILRangeTestAppContainerViewController: UIViewController, UITabBarControll extension SILRangeTestAppContainerViewController: SILRangeTestBluetoothConnectionsHandler { var filter: DiscoveredPeripheralFilter { return { discoveredPeripheral in - guard let discoveredPeripheral = discoveredPeripheral else { + guard let discoveredPeripheral = discoveredPeripheral, let peripheral = discoveredPeripheral.peripheral else { return false } - return discoveredPeripheral.isRangeTest && !self.connectedPeripherals.contains(discoveredPeripheral.peripheral) + return discoveredPeripheral.isRangeTest && !self.connectedPeripherals.contains(peripheral) } } diff --git a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift index 4afdd6c6..ca7ff17f 100644 --- a/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift +++ b/SiliconLabsApp/ViewControllers/RangeTestApp/SILRangeTestSelectDeviceViewController.swift @@ -55,9 +55,11 @@ class SILRangeTestSelectDeviceViewController: UIViewController, SILDeviceSelecti let storyboard = UIStoryboard(name: "SILAppTypeRangeTest", bundle: nil) let selectionViewController = storyboard.instantiateViewController(withIdentifier: "SILRangeTestModeSelectionViewController") as! SILRangeTestModeSelectionViewController + guard let peripheral = peripheral.peripheral else { return } + selectionViewController.app = self.app; selectionViewController.delegate = self - selectionViewController.peripheral = SILRangeTestPeripheral(withPeripheral: peripheral.peripheral, andCentralManager: self.centralManager) + selectionViewController.peripheral = SILRangeTestPeripheral(withPeripheral: peripheral, andCentralManager: self.centralManager) self.popoverController = WYPopoverController.sil_presentCenterPopover(withContentViewController: selectionViewController, presenting: self, delegate: self, animated: true) } diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.h deleted file mode 100644 index 2b3c0cb6..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// SILBeaconRegistry.h -// SiliconLabsApp -// -// Created by Colden Prime on 1/21/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import -#import -@class EddystoneBeacon; - -@interface SILBeaconRegistry : NSObject - -- (void)updateWithAdvertisment:(NSDictionary *)advertisement name:(NSString *)name RSSI:(NSNumber *)RSSI; -- (void)updateWithIBeacon:(CLBeacon *)beacon; -- (void)updateWithEddystoneBeacon:(EddystoneBeacon *)beacon; -- (NSArray *)beaconRegistryEntries; -- (void)removeIBeaconEntriesWithUUID:(NSUUID *)proximityUUID; - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.m deleted file mode 100644 index bbed0000..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistry.m +++ /dev/null @@ -1,122 +0,0 @@ -// -// SILBeaconRegistry.m -// SiliconLabsApp -// -// Created by Colden Prime on 1/21/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconRegistry.h" -#import "SILBeacon.h" -#import "SILBeaconRegistryEntry.h" -#import "SILRSSIMeasurementTable.h" - -NSTimeInterval const SILBeaconRegistryTimeoutThreshold = 5.0; - -@interface SILBeaconRegistry () -@property (strong, nonatomic) NSMutableDictionary *beaconEntries; -@end - -@implementation SILBeaconRegistry - -+ (NSString *)beaconKeyWithBeacon:(SILBeacon *)beacon { - return [NSString stringWithFormat:@"%@_%d_%d_%@", beacon.UUIDString, beacon.major, beacon.minor, beacon.url.absoluteString]; -} - -- (instancetype)init { - self = [super init]; - if (self) { - self.beaconEntries = [NSMutableDictionary dictionary]; - } - return self; -} - -- (void)removeLostBeacons { - NSMutableArray *timeoutBeaconKeys = [NSMutableArray array]; - for (NSString *beaconKey in self.beaconEntries) { - SILBeaconRegistryEntry *entry = self.beaconEntries[beaconKey]; - if (![entry.RSSIMeasurementTable hasRSSIMeasurementInPastTimeInterval:SILBeaconRegistryTimeoutThreshold]) { - [timeoutBeaconKeys addObject:beaconKey]; - } - } - if (timeoutBeaconKeys.count > 0) { - [self.beaconEntries removeObjectsForKeys:timeoutBeaconKeys]; - } -} - -- (void)removeIBeaconEntriesWithUUID:(NSUUID *)proximityUUID { - NSMutableArray *uuidBeaconKeys = [NSMutableArray array]; - for (NSString *beaconKey in self.beaconEntries) { - if ([beaconKey containsString:proximityUUID.UUIDString]) { - [uuidBeaconKeys addObject:beaconKey]; - } - } - if (uuidBeaconKeys.count > 0) { - [self.beaconEntries removeObjectsForKeys:uuidBeaconKeys]; - } -} - -- (void)updateWithAdvertisment:(NSDictionary *)advertisement name:(NSString *)name RSSI:(NSNumber *)RSSI { - [self removeLostBeacons]; - - NSError *error = nil; - SILBeacon *beacon = [SILBeacon beaconWithAdvertisment:advertisement name:name error:&error]; - if (error == nil) { - NSString *beaconKey = [SILBeaconRegistry beaconKeyWithBeacon:beacon]; - - SILBeaconRegistryEntry *entry = [self.beaconEntries objectForKey:beaconKey]; - if (entry == nil) { - entry = [[SILBeaconRegistryEntry alloc] initWithBeacon:beacon]; - self.beaconEntries[beaconKey] = entry; - } else { - entry.beacon.calibrationPower = beacon.calibrationPower; - } - - [entry.RSSIMeasurementTable addRSSIMeasurement:RSSI]; - } -} - -- (void)updateWithIBeacon:(CLBeacon *)beacon { - SILBeacon *silBeacon = [SILBeacon beaconWithIBeacon:beacon]; - - NSString *beaconKey = [SILBeaconRegistry beaconKeyWithBeacon:silBeacon]; - - SILBeaconRegistryEntry *entry = [self.beaconEntries objectForKey:beaconKey]; - if (entry == nil) { - entry = [[SILBeaconRegistryEntry alloc] initWithBeacon:silBeacon]; - self.beaconEntries[beaconKey] = entry; - } else { - entry.beacon.calibrationPower = silBeacon.calibrationPower; - } - - entry.beacon.beacon = beacon; - [entry.RSSIMeasurementTable addRSSIMeasurement:@(beacon.rssi)]; -} - -- (void)updateWithEddystoneBeacon:(EddystoneBeacon *)beacon { - SILBeacon *silBeacon = [SILBeacon beaconWithEddystone:beacon]; - - NSString *beaconKey = [SILBeaconRegistry beaconKeyWithBeacon:silBeacon]; - - SILBeaconRegistryEntry *entry = [self.beaconEntries objectForKey:beaconKey]; - if (entry == nil) { - entry = [[SILBeaconRegistryEntry alloc] initWithBeacon:silBeacon]; - self.beaconEntries[beaconKey] = entry; - } else { - entry.beacon.calibrationPower = silBeacon.calibrationPower; - entry.beacon.tlmData = silBeacon.tlmData; - entry.beacon.url = silBeacon.url; - } - - [entry.RSSIMeasurementTable addRSSIMeasurement:@(beacon.rssi)]; -} - -- (NSArray *)beaconRegistryEntries { - [self removeLostBeacons]; - - NSArray *allValues = [self.beaconEntries allValues]; - NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"RSSIMeasurementTable.lastRSSIMeasurement" ascending:NO]; - return [allValues sortedArrayUsingDescriptors:@[sortDescriptor]]; -} - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.h deleted file mode 100644 index 4247b9e3..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// SILBeaconRegistryEntry.h -// SiliconLabsApp -// -// Created by Colden Prime on 1/21/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import -@class SILBeacon; -@class SILRSSIMeasurementTable; - -@interface SILBeaconRegistryEntry : NSObject - -@property (strong, nonatomic) SILBeacon *beacon; -@property (strong, nonatomic) SILRSSIMeasurementTable *RSSIMeasurementTable; - -- (instancetype)initWithBeacon:(SILBeacon *)beacon; - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.m deleted file mode 100644 index 6dd05104..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntry.m +++ /dev/null @@ -1,32 +0,0 @@ -// -// SILBeaconRegistryEntry.m -// SiliconLabsApp -// -// Created by Colden Prime on 1/21/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconRegistryEntry.h" -#import "SILRSSIMeasurementTable.h" - -NSString * const SILBeaconRegistryEntryDateKey = @"date"; -NSString * const SILBeaconRegistryEntryRSSIKey = @"RSSI"; - -@interface SILBeaconRegistryEntry () - -@end - -@implementation SILBeaconRegistryEntry - -#pragma mark - Instance Methods - -- (instancetype)initWithBeacon:(SILBeacon *)beacon { - self = [super init]; - if (self) { - self.beacon = beacon; - self.RSSIMeasurementTable = [[SILRSSIMeasurementTable alloc] init]; - } - return self; -} - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.h deleted file mode 100644 index 5a5f818f..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// SILBeaconRegistryEntryCell.h -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import -#import "SILBeaconRegistryEntryViewModel.h" - -@interface SILBeaconRegistryEntryCell : UITableViewCell -@property (weak, nonatomic) IBOutlet UIView *beaconSeparatorView; -@property (weak, nonatomic) IBOutlet UIImageView *beaconIconImageView; -@property (weak, nonatomic) IBOutlet UILabel *beaconName; -@property (weak, nonatomic) IBOutlet UILabel *beaconRSSIValue; -@property (weak, nonatomic) IBOutlet UILabel *beaconType; -@property (weak, nonatomic) IBOutlet UILabel *beaconDistanceValue; -@property (weak, nonatomic) IBOutlet UIImageView *beaconDistanceImageView; - -- (void)configureWithViewModel:(SILBeaconRegistryEntryViewModel *)viewModel; -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.m deleted file mode 100644 index 93fddefa..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILBeaconRegistryEntryCell.m +++ /dev/null @@ -1,25 +0,0 @@ -// -// SILBeaconRegistryEntryCell.m -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconRegistryEntryCell.h" - -@implementation SILBeaconRegistryEntryCell - -- (void)configureWithViewModel:(SILBeaconRegistryEntryViewModel *)viewModel { - _beaconName.text = viewModel.name; - _beaconType.text = viewModel.type; - _beaconIconImageView.image = viewModel.image; - UIImage* distanceImage = viewModel.distanceImage; - if (distanceImage != nil) { - _beaconDistanceImageView.image = distanceImage; - } - _beaconDistanceValue.text = viewModel.distanceName; - _beaconRSSIValue.text = viewModel.formattedRSSI; -} - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.h deleted file mode 100644 index 7b8a7f7f..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// SILRetailBeaconAppViewController.h -// SiliconLabsApp -// -// Created by Colden Prime on 1/20/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import -@class SILApp; - -@interface SILRetailBeaconAppViewController : UIViewController - -@property (strong, nonatomic) SILApp *app; - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.m deleted file mode 100644 index 685ee15c..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconAppViewController.m +++ /dev/null @@ -1,385 +0,0 @@ -// -// SILRetailBeaconAppViewController.m -// SiliconLabsApp -// -// Created by Colden Prime on 1/20/15. -// Copyright (c) 2015 SiliconLabs. All rights reserved. -// - -#import -#import -#import "SILRetailBeaconAppViewController.h" -#import "SILApp.h" -#import "SILBeacon.h" -#import "SILBeaconRegistry.h" -#import "SILBeaconRegistryEntry.h" -#import "SILBeaconRegistryEntryViewModel.h" -#import "SILRSSIMeasurementTable.h" -#import "SILSettings.h" -#import "UIView+SILAnimations.h" -#import "SILBeaconViewModel.h" -#import "SILBeaconRegistryEntryCell.h" -#import "UITableViewCell+SILHelpers.h" -#import "SILDoubleKeyDictionaryPair.h" -#import "SILRetailBeaconDetailsViewController.h" -#import "WYPopoverController+SILHelpers.h" -#import "SILCentralManager.h" - -#define IS_IOS_8_OR_LATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) - -CGFloat const SILRetailBeaconAppRefreshRate = 1.0; -extern CGFloat const kIBeaconDMPZigbeeMajorNumber; -extern CGFloat const kIBeaconDMPProprietaryMajorNumber; -extern CGFloat const kIBeaconMajorNumber; -extern CGFloat const kIBeaconMinorNumber; -extern CGFloat const kAltBeaconMfgId; -CGFloat const kBeaconListTableViewCellRowHeight = 80.0; - -extern NSString * const kIBeaconUUIDString; -extern NSString * const kIBeaconDMPUUIDString; -extern NSString * const kAltBeaconUUIDString; -extern NSString * const kIBeaconIdentifier; -extern NSString * const kIBeaconDMPZigbeeIdentifier; -extern NSString * const kIBeaconDMPProprietaryIdentifier; -NSString * const kScanningForBeacons = @"Scanning for beacons..."; -NSString * const kAdditionalBeacons = @"Scanning for additional beacons..."; -NSString * const kScanForNewBeacons = @"Scan for new beacons"; - -@interface SILRetailBeaconAppViewController () - -@property (nonatomic, strong) SILBeaconRegistry *beaconRegistry; -@property (nonatomic, strong) CBCentralManager *centralManager; -@property (nonatomic, strong) CLBeaconRegion *beaconRegion; -@property (nonatomic, strong) CLBeaconRegion *dmpZigbeeBeaconRegion; -@property (nonatomic, strong) CLBeaconRegion *dmpProprietaryBeaconRegion; -@property (nonatomic, strong) NSArray *regions; -@property (nonatomic, strong) CLLocationManager *locationManager; - -@property (nonatomic, assign) BOOL isScanning; -@property (nonatomic, strong) NSTimer *reloadDataTimer; -@property (nonatomic, strong) SILBeaconRegistryEntryCell *sizingRegistryEntryCell; - -@property (weak, nonatomic) IBOutlet UIView *loadingView; -@property (weak, nonatomic) IBOutlet UILabel *bottomScanningLabel; -@property (weak, nonatomic) IBOutlet UITableView *beaconListTableView; -@property (weak, nonatomic) IBOutlet UIImageView *loadingImageView; -@property (weak, nonatomic) IBOutlet UIImageView *bottomScanningImageView; -@property (weak, nonatomic) IBOutlet UIButton *bottomScanningImageButton; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *footerHeightConstraint; -@property (strong, nonatomic) EddystoneScanner *eddystoneScanner; - -@property (strong, nonatomic) WYPopoverController *devicePopoverController; - -@property (assign, nonatomic) BOOL firstLayout; - -@end - -@implementation SILRetailBeaconAppViewController - -#pragma mark - UIViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.title = self.app.title; - self.firstLayout = YES; - self.bottomScanningLabel.text = kScanningForBeacons; - self.beaconRegistry = [[SILBeaconRegistry alloc] init]; - - [self startScanningImages]; - - self.centralManager = [[CBCentralManager alloc] initWithDelegate:self - queue:nil]; - self.eddystoneScanner = [[EddystoneScanner alloc] init]; - self.eddystoneScanner.delegate = self; - [self setUpBeaconMonitoring]; - [self setUpTable]; - [self setupAppNotifications]; -} - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self startTimers]; -} - -- (void)viewDidLayoutSubviews { - [super viewDidLayoutSubviews]; - BOOL deviceIsIPhoneX = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone && UIScreen.mainScreen.nativeBounds.size.height == 2436); - if (deviceIsIPhoneX && self.firstLayout) { - self.footerHeightConstraint.constant = 75; - self.firstLayout = NO; - } -} - -- (void)viewWillDisappear:(BOOL)animated { - [super viewWillDisappear:animated]; - - [self stopTimers]; - [self.navigationController popViewControllerAnimated:NO]; -} - -- (void)didMoveToParentViewController:(UIViewController *)parent { - if (![parent isEqual:self.parentViewController]) { - [self removeAppNotifications]; - } -} - -- (void)dealloc { - [self.reloadDataTimer invalidate]; -} - -#pragma mark - Set Up - -- (void)setUpBeaconMonitoring { - self.locationManager = [[CLLocationManager alloc] init]; - self.locationManager.delegate = self; - - [self.locationManager requestAlwaysAuthorization]; - - NSUUID *iBeaconUUID = [[NSUUID alloc] initWithUUIDString:kIBeaconUUIDString]; - NSUUID *iBeaconDMPUUID = [[NSUUID alloc] initWithUUIDString:kIBeaconDMPUUIDString]; - self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:iBeaconUUID major:kIBeaconMajorNumber minor:kIBeaconMinorNumber identifier:kIBeaconIdentifier]; - self.dmpZigbeeBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:iBeaconDMPUUID major:kIBeaconDMPZigbeeMajorNumber identifier:kIBeaconDMPZigbeeIdentifier]; - self.dmpProprietaryBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:iBeaconDMPUUID major:kIBeaconDMPProprietaryMajorNumber identifier:kIBeaconDMPProprietaryIdentifier]; - self.regions = @[self.beaconRegion, self.dmpZigbeeBeaconRegion, self.dmpProprietaryBeaconRegion]; - - for (CLBeaconRegion *beaconRegion in self.regions) { - [self.locationManager startRangingBeaconsInRegion:beaconRegion]; - } -} - -- (void)setUpTable { - NSString *beaconRegistryEntryCellClassString = NSStringFromClass([SILBeaconRegistryEntryCell class]); - [self.beaconListTableView registerNib:[UINib nibWithNibName:beaconRegistryEntryCellClassString bundle:nil] forCellReuseIdentifier:beaconRegistryEntryCellClassString]; - self.sizingRegistryEntryCell = [[[UINib nibWithNibName:beaconRegistryEntryCellClassString bundle:nil] instantiateWithOwner:nil options:nil] firstObject]; - - self.beaconListTableView.rowHeight = UITableViewAutomaticDimension; - self.beaconListTableView.estimatedRowHeight = kBeaconListTableViewCellRowHeight; -} - -- (void)setupAppNotifications { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; -} - -- (void)removeAppNotifications { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; -} - -- (void)appDidBecomeActive { - [self startScanningImages]; -} - -- (void)appDidEnterBackground { - [self pauseScanningImages]; -} - -#pragma mark - Configure - -- (SILBeaconRegistryEntryViewModel *)beaconRegistryEntryViewModelForEntry:(SILBeaconRegistryEntry *)entry { - return [[SILBeaconRegistryEntryViewModel alloc] initWithBeaconRegistryEntry:entry]; -} - -- (void)updateBeaconList { - [self.beaconListTableView reloadData]; -} - -- (SILBeaconRegistryEntryViewModel *)beaconRegistryEntryViewModelForIndexPath:(NSIndexPath *)indexPath { - NSInteger row = indexPath.row; - NSArray* entries = [self.beaconRegistry beaconRegistryEntries]; - if (row < entries.count) { - SILBeaconRegistryEntry *entry = entries[row]; - SILBeaconRegistryEntryViewModel *entryViewModel = [self beaconRegistryEntryViewModelForEntry:entry]; - return entryViewModel; - } - - return nil; -} - -- (void)configureCell:(SILBeaconRegistryEntryCell *)registryEntryCell forIndexPath:(NSIndexPath *)indexPath { - NSInteger row = indexPath.row; - if (row == 0) { - registryEntryCell.beaconSeparatorView.hidden = true; - } - SILBeaconRegistryEntryViewModel *entryViewModel = [self beaconRegistryEntryViewModelForIndexPath:indexPath]; - [registryEntryCell configureWithViewModel:entryViewModel]; - - [registryEntryCell setNeedsLayout]; - [registryEntryCell layoutIfNeeded]; -} - -- (void)reloadData { - BOOL beaconFound = [self.beaconRegistry beaconRegistryEntries].count > 0; - - if (beaconFound) { - if (self.isScanning) { - self.bottomScanningLabel.text = kAdditionalBeacons; - } - } - - [self updateBeaconList]; -} - -- (void)startScanningImages { - [UIView addContinuousRotationAnimationToLayer:self.bottomScanningImageView.layer withFullRotationDuration:2 forKey:@"rotationAnimation"]; -} - -- (void)pauseScanningImages { - [self.bottomScanningImageView.layer removeAllAnimations]; -} - -#pragma mark - ReloadDataTimer - -- (void)startTimers { - [self.reloadDataTimer invalidate]; - self.reloadDataTimer = [NSTimer scheduledTimerWithTimeInterval:SILRetailBeaconAppRefreshRate - target:self - selector:@selector(reloadData) - userInfo:nil - repeats:YES]; -} - -- (void)stopTimers { - [self.reloadDataTimer invalidate]; - self.reloadDataTimer = nil; -} - -#pragma mark - Scanning - -- (IBAction)didTapScanningToggleButton:(UIButton *)sender { - if (self.isScanning) { - [self stopScanning]; - [self pauseScanningImages]; - } else { - self.beaconRegistry = [[SILBeaconRegistry alloc] init]; - [self.beaconListTableView reloadData]; - [self startScanning]; - [self startScanningImages]; - } - self.bottomScanningLabel.text = self.isScanning ? kScanningForBeacons : kScanForNewBeacons; - self.bottomScanningImageView.alpha = self.isScanning ? 1.0 : 0.0; - NSString *imageString = self.isScanning ? @"cancelScanning" : @"startScanning"; - [self.bottomScanningImageButton setImage:[UIImage imageNamed:imageString] forState: UIControlStateNormal]; -} - -- (void)startScanning { - if (!self.isScanning) { - self.isScanning = YES; - [self.centralManager scanForPeripheralsWithServices:nil - options:@{ - CBCentralManagerScanOptionAllowDuplicatesKey : @YES, - }]; - [self.eddystoneScanner scanForEddystoneBeacons]; - } -} - -- (void)stopScanning { - if (self.isScanning) { - self.isScanning = NO; - [self.centralManager stopScan]; - [self.eddystoneScanner stopScanningForEddystoneBeacons]; - } -} - -#pragma mark - CBCentralManagerDelegate - -- (void)centralManagerDidUpdateState:(CBCentralManager *)central { - if (central.state == CBManagerStatePoweredOn) { - [self startScanning]; - } else { - [self stopScanning]; - } -} - -- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { - if (advertisementData[CBAdvertisementDataManufacturerDataKey] != nil) { - NSString *name = peripheral.name; - [self.beaconRegistry updateWithAdvertisment:advertisementData name:name RSSI:RSSI]; - } -} - -#pragma mark - CLLocationManagerDelegate - -- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region { - for (CLBeaconRegion *beaconRegion in self.regions) { - if ([beaconRegion isEqual:region]) { - [self.locationManager startRangingBeaconsInRegion:beaconRegion]; - } - } -} - -- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { - for (CLBeaconRegion *beaconRegion in self.regions) { - if ([beaconRegion isEqual:region]) { - [self.locationManager stopRangingBeaconsInRegion:beaconRegion]; - [self.beaconRegistry removeIBeaconEntriesWithUUID:beaconRegion.proximityUUID]; - } - } -} - -- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region { - for (CLBeacon *foundBeacon in beacons) { - [self.beaconRegistry updateWithIBeacon:foundBeacon]; - } -} - -#pragma mark - UITableViewDataSourceDelegate - --(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [self.beaconRegistry beaconRegistryEntries].count; -} - --(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - SILBeaconRegistryEntryCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([SILBeaconRegistryEntryCell class]) forIndexPath:indexPath]; - [self configureCell:cell forIndexPath:indexPath]; - return cell; -} - -#pragma mark - UITableViewDelegate - -//Included for compat with iOS7 -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - [self configureCell:self.sizingRegistryEntryCell forIndexPath:indexPath]; - return [self.sizingRegistryEntryCell autoLayoutHeight]; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - - SILBeaconRegistryEntryViewModel *entryViewModel = [self beaconRegistryEntryViewModelForIndexPath:indexPath]; - - SILRetailBeaconDetailsViewController *selectionViewController = [[SILRetailBeaconDetailsViewController alloc] init]; - selectionViewController.delegate = self; - selectionViewController.entryViewModel = entryViewModel; - self.devicePopoverController = [WYPopoverController sil_presentCenterPopoverWithContentViewController:selectionViewController - presentingViewController:self - delegate:self - animated:YES]; -} - -#pragma mark - EddystoneScannerDelegate - -- (void)eddystoneScanner:(EddystoneScanner *)eddystoneScanner didFindBeacons:(NSArray *)beacons { - for (EddystoneBeacon *foundBeacon in beacons) { - [self.beaconRegistry updateWithEddystoneBeacon:foundBeacon]; - } -} - -#pragma mark - SILRetailBeaconDetailsViewControllerDelegate - -- (void)didFinishHelpWithBeaconDetailsViewController:(SILRetailBeaconDetailsViewController *)beaconDetailsViewController { - [self.devicePopoverController dismissPopoverAnimated:YES completion:^{ - self.devicePopoverController = nil; - }]; -} - -#pragma mark - WYPopoverControllerDelegate - -- (void)popoverControllerDidDismissPopover:(WYPopoverController *)popoverController { - [self.devicePopoverController dismissPopoverAnimated:YES completion:nil]; - self.devicePopoverController = nil; -} - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.h deleted file mode 100644 index 33840653..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SILRetailBeaconDetailsHeaderView.h -// SiliconLabsApp -// -// Created by Max Litteral on 7/3/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import - -@interface SILRetailBeaconDetailsHeaderView : UITableViewHeaderFooterView -@property (weak, nonatomic) IBOutlet UILabel *headerLabel; -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.m deleted file mode 100644 index 8d8d7422..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsHeaderView.m +++ /dev/null @@ -1,18 +0,0 @@ -// -// SILRetailBeaconDetailsHeaderView.m -// SiliconLabsApp -// -// Created by Max Litteral on 7/3/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILRetailBeaconDetailsHeaderView.h" - -@interface SILRetailBeaconDetailsHeaderView() -@property (weak, nonatomic) IBOutlet UIView *contentBackground; -@property (weak, nonatomic) IBOutlet UIView *dividerView; -@end - -@implementation SILRetailBeaconDetailsHeaderView - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.h b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.h deleted file mode 100644 index 3aa9b6c8..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// SILRetailBeaconDetailsViewController.h -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import - -@class SILBeaconRegistryEntryViewModel; -@protocol SILRetailBeaconDetailsViewControllerDelegate; - -@interface SILRetailBeaconDetailsViewController : UIViewController - -@property (weak, nonatomic) id delegate; -@property (strong, nonatomic) SILBeaconRegistryEntryViewModel *entryViewModel; - -@end - -@protocol SILRetailBeaconDetailsViewControllerDelegate - -- (void)didFinishHelpWithBeaconDetailsViewController:(SILRetailBeaconDetailsViewController *)beaconDetailsViewController; - -@end diff --git a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.m b/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.m deleted file mode 100644 index 46eab727..00000000 --- a/SiliconLabsApp/ViewControllers/RetailBeaconApp/SILRetailBeaconDetailsViewController.m +++ /dev/null @@ -1,178 +0,0 @@ -// -// SILRetailBeaconDetailsViewController.m -// SiliconLabsApp -// -// Created by Max Litteral on 6/22/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILRetailBeaconDetailsViewController.h" -#import "SILBeaconRegistryEntryViewModel.h" -#import "SILDebugAdvDetailTableViewCell.h" -#import "SILBeaconDataModel.h" -#import "SILBeaconDataViewModel.h" -#import "SILRetailBeaconDetailsHeaderView.h" -#import "UIView+NibInitable.h" - -@interface SILRetailBeaconDetailsViewController () -@property (weak, nonatomic) IBOutlet UILabel *deviceNameLabel; -@property (weak, nonatomic) IBOutlet UILabel *deviceTypeLabel; -@property (weak, nonatomic) IBOutlet UILabel *uuidLabel; -@property (weak, nonatomic) IBOutlet UILabel *rssiValueLabel; -@property (weak, nonatomic) IBOutlet UILabel *txValueLabel; -@property (weak, nonatomic) IBOutlet UITableView *tableView; -@property (strong, nonatomic) NSArray *dataModels; -@property (strong, nonatomic) NSArray *tlmDataViewModels; -@end - -@implementation SILRetailBeaconDetailsViewController - -#pragma MARK: - Lifecycle - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.deviceNameLabel.text = self.entryViewModel.name; - self.deviceTypeLabel.text = self.entryViewModel.type; - self.uuidLabel.text = self.entryViewModel.entry.beacon.UUIDString.uppercaseString; - self.rssiValueLabel.text = self.entryViewModel.formattedRSSI; - self.txValueLabel.text = self.entryViewModel.formattedTx; - - [self setupTableView]; - - NSArray *beaconDataModels = [self beaconDataModels]; - NSMutableArray *tempBeaconDataViewModels = [[NSMutableArray alloc] init]; - for (SILBeaconDataModel *dataModel in beaconDataModels) { - [tempBeaconDataViewModels addObject:[[SILBeaconDataViewModel alloc] initWithBeaconDataModel:dataModel]]; - } - self.dataModels = tempBeaconDataViewModels; - - NSArray *tlmDataModels = [self tlmDataModels]; - NSMutableArray *tempTLMDataViewModels = [[NSMutableArray alloc] init]; - for (SILBeaconDataModel *dataModel in tlmDataModels) { - [tempTLMDataViewModels addObject:[[SILBeaconDataViewModel alloc] initWithBeaconDataModel:dataModel]]; - } - self.tlmDataViewModels = tempTLMDataViewModels; - - [self.tableView reloadData]; -} - -- (CGSize)preferredContentSize { - if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { - return CGSizeMake(540, 600); - } else { - return CGSizeMake(296, 496); - } -} - -#pragma mark - Setup - -- (void)setupTableView { - self.tableView.estimatedRowHeight = 44; - self.tableView.rowHeight = UITableViewAutomaticDimension; - self.tableView.sectionHeaderHeight = 40; - - NSString *cellClassString = NSStringFromClass([SILDebugAdvDetailTableViewCell class]); - [self.tableView registerNib:[UINib nibWithNibName:cellClassString bundle:nil] forCellReuseIdentifier:cellClassString]; -} - -- (NSArray *)beaconDataModels { - NSMutableArray *mutableAdvModels = [[NSMutableArray alloc] init]; - - if (self.entryViewModel.entry.beacon.type == SILBeaconTypeEddystone) { - SILBeaconDataModel *urlModel = [[SILBeaconDataModel alloc] initWithValue:self.entryViewModel.entry.beacon.url.absoluteString ?: @"Unknown" type:BeaconModelTypeURL]; - [mutableAdvModels addObject:urlModel]; - } - - SILBeaconDataModel *instanceModel = [[SILBeaconDataModel alloc] initWithValue:self.entryViewModel.entry.beacon.instance.uppercaseString ?: @"Unknown" type:BeaconModelTypeInstance]; - [mutableAdvModels addObject:instanceModel]; - - NSString *versionValue = [NSString stringWithFormat:@"%hu.%hu", self.entryViewModel.entry.beacon.major, self.entryViewModel.entry.beacon.minor]; - SILBeaconDataModel *versionModel = [[SILBeaconDataModel alloc] initWithValue:versionValue type:BeaconModelTypeVersion]; - [mutableAdvModels addObject:versionModel]; - - return mutableAdvModels; -} - -- (NSArray *)tlmDataModels { - NSMutableArray *mutableAdvModels = [[NSMutableArray alloc] init]; - TLMData *tlmData = self.entryViewModel.beaconViewModel.beacon.tlmData; - if (tlmData == nil) { - return nil; - } - - NSString *voltageValue = [NSString stringWithFormat:@"%f", tlmData.batteryVolts]; - SILBeaconDataModel *voltageModel = [[SILBeaconDataModel alloc] initWithValue:voltageValue type:BeaconModelTypeVoltage]; - [mutableAdvModels addObject:voltageModel]; - - NSString *temperatureValue = [NSString stringWithFormat:@"%f", tlmData.temperature]; - SILBeaconDataModel *temperatureModel = [[SILBeaconDataModel alloc] initWithValue:temperatureValue type:BeaconModelTypeTemperature]; - [mutableAdvModels addObject:temperatureModel]; - - NSString *advertisementCountValue = [NSString stringWithFormat:@"%li", (long)tlmData.advertisementCount]; - SILBeaconDataModel *advertisementCountModel = [[SILBeaconDataModel alloc] initWithValue:advertisementCountValue type:BeaconModelTypeAdvertisementCount]; - [mutableAdvModels addObject:advertisementCountModel]; - - NSString *onTimeValueValue = [NSString stringWithFormat:@"%f", tlmData.onTime]; - SILBeaconDataModel *onTimeModel = [[SILBeaconDataModel alloc] initWithValue:onTimeValueValue type:BeaconModelTypeOnTime]; - [mutableAdvModels addObject:onTimeModel]; - - return mutableAdvModels; -} - -#pragma MARK: - Actions - -- (IBAction)didTapOKButton:(id)sender { - [self.delegate didFinishHelpWithBeaconDetailsViewController:self]; -} - -#pragma MARK: - UITableViewDataSource - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - if (self.tlmDataViewModels != nil && [self.tlmDataViewModels count] != 0) { - return 2; - } - return 1; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section == 0) { - return [self.dataModels count]; - } else if (section == 1) { - return [self.tlmDataViewModels count]; - } - return 0; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - SILDebugAdvDetailTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([SILDebugAdvDetailTableViewCell class]) forIndexPath:indexPath]; - SILBeaconDataViewModel *dataModel; - if (indexPath.section == 0) { - dataModel = self.dataModels[indexPath.row]; - } else if (indexPath.section == 1) { - dataModel = self.tlmDataViewModels[indexPath.row]; - } - cell.detailTypeLabel.text = dataModel.typeString; - cell.detailValueLabel.text = dataModel.valueString; - return cell; -} - -#pragma MARK: - UITableViewDelegate - -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - if (section == 1) { - SILRetailBeaconDetailsHeaderView *headerView = (SILRetailBeaconDetailsHeaderView *)[self.view initWithNibNamed:NSStringFromClass([SILRetailBeaconDetailsHeaderView class])]; - headerView.headerLabel.text = @"TLM DATA"; - return headerView; - } - return nil; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == 0) { - return 0; - } - return 40; -} - -@end diff --git a/SiliconLabsApp/ViewControllers/Throughput/SILThroughputViewController.swift b/SiliconLabsApp/ViewControllers/Throughput/SILThroughputViewController.swift index d9633366..657570bc 100644 --- a/SiliconLabsApp/ViewControllers/Throughput/SILThroughputViewController.swift +++ b/SiliconLabsApp/ViewControllers/Throughput/SILThroughputViewController.swift @@ -63,9 +63,9 @@ class SILThroughputViewController: UIViewController, UIGestureRecognizerDelegate self.navigationController?.interactivePopGestureRecognizer?.delegate = self } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - viewModel.viewWillDisappear() + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + viewModel.unregisterAndStopTests() } private func addShadowForOptionsView() { @@ -148,7 +148,7 @@ class SILThroughputViewController: UIViewController, UIGestureRecognizerDelegate let peripheralConnectionStatus = viewModel.peripheralConnectionStatus.observe( { value in guard let weakSelf = weakSelf else { return } if !value { - weakSelf.viewModel.viewWillDisappear() + weakSelf.viewModel.unregisterAndStopTests() SVProgressHUD.dismiss() weakSelf.navigationController?.popToRootViewController(animated: true) } @@ -255,13 +255,11 @@ class SILThroughputViewController: UIViewController, UIGestureRecognizerDelegate } func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - backToHomeScreenActions() - return false + return true } private func backToHomeScreenActions() { - centralManager.disconnect(from: connectedPeripheral) - viewModel.viewWillDisappear() + viewModel.unregisterAndStopTests() self.navigationController?.popToRootViewController(animated: true) } @@ -272,7 +270,7 @@ class SILThroughputViewController: UIViewController, UIGestureRecognizerDelegate private func showBluetoothDisabledAlert() { let bluetoothDisabledAlert = SILBluetoothDisabledAlert.throughput self.alertWithOKButton(title: bluetoothDisabledAlert.title, message: bluetoothDisabledAlert.message, completion: { _ in - self.viewModel.viewWillDisappear() + self.viewModel.unregisterAndStopTests() self.navigationController?.popViewController(animated: true) }) } diff --git a/SiliconLabsApp/ViewModels/Advertiser/Home/RemoveWarning/SILAdvertiserRemoveWarningViewModel.swift b/SiliconLabsApp/ViewModels/Advertiser/Home/RemoveWarning/SILAdvertiserRemoveWarningViewModel.swift deleted file mode 100644 index e0e8d8b4..00000000 --- a/SiliconLabsApp/ViewModels/Advertiser/Home/RemoveWarning/SILAdvertiserRemoveWarningViewModel.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// SILAdvertiserRemoveWarningViewModel.swift -// BlueGecko -// -// Created by Grzegorz Janosz on 27/10/2020. -// Copyright © 2020 SiliconLabs. All rights reserved. -// - -class SILAdvertiserRemoveWarningViewModel { - - var wireframe: SILAdvertiserHomeWireframe! - var confirmAction: (() -> ()) - - init(wireframe: SILAdvertiserHomeWireframe, confirmAction: @escaping () -> ()) { - self.wireframe = wireframe - self.confirmAction = confirmAction - } - - func onConfirm(with switchState: Bool) { - confirmAction() - SILAdvertiserRemoveSetting.setDisplayAdvertiserRemoveWarning(value: switchState) - wireframe.dismissPopover() - } - - func onCancel() { - wireframe.dismissPopover() - } - -} diff --git a/SiliconLabsApp/ViewModels/Blinky/SILBlinkyViewModel.swift b/SiliconLabsApp/ViewModels/Blinky/SILBlinkyViewModel.swift index 65a96bf3..6e3f1276 100644 --- a/SiliconLabsApp/ViewModels/Blinky/SILBlinkyViewModel.swift +++ b/SiliconLabsApp/ViewModels/Blinky/SILBlinkyViewModel.swift @@ -121,7 +121,7 @@ class SILBlinkyViewModel { self.peripheralDelegate.discoverBlinkyService() } - public func viewWillDisappear() { + public func removeObserverAndDisconnect() { NotificationCenter.default.removeObserver(self) deviceConnector.disconnectAllDevices() } diff --git a/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift b/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift index 28caf041..1a7b8fae 100644 --- a/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift +++ b/SiliconLabsApp/ViewModels/DebugDeviceViewModel.swift @@ -307,7 +307,7 @@ final class DebugDeviceViewModel: NSObject { private func filterByCurrentMinRSSI() { if let currentMinRSSI = currentMinRSSI { - let rssiPredicate = NSPredicate(format: "discoveredPeripheral.RSSIMeasurementTable.lastRSSIMeasurement.intValue > \(currentMinRSSI)") + let rssiPredicate = NSPredicate(format: "discoveredPeripheral.rssiMeasurementTable.lastRSSIMeasurement.intValue > \(currentMinRSSI)") discoveredPeripheralsViewModels = discoveredPeripheralsViewModels.filter { rssiPredicate.evaluate(with: $0) } } } diff --git a/SiliconLabsApp/ViewModels/GattConfigurator/Home/SILGattConfiguratorHomeViewModel.swift b/SiliconLabsApp/ViewModels/GattConfigurator/Home/SILGattConfiguratorHomeViewModel.swift index de584a34..23f1f5af 100644 --- a/SiliconLabsApp/ViewModels/GattConfigurator/Home/SILGattConfiguratorHomeViewModel.swift +++ b/SiliconLabsApp/ViewModels/GattConfigurator/Home/SILGattConfiguratorHomeViewModel.swift @@ -32,7 +32,7 @@ class SILGattConfiguratorHomeViewModel { let isExportModeOn: SILObservable = SILObservable(initialValue: false) let isMenuEnabled: SILObservable = SILObservable(initialValue: true) - let fileWriter = SILGattConfiguratorFileWriter() + let fileWriter = SILFileWriter(exportDirName: "SILGattConfiguratorExport") private var isExportModeTurnOn: Bool = false { didSet { diff --git a/SiliconLabsApp/ViewModels/Popup/Warning/SILWarningViewModel.swift b/SiliconLabsApp/ViewModels/Popup/Warning/SILWarningViewModel.swift index 572b68d8..6c894804 100644 --- a/SiliconLabsApp/ViewModels/Popup/Warning/SILWarningViewModel.swift +++ b/SiliconLabsApp/ViewModels/Popup/Warning/SILWarningViewModel.swift @@ -41,8 +41,9 @@ class SILWarningViewModel { wireframe.dismissPopover() } - func onCancel() { + func onCancel(with switchState: Bool) { cancelAction() + setSettingAction(switchState) wireframe.dismissPopover() } diff --git a/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.h b/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.h deleted file mode 100644 index 2550a704..00000000 --- a/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SILAltBeaconViewModel.h -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconViewModel.h" - -@interface SILAltBeaconViewModel : SILBeaconViewModel - -@end diff --git a/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.m b/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.m deleted file mode 100644 index 9f974d9b..00000000 --- a/SiliconLabsApp/ViewModels/SILAltBeaconViewModel.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// SILAltBeaconViewModel.m -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILAltBeaconViewModel.h" -#import "SILBeacon.h" -#import "SILDoubleKeyDictionaryPair.h" - -@implementation SILAltBeaconViewModel - -- (NSString *)imageName { - return SILImageNameBeaconTypeAltBeacon; -} - -- (NSString *)type { - return @"AltBeacon"; -} - -- (NSNumber *)rssi { - return @(self.beacon.calibrationPower); -} - -- (NSNumber *)tx { - return self.beacon.txPower; -} - -- (SILDoubleKeyDictionaryPair *)beaconDetails { - SILDoubleKeyDictionaryPair *orderedDetails = [[SILDoubleKeyDictionaryPair alloc] init]; - //ids are added as a way to order the entries - [orderedDetails addObject:self.beacon.UUIDString nameKey:@"BEACON ID" idKey:@(1)]; - [orderedDetails addObject:@"0x0047" nameKey:@"MANUFACTURER ID" idKey:@(2)]; - [orderedDetails addObject:[self.beacon.refRSSI stringValue] nameKey:@"REFERENCE RSSI" idKey:@(3)]; - return orderedDetails; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.h b/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.h deleted file mode 100644 index a4e744c1..00000000 --- a/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SILBGBeaconViewModel.h -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconViewModel.h" - -@interface SILBGBeaconViewModel : SILBeaconViewModel - -@end diff --git a/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.m b/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.m deleted file mode 100644 index 1544dd77..00000000 --- a/SiliconLabsApp/ViewModels/SILBGBeaconViewModel.m +++ /dev/null @@ -1,37 +0,0 @@ -// -// SILBGBeaconViewModel.m -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBGBeaconViewModel.h" -#import "SILBeacon.h" -#import "SILDoubleKeyDictionaryPair.h" - -@implementation SILBGBeaconViewModel - -- (NSString *)imageName { - return SILImageNameBeaconTypeBlueGecko; -} - -- (NSString *)type { - return @"Blue Gecko"; -} - -- (NSNumber *)rssi { - return @(self.beacon.calibrationPower); -} - -- (NSNumber *)tx { - return self.beacon.txPower; -} - -- (SILDoubleKeyDictionaryPair *)beaconDetails { - SILDoubleKeyDictionaryPair *orderedDetails = [[SILDoubleKeyDictionaryPair alloc] init]; - [orderedDetails addObject:self.beacon.UUIDString nameKey:@"UUID" idKey:@(1)]; - return orderedDetails; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.h b/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.h deleted file mode 100644 index 8cb5c60d..00000000 --- a/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// SILBeaconRegistryEntryViewModel.h -// SiliconLabsApp -// -// Created by Bob Gilmore on 3/16/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILBeacon.h" -#import "SILBeaconRegistryEntry.h" -#import "SILBeaconViewModel.h" -#import "SILProximityCalculator.h" -#import "SILRSSIMeasurementTable.h" - -#ifndef SILBeaconRegistryEntryViewModel_h -#define SILBeaconRegistryEntryViewModel_h - -@interface SILBeaconRegistryEntryViewModel : NSObject -@property (strong, nonatomic, readonly) SILBeaconRegistryEntry *entry; -@property (strong, nonatomic, readonly) SILBeaconViewModel *beaconViewModel; -@property (strong, nonatomic, readonly) NSString *name; -@property (strong, nonatomic, readonly) NSString *imageName; -@property (strong, nonatomic, readonly) UIImage *image; -@property (strong, nonatomic, readonly) NSString *type; -@property (strong, nonatomic, readonly) UIImage *distanceImage; -@property (strong, nonatomic, readonly) NSString *distanceName; -@property (strong, nonatomic, readonly) NSString *formattedRSSI; -@property (strong, nonatomic, readonly) NSString *formattedTx; - -- (instancetype)initWithBeaconRegistryEntry:(SILBeaconRegistryEntry *)entry; - -@end - -#endif /* SILBeaconRegistryEntryViewModel_h */ diff --git a/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.m b/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.m deleted file mode 100644 index fa01dd6d..00000000 --- a/SiliconLabsApp/ViewModels/SILBeaconRegistryEntryViewModel.m +++ /dev/null @@ -1,116 +0,0 @@ -// -// SILBeaconRegistryEntryViewModel.m -// SiliconLabsApp -// -// Created by Bob Gilmore on 3/16/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import -#import "SILBeaconRegistryEntryViewModel.h" -#import "SILBGBeaconViewModel.h" -#import "SILIBeaconViewModel.h" -#import "SILAltBeaconViewModel.h" -#import "SILEddystoneBeaconViewModel.h" - -CGFloat const SILRetailBeaconAppDistanceInterval = 5.0; - -@class SILDoubleKeyDictionaryPair; - -@interface SILBeaconRegistryEntryViewModel() -@property (strong, nonatomic, readwrite) SILBeaconRegistryEntry *entry; -@property (strong, nonatomic, readwrite) SILBeaconViewModel *beaconViewModel; -@end - -@implementation SILBeaconRegistryEntryViewModel - -- (instancetype)initWithBeaconRegistryEntry:(SILBeaconRegistryEntry *)entry { - self = [super init]; - if (self) { - _entry = entry; - _beaconViewModel = [self beaconViewModelForBeacon:entry.beacon]; - } - return self; -} - -- (SILBeaconViewModel *)beaconViewModelForBeacon:(SILBeacon *)beacon { - SILBeaconViewModel *viewModel = nil; - switch (beacon.type) { - case SILBeaconTypeIBeacon: - viewModel = [[SILIBeaconViewModel alloc] initWithBeacon:beacon]; - break; - case SILBeaconTypeAltBeacon: - viewModel = [[SILAltBeaconViewModel alloc] initWithBeacon:beacon]; - break; - case SILBeaconTypeEddystone: - viewModel = [[SILEddystoneBeaconViewModel alloc] initWithBeacon:beacon]; - break; - default: - break; - } - return viewModel; -} - -- (NSString *)name { - return _beaconViewModel.name; -} - -- (NSString *)imageName { - return _beaconViewModel.imageName; -} - -- (UIImage *)image { - return [UIImage imageNamed:[self imageName]]; -} - -- (NSString *)type { - return _beaconViewModel.type; -} - -- (NSNumber *)rssi { - return _beaconViewModel.rssi; -} - -- (NSNumber *)lastRSSI { - return [_entry.RSSIMeasurementTable lastRSSIMeasurement]; -} - -- (SILBeaconProximity)proximity { - return _entry.beacon.beacon ? - [SILProximityCalculator proximityWithBeacon:_entry.beacon.beacon] : - [SILProximityCalculator estimatedProximityWithRSSI:[self lastRSSI] - calibrationPower:_entry.beacon.txPower]; -} - -- (UIImage *)distanceImage { - UIImage* image = nil; - switch ([self proximity]) { - case SILBeaconProximityImmediate: - image = [UIImage imageNamed:SILImageNameBeaconRangeImmediate]; - break; - case SILBeaconProximityNear: - image = [UIImage imageNamed:SILImageNameBeaconRangeNear]; - break; - case SILBeaconProximityFar: - image = [UIImage imageNamed:SILImageNameBeaconRangeFar]; - default: - break; - } - return image; -} - -- (NSString *)distanceName { - return [SILBeaconProximityDisplayName([self proximity]) uppercaseString]; -} - -- (NSString *)formattedRSSI { - int RSSI = [[self lastRSSI] intValue]; - return [NSString stringWithFormat:@"%i", RSSI]; -} - -- (NSString *)formattedTx { - int tx = [_beaconViewModel.tx intValue]; - return [NSString stringWithFormat:@"%i", tx]; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILBeaconViewModel.h b/SiliconLabsApp/ViewModels/SILBeaconViewModel.h deleted file mode 100644 index 8cb1ff4c..00000000 --- a/SiliconLabsApp/ViewModels/SILBeaconViewModel.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// SILBeaconViewModel.h -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import -#import "SILBeacon.h" -#import "UIImage+SILImages.h" -@class SILBeacon, SILDoubleKeyDictionaryPair; - -// "ABSTRACT" BASE CLASS // -@interface SILBeaconViewModel : NSObject -@property (strong, nonatomic, readonly) SILBeacon *beacon; -@property (strong, nonatomic, readonly) NSString *name; -@property (strong, nonatomic, readonly) NSString *imageName; -@property (strong, nonatomic, readonly) UIImage *image; -@property (strong, nonatomic, readonly) NSString *type; -@property (strong, nonatomic, readonly) NSNumber *rssi; -@property (strong, nonatomic, readonly) NSNumber *tx; -@property (strong, nonatomic, readonly) SILDoubleKeyDictionaryPair *beaconDetails; - -- (instancetype)initWithBeacon:(SILBeacon *)beacon; - -@end diff --git a/SiliconLabsApp/ViewModels/SILBeaconViewModel.m b/SiliconLabsApp/ViewModels/SILBeaconViewModel.m deleted file mode 100644 index b39ee86a..00000000 --- a/SiliconLabsApp/ViewModels/SILBeaconViewModel.m +++ /dev/null @@ -1,59 +0,0 @@ -// -// SILBeaconViewModelBase.m -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconViewModel.h" -@class SILDoubleKeyDictionaryPair; - -@interface SILBeaconViewModel() -@property (strong, nonatomic, readwrite) SILBeacon *beacon; -@end - -@implementation SILBeaconViewModel - -- (instancetype)initWithBeacon:(SILBeacon *)beacon { - self = [super init]; - if (self) { - self.beacon = beacon; - } - return self; -} - -- (NSString *)name { - return self.beacon.name; -} - -- (NSString *)imageName { - NSAssert(NO, @"Implement imageName in child class"); - return nil; -} - -- (UIImage *)image { - return [UIImage imageNamed:[self imageName]]; -} - -- (NSString *)type { - NSAssert(NO, @"Implement type in child class"); - return nil; -} - -- (NSNumber *)rssi { - NSAssert(NO, @"Implement rssi in child class"); - return nil; -} - -- (NSNumber *)tx { - NSAssert(NO, @"Implement tx in child class"); - return nil; -} - -- (SILDoubleKeyDictionaryPair *)beaconDetails { - NSAssert(NO, @"Implement beaconDetails in child class"); - return nil; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h index 7ceefebf..f09c1817 100644 --- a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h +++ b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.h @@ -7,7 +7,6 @@ // #import -#import "SILDiscoveredPeripheral.h" #import "SILApp.h" @interface SILDeviceSelectionViewModel : NSObject diff --git a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m index 915211ef..ea92d0a9 100644 --- a/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m +++ b/SiliconLabsApp/ViewModels/SILDeviceSelectionViewModel.m @@ -8,7 +8,6 @@ #import "SILDeviceSelectionViewModel.h" #import "SILApp+AttributedProfiles.h" -#import "SILRSSIMeasurementTable.h" CGFloat const SILDeviceSelectionViewModelRSSIThreshold = 1.0; diff --git a/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.h b/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.h deleted file mode 100644 index 6e583a8b..00000000 --- a/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SILEddystoneBeaconViewModel.h -// SiliconLabsApp -// -// Created by Nicholas Servidio on 2/23/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILBeaconViewModel.h" - -@interface SILEddystoneBeaconViewModel : SILBeaconViewModel - -@end diff --git a/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.m b/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.m deleted file mode 100644 index 5ee6d888..00000000 --- a/SiliconLabsApp/ViewModels/SILEddystoneBeaconViewModel.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// SILEddystoneBeaconViewModel.m -// SiliconLabsApp -// -// Created by Nicholas Servidio on 2/23/17. -// Copyright © 2017 SiliconLabs. All rights reserved. -// - -#import "SILEddystoneBeaconViewModel.h" -#import "SILBeacon.h" -#import "SILDoubleKeyDictionaryPair.h" - -@implementation SILEddystoneBeaconViewModel - -- (NSString *)imageName { - return SILImageNameBeaconTypeEddystone; -} - -- (NSString *)type { - return @"Eddystone"; -} - -- (NSNumber *)rssi { - return @(self.beacon.calibrationPower); -} - -- (NSNumber *)tx { - return self.beacon.txPower; -} - -- (SILDoubleKeyDictionaryPair *)beaconDetails { - SILDoubleKeyDictionaryPair *orderedDetails = [[SILDoubleKeyDictionaryPair alloc] init]; - //ids are added as a way to order the entries - [orderedDetails addObject:self.beacon.beaconNamespace nameKey:@"NAMESPACE" idKey:@(1)]; - [orderedDetails addObject:self.beacon.instance nameKey:@"INSTANCE" idKey:@(2)]; - [orderedDetails addObject:self.beacon.url.absoluteString nameKey:@"URL" idKey:@(3)]; - return orderedDetails; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILIBeaconViewModel.h b/SiliconLabsApp/ViewModels/SILIBeaconViewModel.h deleted file mode 100644 index 12fb1923..00000000 --- a/SiliconLabsApp/ViewModels/SILIBeaconViewModel.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// SILIBeaconViewModel.h -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILBeaconViewModel.h" - -@interface SILIBeaconViewModel : SILBeaconViewModel - -@end diff --git a/SiliconLabsApp/ViewModels/SILIBeaconViewModel.m b/SiliconLabsApp/ViewModels/SILIBeaconViewModel.m deleted file mode 100644 index f29a87b2..00000000 --- a/SiliconLabsApp/ViewModels/SILIBeaconViewModel.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// SILIBeaconViewModel.m -// SiliconLabsApp -// -// Created by Eric Peterson on 11/18/15. -// Copyright © 2015 SiliconLabs. All rights reserved. -// - -#import "SILIBeaconViewModel.h" -#import "SILBeacon.h" -#import "SILDoubleKeyDictionaryPair.h" - -@implementation SILIBeaconViewModel - -- (NSString *)imageName { - return SILImageNameBeaconTypeIBeacon; -} - -- (NSString *)type { - return @"iBeacon"; -} - -- (NSNumber *)rssi { - return @(self.beacon.beacon.rssi); -} - -- (NSNumber *)tx { - return self.beacon.txPower; -} - -- (SILDoubleKeyDictionaryPair *)beaconDetails { - SILDoubleKeyDictionaryPair *orderedDetails = [[SILDoubleKeyDictionaryPair alloc] init]; - //ids are added as a way to order the entries - [orderedDetails addObject:self.beacon.UUIDString nameKey:@"UUID" idKey:@(1)]; - [orderedDetails addObject:[@(self.beacon.major) stringValue] nameKey:@"MAJOR NUMBER" idKey:@(2)]; - [orderedDetails addObject:[@(self.beacon.minor) stringValue] nameKey:@"MINOR NUMBER" idKey:@(3)]; - return orderedDetails; -} - -@end diff --git a/SiliconLabsApp/ViewModels/SILOTAHUDPeripheralViewModel.m b/SiliconLabsApp/ViewModels/SILOTAHUDPeripheralViewModel.m index ea6d2ad4..e7f25b40 100644 --- a/SiliconLabsApp/ViewModels/SILOTAHUDPeripheralViewModel.m +++ b/SiliconLabsApp/ViewModels/SILOTAHUDPeripheralViewModel.m @@ -8,7 +8,6 @@ #import -#import "SILDiscoveredPeripheral.h" #import "SILOTAHUDPeripheralViewModel.h" #import "SILOTAFirmwareUpdateManager.h" diff --git a/SiliconLabsApp/ViewModels/SILOTAProgressViewModel.h b/SiliconLabsApp/ViewModels/SILOTAProgressViewModel.h index 5e5be7ce..759087e7 100644 --- a/SiliconLabsApp/ViewModels/SILOTAProgressViewModel.h +++ b/SiliconLabsApp/ViewModels/SILOTAProgressViewModel.h @@ -7,13 +7,10 @@ // #import "SILBeacon.h" -#import "SILBeaconRegistryEntry.h" -#import "SILBeaconViewModel.h" #import "SILCentralManager.h" #import "SILOTAFirmwareFile.h" #import "SILOTAHUDPeripheralViewModel.h" #import "SILProximityCalculator.h" -#import "SILRSSIMeasurementTable.h" #import "CBPeripheral+Services.h" #ifndef SILOTAProgressViewModel_h diff --git a/SiliconLabsApp/ViewModels/Throughput/SILThroughputViewModel.swift b/SiliconLabsApp/ViewModels/Throughput/SILThroughputViewModel.swift index 83fc1801..abf22ddd 100644 --- a/SiliconLabsApp/ViewModels/Throughput/SILThroughputViewModel.swift +++ b/SiliconLabsApp/ViewModels/Throughput/SILThroughputViewModel.swift @@ -55,7 +55,7 @@ protocol SILThroughputViewModelType { func changeTestState() func changePhoneTestModeSelection(newSelection: SILThroughputPhoneTestMode) func viewDidLoad() - func viewWillDisappear() + func unregisterAndStopTests() } class SILThroughputViewModel: SILThroughputViewModelType { @@ -169,11 +169,12 @@ class SILThroughputViewModel: SILThroughputViewModelType { peripheralDelegate.discoverThroughputGATTServices() } - func viewWillDisappear() { + func unregisterAndStopTests() { unregisterNotifications() peripheralDelegate.stopTesting() peripheralManager.stopTest() peripheralManager.stopAdvertising() + centralManager.disconnect(from: connectedPeripheral) } private func readConnectionParameters() {