diff options
Diffstat (limited to 'Example Apps/Example Swift')
-rw-r--r-- | Example Apps/Example Swift/MenuManager.swift | 58 | ||||
-rw-r--r-- | Example Apps/Example Swift/ProxyManager.swift | 25 | ||||
-rw-r--r-- | Example Apps/Example Swift/RemoteControlManager.swift | 226 |
3 files changed, 300 insertions, 9 deletions
diff --git a/Example Apps/Example Swift/MenuManager.swift b/Example Apps/Example Swift/MenuManager.swift index 092670559..b35af48aa 100644 --- a/Example Apps/Example Swift/MenuManager.swift +++ b/Example Apps/Example Swift/MenuManager.swift @@ -15,9 +15,10 @@ class MenuManager: NSObject { /// /// - Parameter manager: The SDL Manager /// - Returns: An array of SDLAddCommand objects - class func allMenuItems(with manager: SDLManager, choiceSetManager: PerformInteractionManager) -> [SDLMenuCell] { + class func allMenuItems(with manager: SDLManager, choiceSetManager: PerformInteractionManager, remoteManager: RemoteControlManager) -> [SDLMenuCell] { return [menuCellSpeakName(with: manager), menuCellGetAllVehicleData(with: manager), + menuCellRemoteControl(with: manager, remoteManager: remoteManager), menuCellShowPerformInteraction(with: manager, choiceSetManager: choiceSetManager), sliderMenuCell(with: manager), scrollableMessageMenuCell(with: manager), @@ -117,11 +118,11 @@ private extension MenuManager { /// - Returns: A SDLMenuCell object class func menuCellChangeTemplate(with manager: SDLManager) -> SDLMenuCell { - /// Lets give an example of 2 templates + // Lets give an example of 2 templates var submenuItems = [SDLMenuCell]() let errorMessage = "Changing the template failed" - /// Non-Media + // Non-Media let submenuTitleNonMedia = "Non - Media (Default)" submenuItems.append(SDLMenuCell(title: submenuTitleNonMedia, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .nonMedia)) { err in @@ -132,7 +133,7 @@ private extension MenuManager { } })) - /// Graphic with Text + // Graphic with Text let submenuTitleGraphicText = "Graphic With Text" submenuItems.append(SDLMenuCell(title: submenuTitleGraphicText, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .graphicWithText)) { err in @@ -206,6 +207,55 @@ private extension MenuManager { }) }) } + + /// Menu item that shows remote control example + /// + /// - Parameters: + /// - manager: The SDL Manager + /// - remoteManager: The manager for controling and viewing remote control data + /// - Returns: A SDLMenuCell object + class func menuCellRemoteControl(with manager: SDLManager, remoteManager: RemoteControlManager) -> SDLMenuCell { + let remoteControlIcon = SDLArtwork(image: UIImage(named: RemoteControlIconName)!.withRenderingMode(.alwaysTemplate), persistent: true, as: .PNG) + + // Clicking on cell shows alert message when remote control permissions are disabled + guard remoteManager.isEnabled else { + return SDLMenuCell(title: ACRemoteMenuName, secondaryText: nil, tertiaryText: nil, icon: remoteControlIcon, secondaryArtwork: nil, voiceCommands: nil, handler: { _ in + AlertManager.sendAlert(textField1: AlertRemoteControlNotEnabledWarningText, sdlManager: manager) + }) + } + + var submenuItems = [SDLMenuCell]() + // Climate Control Menu + submenuItems.append(SDLMenuCell(title: ACRemoteControlClimateMenuName, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in + manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .tilesOnly)) { err in + if let error = err { + AlertManager.sendAlert(textField1: error.localizedDescription, sdlManager: manager) + return + } + remoteManager.showClimateControl() + } + })) + + // View Climate Data + submenuItems.append(SDLMenuCell(title: ACRemoteViewClimateMenuName, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { _ in + let climateDataMessage = SDLScrollableMessage(message: remoteManager.climateDataString) + manager.send(request: climateDataMessage, responseHandler: { (request, response, error) in + guard let response = response else { return } + guard response.resultCode == .success else { + if response.resultCode == .timedOut { + AlertManager.sendAlert(textField1: AlertScrollableMessageTimedOutWarningText, sdlManager: manager) + } else if response.resultCode == .aborted { + AlertManager.sendAlert(textField1: AlertScrollableMessageCancelledWarningText, sdlManager: manager) + } else { + AlertManager.sendAlert(textField1: AlertScrollableMessageGeneralWarningText, sdlManager: manager) + } + return + } + }) + })) + + return SDLMenuCell(title: ACRemoteMenuName, secondaryText: nil, tertiaryText: nil, icon: remoteControlIcon, secondaryArtwork: nil, submenuLayout: .list, subCells: submenuItems) + } } // MARK: - Menu Voice Commands diff --git a/Example Apps/Example Swift/ProxyManager.swift b/Example Apps/Example Swift/ProxyManager.swift index efd364784..9a9d10140 100644 --- a/Example Apps/Example Swift/ProxyManager.swift +++ b/Example Apps/Example Swift/ProxyManager.swift @@ -26,7 +26,9 @@ class ProxyManager: NSObject { private var subscribeButtonManager: SubscribeButtonManager! private var vehicleDataManager: VehicleDataManager! private var performInteractionManager: PerformInteractionManager! + private var remoteControlManager: RemoteControlManager! private var firstHMILevelState: SDLHMILevel + private var isRemoteControlEnabled: Bool weak var delegate: ProxyManagerDelegate? @@ -34,6 +36,7 @@ class ProxyManager: NSObject { static let sharedManager = ProxyManager() private override init() { firstHMILevelState = .none + isRemoteControlEnabled = false; super.init() } } @@ -48,6 +51,7 @@ extension ProxyManager { delegate?.didChangeProxyState(ProxyState.searching) sdlManager = SDLManager(configuration: (proxyTransportType == .iap) ? ProxyManager.iapConfiguration : ProxyManager.tcpConfiguration, delegate: self) + self.isRemoteControlEnabled = (proxyTransportType == .tcp) startManager() } @@ -74,7 +78,7 @@ private extension ProxyManager { /// - Returns: A SDLConfiguration object class var iapConfiguration: SDLConfiguration { let lifecycleConfiguration = SDLLifecycleConfiguration(appName: ExampleAppName, fullAppId: ExampleFullAppId) - return setupManagerConfiguration(with: lifecycleConfiguration) + return setupManagerConfiguration(with: lifecycleConfiguration, enableRemote: false) } /// Configures a TCP transport layer with the IP address and port of the remote SDL Core instance. @@ -82,18 +86,25 @@ private extension ProxyManager { /// - Returns: A SDLConfiguration object class var tcpConfiguration: SDLConfiguration { let lifecycleConfiguration = SDLLifecycleConfiguration(appName: ExampleAppName, fullAppId: ExampleFullAppId, ipAddress: AppUserDefaults.shared.ipAddress!, port: UInt16(AppUserDefaults.shared.port!)!) - return setupManagerConfiguration(with: lifecycleConfiguration) + return setupManagerConfiguration(with: lifecycleConfiguration, enableRemote: true) } /// Helper method for setting additional configuration parameters for both TCP and iAP transport layers. /// /// - Parameter lifecycleConfiguration: The transport layer configuration /// - Returns: A SDLConfiguration object - class func setupManagerConfiguration(with lifecycleConfiguration: SDLLifecycleConfiguration) -> SDLConfiguration { + class func setupManagerConfiguration(with lifecycleConfiguration: SDLLifecycleConfiguration, enableRemote: Bool) -> SDLConfiguration { lifecycleConfiguration.shortAppName = ExampleAppNameShort let appIcon = UIImage(named: ExampleAppLogoName)?.withRenderingMode(.alwaysOriginal) lifecycleConfiguration.appIcon = appIcon != nil ? SDLArtwork(image: appIcon!, persistent: true, as: .PNG) : nil lifecycleConfiguration.appType = .default + + // On actual hardware, the app requires permissions to do remote control which this example app will not have. + // Only use the remote control type on the TCP connection. + if enableRemote { + lifecycleConfiguration.additionalAppTypes = [.remoteControl] + } + lifecycleConfiguration.language = .enUs lifecycleConfiguration.languagesSupported = [.enUs, .esMx, .frCa] lifecycleConfiguration.ttsName = [SDLTTSChunk(text: "S D L", type: .text)] @@ -114,7 +125,7 @@ private extension ProxyManager { /// - Returns: A SDLLogConfiguration object class func logConfiguration() -> SDLLogConfiguration { let logConfig = SDLLogConfiguration.default() - let exampleLogFileModule = SDLLogFileModule(name: "SDL Swift Example App", files: ["ProxyManager", "AlertManager", "AudioManager", "ButtonManager", "SubscribeButtonManager", "MenuManager", "PerformInteractionManager", "RPCPermissionsManager", "VehicleDataManager"]) + let exampleLogFileModule = SDLLogFileModule(name: "SDL Swift Example App", files: ["ProxyManager", "AlertManager", "AudioManager", "ButtonManager", "SubscribeButtonManager", "MenuManager", "PerformInteractionManager", "RPCPermissionsManager", "VehicleDataManager", "RemoteControlManager"]) logConfig.modules.insert(exampleLogFileModule) _ = logConfig.targets.insert(SDLLogTargetFile()) // Logs to file logConfig.globalLogLevel = .debug // Filters the logs @@ -136,6 +147,7 @@ private extension ProxyManager { self.subscribeButtonManager = SubscribeButtonManager(sdlManager: self.sdlManager) self.vehicleDataManager = VehicleDataManager(sdlManager: self.sdlManager, refreshUIHandler: self.refreshUIHandler) self.performInteractionManager = PerformInteractionManager(sdlManager: self.sdlManager) + self.remoteControlManager = RemoteControlManager(sdlManager: self.sdlManager, enabled: self.isRemoteControlEnabled, homeButtons: self.buttonManager.allScreenSoftButtons()) RPCPermissionsManager.setupPermissionsCallbacks(with: self.sdlManager) @@ -169,6 +181,9 @@ extension ProxyManager: SDLManagerDelegate { // Subscribe to vehicle data. vehicleDataManager.subscribeToVehicleOdometer() + // Start Remote Control Connection + remoteControlManager.start() + //Handle initial launch showInitialData() } @@ -306,7 +321,7 @@ private extension ProxyManager { func createMenuAndGlobalVoiceCommands() { // Send the root menu items let screenManager = sdlManager.screenManager - let menuItems = MenuManager.allMenuItems(with: sdlManager, choiceSetManager: performInteractionManager) + let menuItems = MenuManager.allMenuItems(with: sdlManager, choiceSetManager: performInteractionManager, remoteManager: remoteControlManager) let voiceMenuItems = MenuManager.allVoiceMenuItems(with: sdlManager) if !menuItems.isEmpty { screenManager.menu = menuItems } diff --git a/Example Apps/Example Swift/RemoteControlManager.swift b/Example Apps/Example Swift/RemoteControlManager.swift new file mode 100644 index 000000000..fc73434b4 --- /dev/null +++ b/Example Apps/Example Swift/RemoteControlManager.swift @@ -0,0 +1,226 @@ +// +// RemoteControlManager.swift +// SmartDeviceLink-Example-Swift +// +// Created by Beharry, Justin (J.S.) on 7/28/22. +// Copyright © 2022 smartdevicelink. All rights reserved. +// + +import Foundation +import SmartDeviceLink +import SmartDeviceLinkSwift + +class RemoteControlManager { + private let sdlManager: SDLManager + private let homeButtons: [SDLSoftButtonObject] + private var remoteControlCapabilities: SDLRemoteControlCapabilities? + private var climateModuleId: String? + private var hasConsent: Bool? + private var climateData: SDLClimateControlData? + + let isEnabled: Bool + var climateDataString: String { + """ + AC: \(optionalNumberBoolToString(climateData?.acEnable)) + AC Max: \(optionalNumberBoolToString(climateData?.acMaxEnable)) + Auto Mode: \(optionalNumberBoolToString(climateData?.autoModeEnable)) + Circulate Air: \(optionalNumberBoolToString(climateData?.circulateAirEnable)) + Climate: \(optionalNumberBoolToString(climateData?.climateEnable)) + Current Temperature: \(optionalTemperatureToString(climateData?.currentTemperature)) + Defrost Zone: \(optionalSDLEnumToString(climateData?.defrostZone?.rawValue)) + Desired Temperature: \(optionalTemperatureToString(climateData?.desiredTemperature)) + Dual Mode: \(optionalNumberBoolToString(climateData?.dualModeEnable)) + Fan Speed: \(optionalNumberToString(climateData?.fanSpeed)) + Heated Mirrors: \(optionalNumberBoolToString(climateData?.heatedMirrorsEnable)) + Heated Rears Window: \(optionalNumberBoolToString(climateData?.heatedRearWindowEnable)) + Heated Steering: \(optionalNumberBoolToString(climateData?.heatedSteeringWheelEnable)) + Heated Windshield: \(optionalNumberBoolToString(climateData?.heatedWindshieldEnable)) + Ventilation: \(optionalSDLEnumToString(climateData?.ventilationMode?.rawValue)) + """ + } + + /// Initialize the RemoteControlManager Object + /// + /// - Parameters: + /// - sdlManager: The SDL Manager. + /// - enabled: Permission from the proxy manager to access remote control data + /// - homeButton: An array of SDLSoftButtonObjects that remote control manager can reset to. + init(sdlManager: SDLManager, enabled: Bool, homeButtons: [SDLSoftButtonObject]) { + self.sdlManager = sdlManager + self.isEnabled = enabled + self.homeButtons = homeButtons + } + + func start() { + if !self.isEnabled { + return SDLLog.w("Missing permissions for Remote Control Manager. Example remote control works only on TCP.") + } + + // Retrieve remote control information and store module ids + self.sdlManager.systemCapabilityManager.subscribe(capabilityType: .remoteControl) { (capability, subscribed, error) in + guard capability?.remoteControlCapability != nil else { + return SDLLog.e("SDL errored getting remote control module information: \(String(describing: error))") + } + self.remoteControlCapabilities = capability?.remoteControlCapability + + let firstClimateModule = self.remoteControlCapabilities?.climateControlCapabilities?.first + let moduleId = firstClimateModule?.moduleInfo?.moduleId + self.climateModuleId = moduleId + + // Get Consent to control module + let getInteriorVehicleDataConsent = SDLGetInteriorVehicleDataConsent(moduleType: .climate, moduleIds: [self.climateModuleId!]) + self.sdlManager.send(request: getInteriorVehicleDataConsent, responseHandler: { (request, response, error) in + guard let response = response as? SDLGetInteriorVehicleDataConsentResponse else { + return SDLLog.e("SDL errored getting remote control consent: \(String(describing: error))"); + } + guard let allowed = response.allowed?.first?.boolValue else { return } + + self.hasConsent = allowed + + // initialize climate data and setup subscription + if self.hasConsent == true { + self.initializeClimateData() + self.subscribeClimateControlData() + } + }) + } + } + + /// Displays Buttons for the user to control the climate + func showClimateControl() { + // Check that the climate module id has been set and consent has been given + guard climateModuleId != nil && hasConsent == true else { + return AlertManager.sendAlert(textField1: "The climate module id was not set or consent was not given", sdlManager: self.sdlManager) + } + self.sdlManager.screenManager.softButtonObjects = climateButtons + } + + private func optionalNumberBoolToString(_ number: NSNumber?) -> String { + guard let number = number else { return "Unknown" } + return number.boolValue ? "On" : "Off" + } + + private func optionalNumberToString(_ number: NSNumber?) -> String { + guard let number = number else { return "Unknown" } + return number.stringValue + } + + private func optionalTemperatureToString(_ temperature: SDLTemperature?) -> String { + guard let temperature = temperature else { return "Unknown" } + return temperature.description + } + + private func optionalSDLEnumToString(_ sdlEnum: SDLEnum?) -> String { + guard let sdlEnum = sdlEnum else { return "Unknown" } + return sdlEnum.rawValue + } + + private func initializeClimateData() { + // Check that the climate module id has been set and consent has been given + guard climateModuleId != nil && hasConsent == true else { + return AlertManager.sendAlert(textField1: "The climate module id was not set or consent was not given", sdlManager: self.sdlManager) + } + + let getInteriorVehicleData = SDLGetInteriorVehicleData(moduleType: .climate, moduleId: self.climateModuleId!) + self.sdlManager.send(request: getInteriorVehicleData) { (req, res, err) in + guard let response = res as? SDLGetInteriorVehicleDataResponse else { return } + self.climateData = response.moduleData?.climateControlData + } + } + + private func subscribeClimateControlData() { + // Start the subscription to remote control data + sdlManager.subscribe(to: .SDLDidReceiveInteriorVehicleData) { (message) in + guard let onInteriorVehicleData = message as? SDLOnInteriorVehicleData else { return } + self.climateData = onInteriorVehicleData.moduleData.climateControlData + } + + // Start the subscription to climate data + let getInteriorVehicleData = SDLGetInteriorVehicleData(andSubscribeToModuleType: .climate, moduleId: self.climateModuleId!) + sdlManager.send(request: getInteriorVehicleData) { (req, res, err) in + guard let response = res as? SDLGetInteriorVehicleDataResponse, response.success.boolValue == true else { + return SDLLog.e("SDL errored trying to subscribe to climate data: \(String(describing: err))") + } + SDLLog.d("Subscribed to climate control data"); + } + } + + private func turnOnAC() { + let climateControlData = SDLClimateControlData(dictionary: [ + "acEnable": true + ]) + let moduleData = SDLModuleData(climateControlData: climateControlData) + let setInteriorVehicleData = SDLSetInteriorVehicleData(moduleData: moduleData) + + self.sdlManager.send(request: setInteriorVehicleData) { (request, response, error) in + guard response?.success.boolValue == true else { + return SDLLog.e("SDL errored trying to turn on climate AC: \(String(describing: error))") + } + } + } + + private func turnOffAC() { + let climateControlData = SDLClimateControlData(dictionary: [ + "acEnable": false + ]) + let moduleData = SDLModuleData(climateControlData: climateControlData) + let setInteriorVehicleData = SDLSetInteriorVehicleData(moduleData: moduleData) + + self.sdlManager.send(request: setInteriorVehicleData) { (request, response, error) in + guard response?.success.boolValue == true else { + return SDLLog.e("SDL errored trying to turn off climate AC: \(String(describing: error))") + } + } + } + + // An array of button objects to control the climate + private var climateButtons : [SDLSoftButtonObject] { + let acOnButton = SDLSoftButtonObject(name: "AC On", text: "AC On", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + self.turnOnAC() + } + + let acOffButton = SDLSoftButtonObject(name: "AC Off", text: "AC Off", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + self.turnOffAC() + } + + let acMaxToggle = SDLSoftButtonObject(name: "AC Max", text: "AC Max", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + let remoteButtonPress = SDLButtonPress(buttonName: .acMax, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short) + self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in + guard response?.success.boolValue == true else { + return SDLLog.e("SDL errored toggling AC Max with remote button press: \(String(describing: error))") + } + } + } + + let temperatureDecreaseButton = SDLSoftButtonObject(name: "Temperature Decrease", text: "Temperature -", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + let remoteButtonPress = SDLButtonPress(buttonName: .tempDown, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short) + self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in + guard response?.success.boolValue == true else { + return SDLLog.e("SDL errored decreasing target climate temperature with remote button: \(String(describing: error))") + } + } + } + + let temperatureIncreaseButton = SDLSoftButtonObject(name: "Temperature Increase", text: "Temperature +", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + let remoteButtonPress = SDLButtonPress(buttonName: .tempUp, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short) + self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in + guard response?.success.boolValue == true else { + return SDLLog.e("SDL errored increasing target climate temperature with remote button:: \(String(describing: error))") + } + } + } + + let backToHomeButton = SDLSoftButtonObject(name: "Home", text: "Back to Home", artwork: nil) { (buttonPress, buttonEvent) in + guard buttonPress != nil else { return } + self.sdlManager.screenManager.softButtonObjects = self.homeButtons + self.sdlManager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .nonMedia)) + } + + return [acOnButton, acOffButton, acMaxToggle, temperatureDecreaseButton, temperatureIncreaseButton, backToHomeButton] + } +} |