summaryrefslogtreecommitdiff
path: root/Example Apps/Example Swift
diff options
context:
space:
mode:
Diffstat (limited to 'Example Apps/Example Swift')
-rw-r--r--Example Apps/Example Swift/MenuManager.swift58
-rw-r--r--Example Apps/Example Swift/ProxyManager.swift25
-rw-r--r--Example Apps/Example Swift/RemoteControlManager.swift226
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]
+ }
+}