├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .hound.yml ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ ├── TeslaSwift-Combine.xcscheme │ ├── TeslaSwift-Package.xcscheme │ └── TeslaSwift.xcscheme ├── .travis.yml ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Extensions │ ├── Combine │ │ └── TeslaSwift+Combine.swift │ ├── Streaming │ │ ├── StreamControl.swift │ │ ├── StreamEvent.swift │ │ └── TeslaStreaming.swift │ └── StreamingCombine │ │ └── TeslaSwift+StreamingCombine.swift └── TeslaSwift │ ├── Model │ ├── Authentication.swift │ ├── BatteryData.swift │ ├── BatteryPowerHistory.swift │ ├── BatteryStatus.swift │ ├── ChargeAmpsCommandOptions.swift │ ├── ChargeHistory.swift │ ├── ChargeLimitPercentageCommandOptions.swift │ ├── ChargeState.swift │ ├── ClimateState.swift │ ├── Codable+JSONString.swift │ ├── CommandResponse.swift │ ├── Distance.swift │ ├── DriveState.swift │ ├── EnergySite.swift │ ├── EnergySiteHistory.swift │ ├── EnergySiteInfo.swift │ ├── EnergySiteLiveStatus.swift │ ├── EnergySiteStatus.swift │ ├── ErrorMessage.swift │ ├── GenericResponse.swift │ ├── GuiSettings.swift │ ├── HomeLinkCommandOptions.swift │ ├── MaxDefrostCommandOptions.swift │ ├── Me.swift │ ├── NearbyChargingSites.swift │ ├── OpenTrunkOptions.swift │ ├── Partner.swift │ ├── Product.swift │ ├── Region.swift │ ├── RemoteSeatHeaterRequestOptions.swift │ ├── RemoteStartDriveCommandOptions.swift │ ├── RemoteSteeringWheelHeaterRequestOptions.swift │ ├── ScheduledChargingCommandOptions.swift │ ├── ScheduledDepartureCommandOptions.swift │ ├── SentryModeCommandOptions.swift │ ├── SetSunRoofCommandOptions.swift │ ├── SetTemperatureCommandOptions.swift │ ├── ShareToVehicleOptions.swift │ ├── SoftwareUpdate.swift │ ├── Speed.swift │ ├── SpeedLimitOptions.swift │ ├── ValetCommandOptions.swift │ ├── Vehicle.swift │ ├── VehicleConfig.swift │ ├── VehicleExtended.swift │ ├── VehicleState.swift │ └── WindowControlCommandOptions.swift │ ├── TeslaEndpoint.swift │ ├── TeslaSwift.swift │ └── VehicleCommands.swift ├── TeslaSwiftDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── FirstViewController.swift ├── Info.plist ├── LoginViewController.swift ├── ProductViewController.swift ├── SecondViewController.swift ├── StreamViewController.swift ├── TabController.swift ├── TeslaSwift.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── TeslaSwiftDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── TeslaSwift.xcscheme ├── VehicleViewController.swift └── ViewController+TeslaSwift.swift └── TeslaSwiftTests ├── AllStates.json ├── Authentication.json ├── AuthenticationFailed.json ├── ChargeLimitMaxRange.json ├── ChargeLimitPercentage.json ├── ChargeLimitStandard.json ├── ChargeState.json ├── ClimateSettings.json ├── DriveState.json ├── FlashLights.json ├── GuiSettings.json ├── HonkHorn.json ├── Info.plist ├── LockDoors.json ├── MobileAccess.json ├── NearbyChargingSites.json ├── OpenChargeDoor.json ├── OpenTrunk.json ├── ResetValetPin.json ├── SetSpeedLimit.json ├── SetSunRoof.json ├── SetTemperature.json ├── SetValetMode.json ├── SpeedLimitPin.json ├── StartAutoConditioning.json ├── StartCharging.json ├── StartVehicle.json ├── StopAutoConditioning.json ├── StopCharging.json ├── StreamingData.txt ├── TeslaSwiftTests.swift ├── UnlockDoors.json ├── VehicleConfig.json ├── VehicleState.json ├── Vehicles.json └── WakeUp.json /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Swift 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: swift build -v 21 | - name: Run tests 22 | run: swift test -v 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | .DS_Store 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/screenshots 65 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swiftlint: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - force_cast 3 | - operator_whitespace 4 | - cyclomatic_complexity 5 | - variable_name 6 | - switch_case_alignment 7 | - trailing_whitespace 8 | - no_space_in_method_call 9 | - nesting 10 | - function_body_length 11 | - type_body_length 12 | - file_length 13 | included: # paths to include during linting. `--path` is ignored if present. 14 | - Sources 15 | line_length: 600 16 | variable_name: 17 | min_length: # only min_length 18 | error: 3 # only error 19 | excluded: # excluded via string array 20 | - on 21 | - id 22 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/TeslaSwift-Combine.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/TeslaSwift-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 71 | 77 | 78 | 79 | 85 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 113 | 119 | 120 | 121 | 127 | 133 | 134 | 135 | 141 | 147 | 148 | 149 | 155 | 161 | 162 | 163 | 164 | 165 | 170 | 171 | 172 | 173 | 183 | 184 | 190 | 191 | 197 | 198 | 199 | 200 | 202 | 203 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/TeslaSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode12.2 3 | xcode_workspace: TeslaSwiftDemo/TeslaSwift.xcworkspace 4 | xcode_scheme: TeslaSwift 5 | podfile: TeslaSwiftDemo/Podfile 6 | notifications: 7 | email: false 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 João Nunes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "PromiseKit", 6 | "repositoryURL": "https://github.com/mxcl/PromiseKit", 7 | "state": { 8 | "branch": null, 9 | "revision": "2fdb8c64a85e2081388532dbc61eb348a0793b1c", 10 | "version": "6.10.0" 11 | } 12 | }, 13 | { 14 | "package": "RxSwift", 15 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "97e14358a3e6564de8a6289362385a92d85936e1", 19 | "version": "6.0.0-rc.2" 20 | } 21 | }, 22 | { 23 | "package": "Starscream", 24 | "repositoryURL": "https://github.com/daltoniam/Starscream.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "df8d82047f6654d8e4b655d1b1525c64e1059d21", 28 | "version": "4.0.4" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TeslaSwift", 6 | platforms: [ 7 | .macOS(.v12), .iOS(.v15), .watchOS(.v8), .tvOS(.v15) 8 | ], 9 | products: [ 10 | .library(name: "TeslaSwift", targets: ["TeslaSwift"]), 11 | .library(name: "TeslaSwiftStreaming", targets: ["TeslaSwiftStreaming"]), 12 | .library(name: "TeslaSwiftCombine", targets: ["TeslaSwiftCombine"]), 13 | .library(name: "TeslaSwiftStreamingCombine", targets: ["TeslaSwiftStreamingCombine"]) 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/daltoniam/Starscream.git", from: "4.0.6"), 17 | //.package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", Package.Dependency.Requirement.branch("feature/spm-support")) 18 | ], 19 | targets: [ 20 | .target(name: "TeslaSwift"), 21 | .target(name: "TeslaSwiftStreaming", dependencies: ["TeslaSwift", "Starscream"], path: "Sources/Extensions/Streaming"), 22 | .target(name: "TeslaSwiftCombine", dependencies: ["TeslaSwift"], path: "Sources/Extensions/Combine"), 23 | .target(name: "TeslaSwiftStreamingCombine", dependencies: ["TeslaSwiftStreaming", "TeslaSwiftCombine"], path: "Sources/Extensions/StreamingCombine"), 24 | //.testTarget(name: "TeslaSwiftTests", dependencies: ["TeslaSwiftPMK", "PromiseKit", "OHHTTPStubsSwift"], path: "TeslaSwiftTests") 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /Sources/Extensions/Combine/TeslaSwift+Combine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeslaSwift+Combine.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 08/07/2019. 6 | // Copyright © 2019 Joao Nunes. All rights reserved. 7 | // 8 | 9 | #if swift(>=5.1) 10 | import Combine 11 | import TeslaSwift 12 | 13 | extension TeslaSwift { 14 | public func revokeToken() -> Future { 15 | Future { promise in 16 | Task { 17 | do { 18 | let result = try await self.revokeToken() 19 | promise(.success(result)) 20 | } catch let error { 21 | promise(.failure(error)) 22 | } 23 | } 24 | } 25 | } 26 | 27 | public func getVehicles() -> Future<[Vehicle], Error> { 28 | Future { promise in 29 | Task { 30 | do { 31 | let result = try await self.getVehicles() 32 | promise(.success(result)) 33 | } catch let error { 34 | promise(.failure(error)) 35 | } 36 | } 37 | } 38 | } 39 | 40 | public func getVehicle(_ vehicleID: VehicleId) -> Future { 41 | Future { promise in 42 | Task { 43 | do { 44 | let result = try await self.getVehicle(vehicleID) 45 | promise(.success(result)) 46 | } catch let error { 47 | promise(.failure(error)) 48 | } 49 | } 50 | } 51 | } 52 | 53 | public func getVehicle(_ vehicle: Vehicle) -> Future { 54 | Future { promise in 55 | Task { 56 | do { 57 | let result = try await self.getVehicle(vehicle) 58 | promise(.success(result)) 59 | } catch let error { 60 | promise(.failure(error)) 61 | } 62 | } 63 | } 64 | } 65 | 66 | public func getAllData(_ vehicle: Vehicle, endpoints: [AllStatesEndpoints] = AllStatesEndpoints.allWithLocation) -> Future { 67 | Future { promise in 68 | Task { 69 | do { 70 | let result = try await self.getAllData(vehicle, endpoints: endpoints) 71 | promise(.success(result)) 72 | } catch let error { 73 | promise(.failure(error)) 74 | } 75 | } 76 | } 77 | } 78 | 79 | public func getVehicleMobileAccessState(_ vehicle: Vehicle) -> Future { 80 | Future { promise in 81 | Task { 82 | do { 83 | let result = try await self.getVehicleMobileAccessState(vehicle) 84 | promise(.success(result)) 85 | } catch let error { 86 | promise(.failure(error)) 87 | } 88 | } 89 | } 90 | } 91 | 92 | public func wakeUp(_ vehicle: Vehicle) -> Future { 93 | Future { promise in 94 | Task { 95 | do { 96 | let result = try await self.wakeUp(vehicle) 97 | promise(.success(result)) 98 | } catch let error { 99 | promise(.failure(error)) 100 | } 101 | } 102 | } 103 | } 104 | 105 | public func sendCommandToVehicle(_ vehicle: Vehicle, command: VehicleCommand) -> Future { 106 | Future { promise in 107 | Task { 108 | do { 109 | let result = try await self.sendCommandToVehicle(vehicle, command: command) 110 | promise(.success(result)) 111 | } catch let error { 112 | promise(.failure(error)) 113 | } 114 | } 115 | } 116 | } 117 | 118 | public func getNearbyChargingSite(vehicle: Vehicle) -> Future { 119 | Future { promise in 120 | Task { 121 | do { 122 | let result = try await self.getNearbyChargingSites(vehicle) 123 | promise(.success(result)) 124 | } catch let error { 125 | promise(.failure(error)) 126 | } 127 | } 128 | } 129 | } 130 | 131 | public func getProducts() -> Future<[Product], Error> { 132 | Future { promise in 133 | Task { 134 | do { 135 | let result = try await self.getProducts() 136 | promise(.success(result)) 137 | } catch let error { 138 | promise(.failure(error)) 139 | } 140 | } 141 | } 142 | } 143 | 144 | public func getEnergySiteStatus(siteID: SiteId) -> Future { 145 | Future { promise in 146 | Task { 147 | do { 148 | let result = try await self.getEnergySiteStatus(siteID: siteID) 149 | promise(.success(result)) 150 | } catch let error { 151 | promise(.failure(error)) 152 | } 153 | } 154 | } 155 | } 156 | 157 | public func getEnergySiteLiveStatus(siteID: SiteId) -> Future { 158 | Future { promise in 159 | Task { 160 | do { 161 | let result = try await self.getEnergySiteLiveStatus(siteID: siteID) 162 | promise(.success(result)) 163 | } catch let error { 164 | promise(.failure(error)) 165 | } 166 | } 167 | } 168 | } 169 | 170 | public func getEnergySiteInfo(siteID: SiteId) -> Future { 171 | Future { promise in 172 | Task { 173 | do { 174 | let result = try await self.getEnergySiteInfo(siteID: siteID) 175 | promise(.success(result)) 176 | } catch let error { 177 | promise(.failure(error)) 178 | } 179 | } 180 | } 181 | } 182 | public func getEnergySiteHistory(siteID: SiteId, period: EnergySiteHistory.Period) -> Future { 183 | Future { promise in 184 | Task { 185 | do { 186 | let result = try await self.getEnergySiteHistory(siteID: siteID, period: period) 187 | promise(.success(result)) 188 | } catch let error { 189 | promise(.failure(error)) 190 | } 191 | } 192 | } 193 | } 194 | 195 | 196 | public func getBatteryStatus(batteryID: BatteryId) -> Future { 197 | Future { promise in 198 | Task { 199 | do { 200 | let result = try await self.getBatteryStatus(batteryID: batteryID) 201 | promise(.success(result)) 202 | } catch let error { 203 | promise(.failure(error)) 204 | } 205 | } 206 | } 207 | } 208 | 209 | public func getBatteryData(batteryID: BatteryId) -> Future { 210 | Future { promise in 211 | Task { 212 | do { 213 | let result = try await self.getBatteryData(batteryID: batteryID) 214 | promise(.success(result)) 215 | } catch let error { 216 | promise(.failure(error)) 217 | } 218 | } 219 | } 220 | } 221 | 222 | public func getBatteryPowerHistory(batteryID: BatteryId) -> Future { 223 | Future { promise in 224 | Task { 225 | do { 226 | let result = try await self.getBatteryPowerHistory(batteryID: batteryID) 227 | promise(.success(result)) 228 | } catch let error { 229 | promise(.failure(error)) 230 | } 231 | } 232 | } 233 | } 234 | } 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /Sources/Extensions/Streaming/StreamControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamControl.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 20/10/2019. 6 | // Copyright © 2019 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class StreamAuthentication: Encodable { 12 | var messageType: String 13 | var token: String 14 | var value = "speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power,shift_state,range,est_range,heading" 15 | var tag: String 16 | 17 | init?(type: TeslaStreamAuthenticationType, vehicleId: String) { 18 | switch type { 19 | case let .bearer(email, vehicleToken): 20 | self.messageType = "data:subscribe" 21 | if let token = "\(email):\(vehicleToken)".data(using: String.Encoding.utf8)?.base64EncodedString() { 22 | self.token = token 23 | } else { 24 | return nil 25 | } 26 | case let .oAuth(oAuthToken): 27 | self.messageType = "data:subscribe_oauth" 28 | self.token = "\(oAuthToken)" 29 | } 30 | self.tag = vehicleId 31 | } 32 | 33 | enum CodingKeys: String, CodingKey { 34 | case messageType = "msg_type" 35 | case token 36 | case value 37 | case tag 38 | } 39 | } 40 | 41 | class StreamMessage: Decodable { 42 | var messageType: String 43 | var value: String? 44 | var tag: String? 45 | var errorType: String? 46 | var connectionTimeout: Int? 47 | 48 | enum CodingKeys: String, CodingKey { 49 | case messageType = "msg_type" 50 | case value 51 | case tag 52 | case errorType = "error_type" 53 | case connectionTimeout = "connection_timeout" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Extensions/Streaming/StreamEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamEvent.swift 3 | // TeslaSwift 4 | // 5 | // Created by Jacob Holland on 4/11/17. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | import TeslaSwift 12 | 13 | public enum TeslaStreamingEvent: Equatable { 14 | case open 15 | case event(StreamEvent) 16 | case error(Error) 17 | case disconnected 18 | 19 | public static func == (lhs: TeslaStreamingEvent, rhs: TeslaStreamingEvent) -> Bool { 20 | switch (lhs, rhs) { 21 | case (.open, .open): return true 22 | case (.event, .event): return true // ignore the event 23 | case (.error, .error): return true // ignore the error 24 | case (.disconnected, .disconnected): return true 25 | default: return false 26 | } 27 | } 28 | } 29 | 30 | open class StreamEvent: Codable { 31 | open var timestamp: Double? 32 | open var speed: CLLocationSpeed? // mph 33 | open var speedUnit: Speed? { 34 | guard let speed = speed else { return nil } 35 | return Speed(milesPerHour: speed) 36 | } 37 | open var odometer: Distance? // miles 38 | open var soc: Int? 39 | open var elevation: Int? // feet 40 | open var estLat: CLLocationDegrees? 41 | open var estLng: CLLocationDegrees? 42 | open var power: Int? // kW 43 | open var shiftState: String? 44 | open var range: Distance? // miles 45 | open var estRange: Distance? // miles 46 | open var estHeading: CLLocationDirection? 47 | open var heading: CLLocationDirection? 48 | 49 | open var position: CLLocation? { 50 | if let latitude = estLat, 51 | let longitude = estLng, 52 | let heading = heading, 53 | let timestamp = timestamp { 54 | let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) 55 | return CLLocation(coordinate: coordinate, 56 | altitude: 0.0, horizontalAccuracy: 0.0, verticalAccuracy: 0.0, 57 | course: heading, 58 | speed: speed ?? 0, 59 | timestamp: Date(timeIntervalSince1970: timestamp/1000)) 60 | } 61 | return nil 62 | } 63 | 64 | init(values: String) { 65 | // timeStamp,speed,odometer,soc,elevation,est_heading,est_lat,est_lng,power,shift_state,range,est_range,heading 66 | let separatedValues = values.components(separatedBy: ",") 67 | 68 | guard separatedValues.count > 11 else { return } 69 | 70 | if let timeValue = Double(separatedValues[0]) { 71 | timestamp = timeValue 72 | } 73 | speed = CLLocationSpeed(separatedValues[1]) 74 | if let value = Double(separatedValues[2]) { 75 | odometer = Distance(miles: value) 76 | } 77 | soc = Int(separatedValues[3]) 78 | elevation = Int(separatedValues[4]) 79 | estHeading = CLLocationDirection(separatedValues[5]) 80 | estLat = CLLocationDegrees(separatedValues[6]) 81 | estLng = CLLocationDegrees(separatedValues[7]) 82 | power = Int(separatedValues[8]) 83 | shiftState = separatedValues[9] 84 | if let value = Double(separatedValues[10]) { 85 | range = Distance(miles: value) 86 | } 87 | if let value = Double(separatedValues[11]) { 88 | estRange = Distance(miles: value) 89 | } 90 | heading = CLLocationDirection(separatedValues[12]) 91 | } 92 | 93 | open var originalValues: String { 94 | var resultString = "" 95 | if let timestamp = timestamp { 96 | resultString.append("\(timestamp)") 97 | } 98 | resultString.append(",") 99 | if let speed = speed { 100 | resultString.append("\(speed)") 101 | } 102 | resultString.append(",") 103 | if let odometer = odometer { 104 | resultString.append("\(odometer.miles)") 105 | } 106 | resultString.append(",") 107 | if let soc = soc { 108 | resultString.append("\(soc)") 109 | } 110 | resultString.append(",") 111 | if let elevation = elevation { 112 | resultString.append("\(elevation)") 113 | } 114 | resultString.append(",") 115 | if let estHeading = estHeading { 116 | resultString.append("\(estHeading)") 117 | } 118 | resultString.append(",") 119 | if let estLat = estLat { 120 | resultString.append("\(estLat)") 121 | } 122 | resultString.append(",") 123 | if let estLng = estLng { 124 | resultString.append("\(estLng)") 125 | } 126 | resultString.append(",") 127 | if let power = power { 128 | resultString.append("\(power)") 129 | } 130 | resultString.append(",") 131 | if let shiftState = shiftState { 132 | resultString.append("\(shiftState)") 133 | } 134 | resultString.append(",") 135 | if let range = range { 136 | resultString.append("\(range.miles)") 137 | } 138 | resultString.append(",") 139 | if let estRange = estRange { 140 | resultString.append("\(estRange.miles)") 141 | } 142 | resultString.append(",") 143 | if let heading = heading { 144 | resultString.append("\(heading)") 145 | } 146 | 147 | return resultString 148 | } 149 | 150 | enum CodingKeys: String, CodingKey { 151 | case timestamp 152 | case speed 153 | case odometer 154 | case soc 155 | case elevation 156 | case estLat 157 | case estLng 158 | case power 159 | case shiftState 160 | case range 161 | case estRange 162 | case estHeading 163 | case heading 164 | } 165 | 166 | } 167 | 168 | extension StreamEvent: CustomStringConvertible { 169 | public var description: String { 170 | return "speed: \(speed ?? -1), odo: \(odometer?.miles ?? -1.0), soc: \(soc ?? -1), elevation: \(elevation ?? -1), estLat: \(estLat ?? -1), estLng: \(estLng ?? -1), power: \(power ?? -1), shift: \(shiftState ?? ""), range: \(range?.miles ?? -1), estRange: \(estRange?.miles ?? -1) heading: \(heading ?? -1), estHeading: \(estHeading ?? -1), timestamp: \(timestamp ?? 0)" 171 | } 172 | 173 | public var descriptionKm: String { 174 | return "speed: \(speedUnit?.kilometersPerHour ?? -1), odo: \(odometer?.kms ?? -1.0), soc: \(soc ?? -1), elevation: \(elevation ?? -1), estLat: \(estLat ?? -1), estLng: \(estLng ?? -1), power: \(power ?? -1), shift: \(shiftState ?? ""), range: \(range?.kms ?? -1), estRange: \(estRange?.kms ?? -1) heading: \(heading ?? -1), estHeading: \(estHeading ?? -1), timestamp: \(timestamp ?? 0)" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Sources/Extensions/Streaming/TeslaStreaming.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeslaStreaming.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 23/04/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Starscream 11 | import TeslaSwift 12 | import os 13 | 14 | enum TeslaStreamingError: Error { 15 | case streamingMissingVehicleTokenOrEmail 16 | case streamingMissingOAuthToken 17 | } 18 | 19 | enum TeslaStreamAuthenticationType { 20 | case bearer(String, String) // email, vehicleToken 21 | case oAuth(String) // oAuthToken 22 | } 23 | 24 | struct TeslaStreamAuthentication { 25 | let type: TeslaStreamAuthenticationType 26 | let vehicleId: String 27 | 28 | public init(type: TeslaStreamAuthenticationType, vehicleId: String) { 29 | self.type = type 30 | self.vehicleId = vehicleId 31 | } 32 | } 33 | 34 | /* 35 | * Streaming class takes care of the different types of data streaming from Tesla servers 36 | * 37 | */ 38 | public class TeslaStreaming { 39 | var debuggingEnabled: Bool { 40 | teslaSwift.debuggingEnabled 41 | } 42 | private var httpStreaming: WebSocket 43 | private var webSocketTask: URLSessionWebSocketTask 44 | private var teslaSwift: TeslaSwift 45 | 46 | private static let logger = Logger(subsystem: "Tesla Swift", category: "Tesla Streaming") 47 | 48 | public init(teslaSwift: TeslaSwift) { 49 | webSocketTask = URLSession(configuration: .default).webSocketTask(with: URL(string: "wss://streaming.vn.teslamotors.com/streaming/")!) 50 | 51 | httpStreaming = WebSocket(request: URLRequest(url: URL(string: "wss://streaming.vn.teslamotors.com/streaming/")!)) 52 | self.teslaSwift = teslaSwift 53 | } 54 | 55 | private static func logDebug(_ format: String, debuggingEnabled: Bool) { 56 | if debuggingEnabled { 57 | logger.debug("\(format)") 58 | } 59 | } 60 | 61 | /** 62 | Streams vehicle data 63 | 64 | - parameter vehicle: the vehicle that will receive the command 65 | - parameter reloadsVehicle: if you have a cached vehicle, the token might be expired, this forces a vehicle token reload 66 | - parameter dataReceived: callback to receive the websocket data 67 | */ 68 | public func openStream(vehicle: Vehicle, reloadsVehicle: Bool = true) async throws -> AsyncThrowingStream { 69 | if reloadsVehicle { 70 | return AsyncThrowingStream { continuation in 71 | Task { 72 | do { 73 | let freshVehicle = try await reloadVehicle(vehicle: vehicle) 74 | self.startStream(vehicle: freshVehicle, dataReceived: { data in 75 | continuation.yield(data) 76 | if data == .disconnected { 77 | continuation.finish() 78 | } 79 | }) 80 | } catch let error { 81 | continuation.finish(throwing: error) 82 | } 83 | } 84 | } 85 | } else { 86 | return AsyncThrowingStream { continuation in 87 | startStream(vehicle: vehicle, dataReceived: { data in 88 | continuation.yield(data) 89 | if data == .disconnected { 90 | continuation.finish() 91 | } 92 | }) 93 | } 94 | } 95 | } 96 | 97 | /** 98 | Stops the stream 99 | */ 100 | public func closeStream() { 101 | httpStreaming.disconnect() 102 | webSocketTask.cancel() 103 | Self.logDebug("Stream closed", debuggingEnabled: self.debuggingEnabled) 104 | } 105 | 106 | private func reloadVehicle(vehicle: Vehicle) async throws -> Vehicle { 107 | let vehicles = try await teslaSwift.getVehicles() 108 | for freshVehicle in vehicles where freshVehicle.vehicleID == vehicle.vehicleID { 109 | return freshVehicle 110 | } 111 | throw TeslaError.failedToReloadVehicle 112 | } 113 | 114 | private func startStream(vehicle: Vehicle, dataReceived: @escaping (TeslaStreamingEvent) -> Void) { 115 | guard let accessToken = teslaSwift.token?.accessToken else { 116 | dataReceived(TeslaStreamingEvent.error(TeslaStreamingError.streamingMissingOAuthToken)) 117 | return 118 | } 119 | let type: TeslaStreamAuthenticationType = .oAuth(accessToken) 120 | let authentication = TeslaStreamAuthentication(type: type, vehicleId: "\(vehicle.vehicleID!)") 121 | 122 | openStream(authentication: authentication, dataReceived: dataReceived) 123 | } 124 | 125 | private func openStream(authentication: TeslaStreamAuthentication, dataReceived: @escaping (TeslaStreamingEvent) -> Void) { 126 | let url = httpStreaming.request.url?.absoluteString 127 | 128 | Self.logDebug("Opening Stream to: \(url ?? "")", debuggingEnabled: debuggingEnabled) 129 | 130 | 131 | webSocketTask.receive { result in 132 | switch result { 133 | case let .success(message): 134 | print(message) 135 | case let .failure(error): 136 | //logDebug("Stream disconnected \(code):\(error)", debuggingEnabled: self.debuggingEnabled) 137 | dataReceived(TeslaStreamingEvent.error(NSError(domain: "TeslaStreamingError", code: Int(404), userInfo: ["error": error]))) 138 | } 139 | } 140 | 141 | httpStreaming.onEvent = { 142 | [weak self] event in 143 | guard let self = self else { return } 144 | 145 | switch event { 146 | case let .connected(headers): 147 | Self.logDebug("Stream open headers: \(headers)", debuggingEnabled: self.debuggingEnabled) 148 | 149 | if let authMessage = StreamAuthentication(type: authentication.type, vehicleId: authentication.vehicleId), let string = try? teslaJSONEncoder.encode(authMessage) { 150 | 151 | self.httpStreaming.write(data: string) 152 | dataReceived(TeslaStreamingEvent.open) 153 | } else { 154 | dataReceived(TeslaStreamingEvent.error(NSError(domain: "TeslaStreamingError", code: 0, userInfo: ["errorDescription": "Failed to parse authentication data"]))) 155 | self.closeStream() 156 | } 157 | case let .binary(data): 158 | Self.logDebug("Stream data: \(String(data: data, encoding: .utf8) ?? "")", debuggingEnabled: self.debuggingEnabled) 159 | 160 | guard let message = try? teslaJSONDecoder.decode(StreamMessage.self, from: data) else { return } 161 | 162 | let type = message.messageType 163 | switch type { 164 | case "control:hello": 165 | Self.logDebug("Stream got hello", debuggingEnabled: self.debuggingEnabled) 166 | case "data:update": 167 | if let values = message.value { 168 | let event = StreamEvent(values: values) 169 | Self.logDebug("Stream got data: \(values)", debuggingEnabled: self.debuggingEnabled) 170 | dataReceived(TeslaStreamingEvent.event(event)) 171 | } 172 | case "data:error": 173 | Self.logDebug("Stream got data error: \(message.value ?? ""), \(message.errorType ?? "")", debuggingEnabled: self.debuggingEnabled) 174 | dataReceived(TeslaStreamingEvent.error(NSError(domain: "TeslaStreamingError", code: 0, userInfo: [message.value ?? "error": message.errorType ?? ""]))) 175 | default: 176 | break 177 | } 178 | case let .disconnected(error, code): 179 | Self.logDebug("Stream disconnected \(code):\(error)", debuggingEnabled: self.debuggingEnabled) 180 | dataReceived(TeslaStreamingEvent.error(NSError(domain: "TeslaStreamingError", code: Int(code), userInfo: ["error": error]))) 181 | case let .pong(data): 182 | Self.logDebug("Stream Pong", debuggingEnabled: self.debuggingEnabled) 183 | self.httpStreaming.write(pong: data ?? Data()) 184 | case let .text(text): 185 | Self.logDebug("Stream Text: \(text)", debuggingEnabled: self.debuggingEnabled) 186 | case let .ping(ping): 187 | Self.logDebug("Stream ping: \(String(describing: ping))", debuggingEnabled: self.debuggingEnabled) 188 | case let .error(error): 189 | DispatchQueue.main.async { 190 | Self.logDebug("Stream error:\(String(describing: error))", debuggingEnabled: self.debuggingEnabled) 191 | dataReceived(TeslaStreamingEvent.error(NSError(domain: "TeslaStreamingError", code: 0, userInfo: ["error": error ?? ""]))) 192 | } 193 | case let .viabilityChanged(viability): 194 | Self.logDebug("Stream viabilityChanged: \(viability)", debuggingEnabled: self.debuggingEnabled) 195 | case let .reconnectSuggested(reconnect): 196 | Self.logDebug("Stream reconnectSuggested: \(reconnect)", debuggingEnabled: self.debuggingEnabled) 197 | case .cancelled: 198 | Self.logDebug("Stream cancelled", debuggingEnabled: self.debuggingEnabled) 199 | case .peerClosed: 200 | Self.logDebug("Peer Closed", debuggingEnabled: self.debuggingEnabled) 201 | } 202 | } 203 | httpStreaming.connect() 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /Sources/Extensions/StreamingCombine/TeslaSwift+StreamingCombine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeslaSwift+StreamingCombine.swift 3 | // TeslaSwift 4 | // 5 | // Created by João Nunes on 19/12/2020. 6 | // Copyright © 2020 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import TeslaSwift 11 | 12 | extension TeslaStreaming { 13 | public func streamPublisher(vehicle: Vehicle) -> TeslaStreamingPublisher { 14 | return TeslaStreamingPublisher(vehicle: vehicle, stream: self) 15 | } 16 | 17 | public struct TeslaStreamingPublisher: Publisher, Cancellable { 18 | public typealias Output = TeslaStreamingEvent 19 | public typealias Failure = Error 20 | 21 | let vehicle: Vehicle 22 | let stream: TeslaStreaming 23 | 24 | init(vehicle: Vehicle, stream: TeslaStreaming) { 25 | self.vehicle = vehicle 26 | self.stream = stream 27 | } 28 | 29 | public func receive(subscriber: S) where S: Subscriber, TeslaStreamingPublisher.Failure == S.Failure, TeslaStreamingPublisher.Output == S.Input { 30 | 31 | Task { 32 | do { 33 | for try await streamEvent in try await stream.openStream(vehicle: vehicle) { 34 | switch streamEvent { 35 | case .open: 36 | _ = subscriber.receive(TeslaStreamingEvent.open) 37 | case .event(let event): 38 | _ = subscriber.receive(TeslaStreamingEvent.event(event)) 39 | case .error(let error): 40 | _ = subscriber.receive(TeslaStreamingEvent.error(error)) 41 | case .disconnected: 42 | _ = subscriber.receive(TeslaStreamingEvent.disconnected) 43 | subscriber.receive(completion: Subscribers.Completion.finished) 44 | } 45 | } 46 | } catch let error { 47 | _ = subscriber.receive(TeslaStreamingEvent.error(error)) 48 | subscriber.receive(completion: Subscribers.Completion.finished) 49 | } 50 | } 51 | } 52 | 53 | public func cancel() { 54 | stream.closeStream() 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Authentication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthToken.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 04/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CryptoKit 11 | 12 | private let oAuthCodeVerifier: String = "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef21067963841234334232123232323232" 13 | 14 | open class AuthToken: Codable { 15 | 16 | open var accessToken: String? 17 | open var tokenType: String? 18 | open var createdAt: Date? = Date() 19 | open var expiresIn: TimeInterval? 20 | open var refreshToken: String? 21 | open var idToken: String? 22 | 23 | open var isValid: Bool { 24 | if let createdAt = createdAt, let expiresIn = expiresIn { 25 | return -createdAt.timeIntervalSinceNow < expiresIn 26 | } else { 27 | return false 28 | } 29 | } 30 | 31 | public init(accessToken: String) { 32 | self.accessToken = accessToken 33 | } 34 | 35 | public required init(from decoder: Decoder) throws { 36 | let container = try decoder.container(keyedBy: CodingKeys.self) 37 | 38 | accessToken = try? container.decode(String.self, forKey: .accessToken) 39 | tokenType = try? container.decode(String.self, forKey: .tokenType) 40 | createdAt = (try? container.decode(Date.self, forKey: .createdAt)) ?? Date() 41 | expiresIn = try? container.decode(TimeInterval.self, forKey: .expiresIn) 42 | refreshToken = try? container.decode(String.self, forKey: .refreshToken) 43 | idToken = try? container.decode(String.self, forKey: .idToken) 44 | } 45 | 46 | // MARK: Codable protocol 47 | enum CodingKeys: String, CodingKey { 48 | case accessToken = "access_token" 49 | case tokenType = "token_type" 50 | case createdAt = "created_at" 51 | case expiresIn = "expires_in" 52 | case refreshToken = "refresh_token" 53 | case idToken = "id_token" 54 | } 55 | } 56 | 57 | class AuthTokenRequestWeb: Encodable { 58 | 59 | enum GrantType: String, Encodable { 60 | case refreshToken = "refresh_token" 61 | case authorizationCode = "authorization_code" 62 | case clientCredentials = "client_credentials" 63 | } 64 | 65 | var grantType: GrantType 66 | var clientID: String 67 | var clientSecret: String 68 | 69 | var codeVerifier: String? 70 | var code: String? 71 | var redirectURI: String? 72 | 73 | var refreshToken: String? 74 | var scope: String? 75 | var audience: String? 76 | 77 | init(teslaAPI: TeslaAPI, grantType: GrantType = .authorizationCode, code: String? = nil, refreshToken: String? = nil) { 78 | switch grantType { 79 | case .authorizationCode: 80 | self.codeVerifier = oAuthCodeVerifier 81 | self.redirectURI = teslaAPI.redirectURI 82 | self.audience = teslaAPI.region?.rawValue 83 | self.code = code 84 | case .refreshToken: 85 | self.refreshToken = refreshToken 86 | self.scope = teslaAPI.scope 87 | case .clientCredentials: 88 | self.scope = teslaAPI.scope 89 | self.audience = teslaAPI.region?.rawValue 90 | } 91 | self.clientID = teslaAPI.clientID 92 | self.clientSecret = teslaAPI.clientSecret 93 | self.grantType = grantType 94 | } 95 | 96 | // MARK: Codable protocol 97 | enum CodingKeys: String, CodingKey { 98 | typealias RawValue = String 99 | 100 | case grantType = "grant_type" 101 | case clientID = "client_id" 102 | case clientSecret = "client_secret" 103 | case code = "code" 104 | case redirectURI = "redirect_uri" 105 | case refreshToken = "refresh_token" 106 | case codeVerifier = "code_verifier" 107 | case scope = "scope" 108 | } 109 | } 110 | 111 | class AuthCodeRequest: Encodable { 112 | 113 | var responseType: String = "code" 114 | var clientID: String 115 | var clientSecret: String 116 | var redirectURI: String 117 | var scope: String 118 | let codeChallenge: String 119 | var codeChallengeMethod = "S256" 120 | var state = "teslaSwift" 121 | 122 | init(teslaAPI: TeslaAPI) { 123 | self.clientID = teslaAPI.clientID 124 | self.clientSecret = teslaAPI.clientSecret 125 | self.redirectURI = teslaAPI.redirectURI 126 | self.scope = teslaAPI.scope 127 | self.codeChallenge = oAuthCodeVerifier.challenge 128 | } 129 | 130 | // MARK: Codable protocol 131 | enum CodingKeys: String, CodingKey { 132 | typealias RawValue = String 133 | 134 | case clientID = "client_id" 135 | case redirectURI = "redirect_uri" 136 | case responseType = "response_type" 137 | case scope = "scope" 138 | case codeChallenge = "code_challenge" 139 | case codeChallengeMethod = "code_challenge_method" 140 | case state = "state" 141 | } 142 | 143 | func parameters() -> [URLQueryItem] { 144 | return[ 145 | URLQueryItem(name: CodingKeys.clientID.rawValue, value: clientID), 146 | URLQueryItem(name: CodingKeys.redirectURI.rawValue, value: redirectURI), 147 | URLQueryItem(name: CodingKeys.responseType.rawValue, value: responseType), 148 | URLQueryItem(name: CodingKeys.scope.rawValue, value: scope), 149 | URLQueryItem(name: CodingKeys.codeChallenge.rawValue, value: codeChallenge), 150 | URLQueryItem(name: CodingKeys.codeChallengeMethod.rawValue, value: codeChallengeMethod), 151 | URLQueryItem(name: CodingKeys.state.rawValue, value: state) 152 | ] 153 | } 154 | } 155 | 156 | extension String { 157 | var challenge: String { 158 | let hash = self.sha256 159 | let challenge = hash.base64EncodedString() 160 | .replacingOccurrences(of: "+", with: "-") 161 | .replacingOccurrences(of: "/", with: "_") 162 | .replacingOccurrences(of: "=", with: "") 163 | .trimmingCharacters(in: .whitespaces) 164 | return challenge 165 | } 166 | 167 | private var sha256: Data { 168 | let inputData = Data(self.utf8) 169 | let hashed = SHA256.hash(data: inputData) 170 | return Data(hashed) 171 | } 172 | } 173 | 174 | extension TeslaAPI { 175 | var region: Region? { 176 | switch self { 177 | case .ownerAPI: return nil 178 | case let .fleetAPI(region: region, clientID: _, clientSecret: _, redirectURI: _, scopes: _): return region 179 | } 180 | } 181 | 182 | var clientID: String { 183 | switch self { 184 | case .ownerAPI: return "ownerapi" 185 | case let .fleetAPI(region: _, clientID: clientID, clientSecret: _, redirectURI: _, scopes: _): return clientID 186 | } 187 | } 188 | 189 | var clientSecret: String { 190 | switch self { 191 | case .ownerAPI: return "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3" 192 | case let .fleetAPI(region: _, clientID: _, clientSecret: clientSecret, redirectURI: _, scopes: _): return clientSecret 193 | } 194 | } 195 | 196 | var redirectURI: String { 197 | switch self { 198 | case .ownerAPI: return "https://auth.tesla.com/void/callback" 199 | case let .fleetAPI(region: _, clientID: _, clientSecret: _, redirectURI: redirectURI, scopes: _): return redirectURI 200 | } 201 | } 202 | 203 | var scope: String { 204 | switch self { 205 | case .ownerAPI: return "openid email offline_access" 206 | case let .fleetAPI(region: _, clientID: _, clientSecret: _, redirectURI: _, scopes: scopes): 207 | return scopes.map { $0.rawValue }.joined(separator: " ") 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/BatteryData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryData.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Welcome 12 | open class BatteryData: Codable { 13 | open var energyLeft: Double 14 | open var totalPackEnergy: Double 15 | open var gridStatus: String 16 | open var defaultRealMode: String 17 | open var operation: String 18 | open var installationDate: Date 19 | open var batteryCount: Int 20 | open var backup: Backup 21 | open var userSettings: UserSettings 22 | open var components: Components 23 | open var powerReading: [PowerReading] 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case energyLeft = "energy_left" 27 | case totalPackEnergy = "total_pack_energy" 28 | case gridStatus = "grid_status" 29 | case backup 30 | case userSettings = "user_settings" 31 | case components 32 | case defaultRealMode = "default_real_mode" 33 | case operation 34 | case installationDate = "installation_date" 35 | case powerReading = "power_reading" 36 | case batteryCount = "battery_count" 37 | } 38 | 39 | // MARK: - Backup 40 | open class Backup: Codable { 41 | open var backupReservePercent: Double 42 | open var events: [Event] 43 | 44 | enum CodingKeys: String, CodingKey { 45 | case backupReservePercent = "backup_reserve_percent" 46 | case events 47 | } 48 | } 49 | 50 | // MARK: - Event 51 | open class Event: Codable { 52 | let timestamp: Date 53 | let duration: Int 54 | } 55 | 56 | // MARK: - Components 57 | open class Components: Codable { 58 | open var solar: Bool 59 | open var solarType: String 60 | open var battery: Bool 61 | open var grid: Bool 62 | open var backup: Bool 63 | open var gateway: String 64 | open var loadMeter: Bool 65 | open var touCapable: Bool 66 | open var stormModeCapable: Bool 67 | open var flexEnergyRequestCapable: Bool 68 | open var carChargingDataSupported: Bool 69 | open var offGridVehicleChargingReserveSupported: Bool 70 | open var vehicleChargingPerformanceViewEnabled: Bool 71 | open var vehicleChargingSolarOffsetViewEnabled: Bool 72 | open var batterySolarOffsetViewEnabled: Bool 73 | open var setIslandingModeEnabled: Bool 74 | open var backupTimeRemainingEnabled: Bool 75 | open var batteryType: String 76 | open var configurable: Bool 77 | open var gridServicesEnabled: Bool 78 | 79 | enum CodingKeys: String, CodingKey { 80 | case solar 81 | case solarType = "solar_type" 82 | case battery, grid, backup, gateway 83 | case loadMeter = "load_meter" 84 | case touCapable = "tou_capable" 85 | case stormModeCapable = "storm_mode_capable" 86 | case flexEnergyRequestCapable = "flex_energy_request_capable" 87 | case carChargingDataSupported = "car_charging_data_supported" 88 | case offGridVehicleChargingReserveSupported = "off_grid_vehicle_charging_reserve_supported" 89 | case vehicleChargingPerformanceViewEnabled = "vehicle_charging_performance_view_enabled" 90 | case vehicleChargingSolarOffsetViewEnabled = "vehicle_charging_solar_offset_view_enabled" 91 | case batterySolarOffsetViewEnabled = "battery_solar_offset_view_enabled" 92 | case setIslandingModeEnabled = "set_islanding_mode_enabled" 93 | case backupTimeRemainingEnabled = "backup_time_remaining_enabled" 94 | case batteryType = "battery_type" 95 | case configurable 96 | case gridServicesEnabled = "grid_services_enabled" 97 | } 98 | } 99 | 100 | // MARK: - PowerReading 101 | open class PowerReading: Codable { 102 | open var timestamp: Date 103 | open var loadPower: Double 104 | open var solarPower: Double 105 | open var gridPower: Double 106 | open var batteryPower: Double 107 | open var generatorPower: Double 108 | 109 | enum CodingKeys: String, CodingKey { 110 | case timestamp 111 | case loadPower = "load_power" 112 | case solarPower = "solar_power" 113 | case gridPower = "grid_power" 114 | case batteryPower = "battery_power" 115 | case generatorPower = "generator_power" 116 | } 117 | } 118 | 119 | // MARK: - UserSettings 120 | open class UserSettings: Codable { 121 | open var stormModeEnabled: Bool 122 | open var syncGridAlertEnabled: Bool 123 | open var breakerAlertEnabled: Bool 124 | 125 | enum CodingKeys: String, CodingKey { 126 | case stormModeEnabled = "storm_mode_enabled" 127 | case syncGridAlertEnabled = "sync_grid_alert_enabled" 128 | case breakerAlertEnabled = "breaker_alert_enabled" 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/BatteryPowerHistory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryPowerHistory.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - BatteryPowerHistory 12 | open class BatteryPowerHistory: Codable { 13 | open var serialNumber: String 14 | open var timeSeries: [TimeSeries] 15 | open var selfConsumptionData: [SelfConsumptionDatum] 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case serialNumber = "serial_number" 19 | case timeSeries = "time_series" 20 | case selfConsumptionData = "self_consumption_data" 21 | } 22 | 23 | // MARK: - SelfConsumptionDatum 24 | open class SelfConsumptionDatum: Codable { 25 | open var timestamp: Date 26 | open var solar: Double 27 | open var battery: Double 28 | } 29 | 30 | // MARK: - TimeSeries 31 | open class TimeSeries: Codable { 32 | open var timestamp: Date 33 | open var solarPower: Double 34 | open var batteryPower: Double 35 | open var gridPower: Double 36 | open var gridServicesPower: Double 37 | open var generatorPower: Double 38 | 39 | enum CodingKeys: String, CodingKey { 40 | case timestamp 41 | case solarPower = "solar_power" 42 | case batteryPower = "battery_power" 43 | case gridPower = "grid_power" 44 | case gridServicesPower = "grid_services_power" 45 | case generatorPower = "generator_power" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/BatteryStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryStatus.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - BatteryStatus 12 | open class BatteryStatus: Codable { 13 | open var siteName: String 14 | open var id: String 15 | open var energyLeft: Double 16 | open var totalPackEnergy: Double 17 | open var percentageCharged: Double 18 | open var batteryPower: Double 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case siteName = "site_name" 22 | case id 23 | case energyLeft = "energy_left" 24 | case totalPackEnergy = "total_pack_energy" 25 | case percentageCharged = "percentage_charged" 26 | case batteryPower = "battery_power" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ChargeAmpsCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChargeAmpsCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by João Nunes on 30/10/2021. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ChargeAmpsCommandOptions: Encodable { 12 | 13 | open var amps: Int 14 | 15 | public init(amps: Int) { 16 | self.amps = amps 17 | } 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case amps = "charging_amps" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ChargeHistory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChargeHistory.swift 3 | // TeslaSwiftDemoTests 4 | // 5 | // Created by João Nunes on 29/10/2023. 6 | // Copyright © 2023 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct ChargeHistory: Codable { 12 | let screenTitle: String 13 | let screenSubtitle: String 14 | let totalCharged: Charged 15 | let totalChargedBreakdown: Breakdown 16 | let chargingHistoryGraph: Graph 17 | let setRatePlan: RatePlan 18 | let totalChargedEnergyBreakdown: Breakdown 19 | let chargingTips: Tips 20 | let timePeriodLimit: TimePeriodLimit 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case screenTitle = "screen_title" 24 | case screenSubtitle = "screen_subtitle" 25 | case totalCharged = "total_charged" 26 | case totalChargedBreakdown = "total_charged_breakdown" 27 | case chargingHistoryGraph = "charging_history_graph" 28 | case setRatePlan = "set_rate_plan" 29 | case totalChargedEnergyBreakdown = "total_charged_energy_breakdown" 30 | case chargingTips = "charging_tips" 31 | case timePeriodLimit = "time_period_limit" 32 | } 33 | 34 | public struct Charged: Codable { 35 | let value: String 36 | let rawValue: Int 37 | let afterAdornment: String 38 | let title: String 39 | 40 | enum CodingKeys: String, CodingKey { 41 | case value 42 | case rawValue = "raw_value" 43 | case afterAdornment = "after_adornment" 44 | case title 45 | } 46 | } 47 | 48 | public struct Breakdown: Codable { 49 | let home, superCharger, other, work: BreakdownItem 50 | 51 | enum CodingKeys: String, CodingKey { 52 | case home 53 | case superCharger = "super_charger" 54 | case other 55 | case work 56 | } 57 | } 58 | 59 | public struct BreakdownItem: Codable { 60 | let value: String 61 | let rawValue: Int? 62 | let afterAdornment: String 63 | let subTitle: String? 64 | 65 | enum CodingKeys: String, CodingKey { 66 | case value 67 | case rawValue = "raw_value" 68 | case afterAdornment = "after_adornment" 69 | case subTitle = "sub_title" 70 | } 71 | } 72 | 73 | struct Graph: Codable { 74 | let dataPoints: [DataPoint] 75 | let period: Period 76 | let interval: Int 77 | let xLabels: [XLabel] 78 | let yLabels: [YLabel] 79 | let horizontalGridLines: [Double] 80 | let verticalGridLines: [Double] 81 | let discreteX: Bool 82 | let yRangeMax: Double 83 | let XDomainMin: String? 84 | let XDomainMax: String? 85 | 86 | enum CodingKeys: String, CodingKey { 87 | case dataPoints = "data_points" 88 | case period 89 | case interval 90 | case xLabels = "x_labels" 91 | case yLabels = "y_labels" 92 | case horizontalGridLines = "horizontal_grid_lines" 93 | case verticalGridLines = "vertical_grid_lines" 94 | case discreteX = "discrete_x" 95 | case yRangeMax = "y_range_max" 96 | case XDomainMin = "XDomainMin" 97 | case XDomainMax = "XDomainMax" 98 | } 99 | } 100 | 101 | struct Period: Codable { 102 | let startTimestamp: Timestamp 103 | let endTimestamp: Timestamp 104 | 105 | enum CodingKeys: String, CodingKey { 106 | case startTimestamp = "start_timestamp" 107 | case endTimestamp = "end_timestamp" 108 | } 109 | } 110 | 111 | struct XLabel: Codable { 112 | let value: String 113 | let rawValue: Int 114 | 115 | enum CodingKeys: String, CodingKey { 116 | case value 117 | case rawValue = "raw_value" 118 | } 119 | } 120 | 121 | struct YLabel: Codable { 122 | let value: String 123 | let rawValue: Double? 124 | let afterAdornment: String? 125 | 126 | enum CodingKeys: String, CodingKey { 127 | case value 128 | case rawValue = "raw_value" 129 | case afterAdornment = "after_adornment" 130 | } 131 | } 132 | 133 | public struct DataPoint: Codable { 134 | let timestamp: TimestampContainer 135 | let values: [Value] 136 | } 137 | 138 | public struct TimestampContainer: Codable { 139 | let timestamp: Timestamp 140 | let displayString: String 141 | 142 | enum CodingKeys: String, CodingKey { 143 | case timestamp 144 | case displayString = "display_string" 145 | } 146 | } 147 | 148 | public struct Timestamp: Codable { 149 | let seconds: Int 150 | } 151 | 152 | public struct Value: Codable { 153 | let value: String 154 | let rawValue: Double? 155 | let afterAdornment: String 156 | let title: String? 157 | let subTitle: String? 158 | 159 | enum CodingKeys: String, CodingKey { 160 | case value 161 | case rawValue = "raw_value" 162 | case afterAdornment = "after_adornment" 163 | case title 164 | case subTitle = "sub_title" 165 | } 166 | } 167 | 168 | struct RatePlan: Codable { 169 | let messageCard: MessageCard 170 | let primaryLink: PrimaryLink 171 | 172 | enum CodingKeys: String, CodingKey { 173 | case messageCard = "message_card" 174 | case primaryLink = "primary_link" 175 | } 176 | } 177 | 178 | struct Card: Codable { 179 | let id: String 180 | let title: String 181 | } 182 | 183 | struct PrimaryLink: Codable { 184 | let type: Int 185 | let destination: String 186 | let label: String 187 | } 188 | 189 | struct Tips: Codable { 190 | let title: String 191 | let imageUrl: String 192 | let textSections: [TextSection] 193 | let tips: [TipLink] 194 | 195 | enum CodingKeys: String, CodingKey { 196 | case title 197 | case imageUrl = "image_url" 198 | case textSections = "text_sections" 199 | case tips 200 | } 201 | } 202 | 203 | struct TextSection: Codable { 204 | let paragraphs: [String] 205 | } 206 | 207 | struct TipLink: Codable { 208 | let link: LinkIcon 209 | let section: Section 210 | } 211 | 212 | struct LinkIcon: Codable { 213 | let link: Link 214 | let leftIcon: String 215 | let rightIcon: String 216 | 217 | enum CodingKeys: String, CodingKey { 218 | case link 219 | case leftIcon = "left_icon" 220 | case rightIcon = "right_icon" 221 | } 222 | } 223 | 224 | struct Link: Codable { 225 | let type: Int 226 | let destination: String 227 | let label: String 228 | } 229 | 230 | struct Section: Codable { 231 | let title: String 232 | let tips: [Tip] 233 | } 234 | 235 | struct Tip: Codable { 236 | let title: String 237 | let description: String 238 | let media: Media 239 | } 240 | 241 | struct Media: Codable { 242 | let type: Int 243 | let source: String 244 | let resizeMode: Int 245 | 246 | enum CodingKeys: String, CodingKey { 247 | case type 248 | case source 249 | case resizeMode = "resize_mode" 250 | } 251 | } 252 | 253 | public struct SetRatePlan: Codable { 254 | let messageCard: MessageCard 255 | let primaryLink: PrimaryLink 256 | } 257 | 258 | public struct MessageCard: Codable { 259 | let card: Card 260 | let imageID: String 261 | 262 | enum CodingKeys: String, CodingKey { 263 | case card 264 | case imageID = "image_id" 265 | } 266 | } 267 | 268 | public struct TimePeriodLimit: Codable { 269 | let ownershipStart: TimestampContainer 270 | let featureStart: TimestampContainer 271 | 272 | enum CodingKeys: String, CodingKey { 273 | case ownershipStart = "ownership_start" 274 | case featureStart = "feature_start" 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ChargeLimitPercentageCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChargeLimitPercentageCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 10/11/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ChargeLimitPercentageCommandOptions: Encodable { 12 | open var percent: Int? 13 | 14 | public init(limit: Int) { 15 | percent = limit 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case percent 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Codable+JSONString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Codable+JSONString.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 23/09/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Encodable { 12 | 13 | var jsonString: String? { 14 | let encoder = teslaJSONEncoder 15 | guard let data = try? encoder.encode(self) else { return nil } 16 | return String(data: data, encoding: String.Encoding.utf8) 17 | } 18 | 19 | } 20 | 21 | public extension String { 22 | func decodeJSON() -> T? { 23 | guard let data = self.data(using: String.Encoding.utf8) else { return nil } 24 | return try? teslaJSONDecoder.decode(T.self, from: data) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/CommandResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandResponse.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 05/04/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class CommandResponse: Decodable { 12 | 13 | private struct Response: Decodable { 14 | var result: Bool? 15 | var reason: String? 16 | } 17 | private var response: Response 18 | 19 | open var result: Bool? { return response.result } 20 | open var reason: String? { return response.reason } 21 | 22 | init() { 23 | response = Response() 24 | } 25 | 26 | enum CodingKeys: String, CodingKey { 27 | case response 28 | } 29 | 30 | required public init(from decoder: Decoder) throws { 31 | 32 | let container = try decoder.container(keyedBy: CodingKeys.self) 33 | response = (try? container.decode(Response.self, forKey: .response)) ?? Response() 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Distance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Distance.swift 3 | // Pods 4 | // 5 | // Created by Jacob Holland on 7/20/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Distance: Codable { 12 | public var value: Measurement 13 | 14 | public init(miles: Double?) { 15 | let tempValue = miles ?? 0.0 16 | value = Measurement(value: tempValue, unit: UnitLength.miles) 17 | } 18 | public init(kms: Double) { 19 | value = Measurement(value: kms, unit: UnitLength.kilometers) 20 | } 21 | 22 | public init(from decoder: Decoder) throws { 23 | let container = try decoder.singleValueContainer() 24 | if let tempValue = try? container.decode(Double.self) { 25 | value = Measurement(value: tempValue, unit: UnitLength.miles) 26 | } else { 27 | value = Measurement(value: 0, unit: UnitLength.miles) 28 | } 29 | } 30 | 31 | public func encode(to encoder: Encoder) throws { 32 | 33 | var container = encoder.singleValueContainer() 34 | try container.encode(value.converted(to: .miles).value) 35 | 36 | } 37 | 38 | public var miles: Double { return value.converted(to: .miles).value } 39 | public var kms: Double { return value.converted(to: .kilometers).value } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/DriveState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrivingPosition.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 14/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | open class DriveState: Codable { 13 | public enum ShiftState: String, Codable { 14 | case drive = "D" 15 | case park = "P" 16 | case reverse = "R" 17 | case neutral = "N" 18 | } 19 | 20 | open var shiftState: ShiftState? 21 | 22 | open var speed: CLLocationSpeed? 23 | open var latitude: CLLocationDegrees? 24 | open var longitude: CLLocationDegrees? 25 | open var heading: CLLocationDirection? 26 | open var nativeLatitude: CLLocationDegrees? 27 | open var nativeLongitude: CLLocationDegrees? 28 | private var nativeLocationSupportedBool: Int? 29 | open var nativeLocationSupported: Bool { return nativeLocationSupportedBool == 1 } 30 | open var nativeType: String? 31 | 32 | open var date: Date? 33 | open var timeStamp: Double? 34 | open var power: Int? 35 | 36 | open var activeRouteLatitude: CLLocationDegrees? 37 | open var activeRouteLongitude: CLLocationDegrees? 38 | open var activeRouteTrafficMinutesDelay: Double? 39 | open var activeRouteDestination: String? 40 | open var activeRouteEnergyAtArrival: Int? 41 | open var activeRouteMilesToArrival: Double? 42 | open var activeRouteMinutesToArrival: Double? 43 | 44 | open var position: CLLocation? { 45 | if let latitude = latitude, 46 | let longitude = longitude, 47 | let heading = heading, 48 | let date = date { 49 | let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) 50 | return CLLocation(coordinate: coordinate, 51 | altitude: 0.0, horizontalAccuracy: 0.0, verticalAccuracy: 0.0, 52 | course: heading, 53 | speed: speed ?? 0, 54 | timestamp: date) 55 | 56 | } 57 | return nil 58 | } 59 | 60 | enum CodingKeys: String, CodingKey { 61 | case shiftState = "shift_state" 62 | case speed = "speed" 63 | case latitude = "latitude" 64 | case longitude = "longitude" 65 | case power 66 | case heading = "heading" 67 | case date = "gps_as_of" 68 | case timeStamp = "timestamp" 69 | case nativeLatitude = "native_latitude" 70 | case nativeLongitude = "native_longitude" 71 | case nativeLocationSupportedBool = "native_location_supported" 72 | case nativeType = "native_type" 73 | case activeRouteLatitude = "active_route_latitude" 74 | case activeRouteLongitude = "active_route_longitude" 75 | case activeRouteTrafficMinutesDelay = "active_route_traffic_minutes_delay" 76 | case activeRouteDestination = "active_route_destination" 77 | case activeRouteEnergyAtArrival = "active_route_energy_at_arrival" 78 | case activeRouteMilesToArrival = "active_route_miles_to_arrival" 79 | case activeRouteMinutesToArrival = "active_route_minutes_to_arrival" 80 | } 81 | 82 | required public init(from decoder: Decoder) throws { 83 | let container = try decoder.container(keyedBy: CodingKeys.self) 84 | 85 | shiftState = try? container.decode(ShiftState.self, forKey: .shiftState) 86 | 87 | speed = try? container.decode(CLLocationSpeed.self, forKey: .speed) 88 | latitude = try? container.decode(CLLocationDegrees.self, forKey: .latitude) 89 | longitude = try? container.decode(CLLocationDegrees.self, forKey: .longitude) 90 | heading = try? container.decode(CLLocationDirection.self, forKey: .heading) 91 | nativeLatitude = try? container.decode(CLLocationDegrees.self, forKey: .nativeLatitude) 92 | nativeLongitude = try? container.decode(CLLocationDegrees.self, forKey: .nativeLongitude) 93 | nativeLocationSupportedBool = try? container.decode(Int.self, forKey: .nativeLocationSupportedBool) 94 | 95 | nativeType = try? container.decode(String.self, forKey: .nativeType) 96 | 97 | date = try? container.decode(Date.self, forKey: .date) 98 | timeStamp = try? container.decode(Double.self, forKey: .timeStamp) 99 | power = try? container.decode(Int.self, forKey: .power) 100 | 101 | activeRouteLatitude = try? container.decode(CLLocationDegrees.self, forKey: .activeRouteLatitude) 102 | activeRouteLongitude = try? container.decode(CLLocationDegrees.self, forKey: .activeRouteLongitude) 103 | activeRouteTrafficMinutesDelay = try? container.decode(Double.self, forKey: .activeRouteTrafficMinutesDelay) 104 | activeRouteDestination = try? container.decode(String.self, forKey: .activeRouteDestination) 105 | activeRouteEnergyAtArrival = try? container.decode(Int.self, forKey: .activeRouteEnergyAtArrival) 106 | activeRouteMilesToArrival = try? container.decode(Double.self, forKey: .activeRouteMilesToArrival) 107 | activeRouteMinutesToArrival = try? container.decode(Double.self, forKey: .activeRouteMinutesToArrival) 108 | } 109 | 110 | public func encode(to encoder: Encoder) throws { 111 | var container = encoder.container(keyedBy: CodingKeys.self) 112 | 113 | try container.encodeIfPresent(shiftState, forKey: .shiftState) 114 | 115 | try container.encodeIfPresent(speed, forKey: .speed) 116 | try container.encodeIfPresent(latitude, forKey: .latitude) 117 | try container.encodeIfPresent(longitude, forKey: .longitude) 118 | try container.encodeIfPresent(heading, forKey: .heading) 119 | try container.encodeIfPresent(nativeLatitude, forKey: .nativeLatitude) 120 | try container.encodeIfPresent(nativeLongitude, forKey: .nativeLongitude) 121 | try container.encodeIfPresent(nativeLocationSupportedBool, forKey: .nativeLocationSupportedBool) 122 | 123 | try container.encodeIfPresent(nativeType, forKey: .nativeType) 124 | 125 | try container.encodeIfPresent(date, forKey: .date) 126 | try container.encodeIfPresent(timeStamp, forKey: .timeStamp) 127 | try container.encodeIfPresent(power, forKey: .power) 128 | 129 | try container.encodeIfPresent(activeRouteLatitude, forKey: .activeRouteLatitude) 130 | try container.encodeIfPresent(activeRouteLongitude, forKey: .activeRouteLongitude) 131 | try container.encodeIfPresent(activeRouteTrafficMinutesDelay, forKey: .activeRouteTrafficMinutesDelay) 132 | try container.encodeIfPresent(activeRouteDestination, forKey: .activeRouteDestination) 133 | try container.encodeIfPresent(activeRouteEnergyAtArrival, forKey: .activeRouteEnergyAtArrival) 134 | try container.encodeIfPresent(activeRouteMilesToArrival, forKey: .activeRouteMilesToArrival) 135 | try container.encodeIfPresent(activeRouteMinutesToArrival, forKey: .activeRouteMinutesToArrival) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/EnergySite.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnergySite.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - EnergySite 12 | open class EnergySite: Codable { 13 | 14 | // Unique to EnergySite 15 | open var id: String? 16 | public var batteryId: BatteryId? { 17 | guard let id else { return nil } 18 | 19 | return BatteryId(id: id) 20 | } 21 | open var energySiteID: Decimal 22 | public var siteId: SiteId { 23 | SiteId(id: energySiteID) 24 | } 25 | open var siteName: String? 26 | open var assetSiteID: String? 27 | open var components: Components? 28 | open var goOffGridTestBannerEnabled: Bool? 29 | open var stormModeEnabled: Bool? 30 | open var powerwallOnboardingSettingsSet: Bool? 31 | open var powerwallTeslaElectricInterestedIn: String? 32 | open var vppTourEnabled: Bool? 33 | 34 | // Also available in EnergySiteStatus 35 | open var resourceType: String 36 | open var warpSiteNumber: String? 37 | open var gatewayID: String? 38 | open var energyLeft: Double? 39 | open var totalPackEnergy: Double? 40 | open var percentageCharged: Double? 41 | open var batteryType: String? 42 | open var backupCapable: Bool? 43 | open var batteryPower: Double? 44 | open var syncGridAlertEnabled: Bool? 45 | open var breakerAlertEnabled: Bool? 46 | 47 | enum CodingKeys: String, CodingKey { 48 | case siteName = "site_name" 49 | case energySiteID = "energy_site_id" 50 | case resourceType = "resource_type" 51 | case warpSiteNumber = "warp_site_number" 52 | case id 53 | case gatewayID = "gateway_id" 54 | case assetSiteID = "asset_site_id" 55 | case energyLeft = "energy_left" 56 | case totalPackEnergy = "total_pack_energy" 57 | case percentageCharged = "percentage_charged" 58 | case batteryType = "battery_type" 59 | case backupCapable = "backup_capable" 60 | case batteryPower = "battery_power" 61 | case syncGridAlertEnabled = "sync_grid_alert_enabled" 62 | case breakerAlertEnabled = "breaker_alert_enabled" 63 | case components 64 | case goOffGridTestBannerEnabled = "go_off_grid_test_banner_enabled" 65 | case stormModeEnabled = "storm_mode_enabled" 66 | case powerwallOnboardingSettingsSet = "powerwall_onboarding_settings_set" 67 | case powerwallTeslaElectricInterestedIn = "powerwall_tesla_electric_interested_in" 68 | case vppTourEnabled = "vpp_tour_enabled" 69 | } 70 | 71 | // MARK: - Components 72 | open class Components: Codable { 73 | open var battery: Bool 74 | open var batteryType: String? 75 | open var solar: Bool 76 | open var solarType: String? 77 | open var grid: Bool 78 | open var loadMeter: Bool 79 | open var marketType: String 80 | open var wallConnectors: [WallConnectors]? 81 | 82 | enum CodingKeys: String, CodingKey { 83 | case battery 84 | case batteryType = "battery_type" 85 | case solar 86 | case solarType = "solar_type" 87 | case grid 88 | case loadMeter = "load_meter" 89 | case marketType = "market_type" 90 | case wallConnectors = "wall_connectors" 91 | } 92 | } 93 | } 94 | 95 | open class WallConnectors: Codable { 96 | open var deviceId: String? 97 | open var din: String? 98 | open var serialNumber: String? 99 | open var partNumber: String? 100 | open var partType: Int? 101 | open var partName: String? 102 | open var isActive: Bool? 103 | 104 | enum CodingKeys: String, CodingKey { 105 | case deviceId = "device_id" 106 | case din 107 | case serialNumber = "serial_number" 108 | case partNumber = "part_number" 109 | case partType = "part_type" 110 | case partName = "part_name" 111 | case isActive = "is_active" 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/EnergySiteHistory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnergySiteHistory.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - EnergySiteHistory 12 | open class EnergySiteHistory: Codable { 13 | open var serialNumber: String 14 | open var period: Period 15 | open var timeSeries: [TimeSeries] 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case serialNumber = "serial_number" 19 | case period 20 | case timeSeries = "time_series" 21 | } 22 | 23 | public enum Period: String, Codable { 24 | case day, week, month, year 25 | } 26 | 27 | // MARK: - TimeSeries 28 | open class TimeSeries: Codable { 29 | open var timestamp: Date 30 | open var solarEnergyExported: Double 31 | open var generatorEnergyExported: Double 32 | open var gridEnergyImported: Double 33 | open var gridServicesEnergyImported: Double 34 | open var gridServicesEnergyExported: Double 35 | open var gridEnergyExportedFromSolar: Double 36 | open var gridEnergyExportedFromGenerator: Double 37 | open var gridEnergyExportedFromBattery: Double 38 | open var batteryEnergyExported: Double 39 | open var batteryEnergyImportedFromGrid: Double 40 | open var batteryEnergyImportedFromSolar: Double 41 | open var batteryEnergyImportedFromGenerator: Double 42 | open var consumerEnergyImportedFromGrid: Double 43 | open var consumerEnergyImportedFromSolar: Double 44 | open var consumerEnergyImportedFromBattery: Double 45 | open var consumerEnergyImportedFromGenerator: Double 46 | 47 | enum CodingKeys: String, CodingKey { 48 | case timestamp 49 | case solarEnergyExported = "solar_energy_exported" 50 | case generatorEnergyExported = "generator_energy_exported" 51 | case gridEnergyImported = "grid_energy_imported" 52 | case gridServicesEnergyImported = "grid_services_energy_imported" 53 | case gridServicesEnergyExported = "grid_services_energy_exported" 54 | case gridEnergyExportedFromSolar = "grid_energy_exported_from_solar" 55 | case gridEnergyExportedFromGenerator = "grid_energy_exported_from_generator" 56 | case gridEnergyExportedFromBattery = "grid_energy_exported_from_battery" 57 | case batteryEnergyExported = "battery_energy_exported" 58 | case batteryEnergyImportedFromGrid = "battery_energy_imported_from_grid" 59 | case batteryEnergyImportedFromSolar = "battery_energy_imported_from_solar" 60 | case batteryEnergyImportedFromGenerator = "battery_energy_imported_from_generator" 61 | case consumerEnergyImportedFromGrid = "consumer_energy_imported_from_grid" 62 | case consumerEnergyImportedFromSolar = "consumer_energy_imported_from_solar" 63 | case consumerEnergyImportedFromBattery = "consumer_energy_imported_from_battery" 64 | case consumerEnergyImportedFromGenerator = "consumer_energy_imported_from_generator" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/EnergySiteInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnergySiteInfo.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - EnergySiteInfo 12 | open class EnergySiteInfo: Codable { 13 | open var id: String 14 | open var siteName: String? 15 | open var siteNumber: String? 16 | open var backupReservePercent: Double? 17 | open var defaultRealMode: String? 18 | open var installationDate: Date 19 | open var version: String? 20 | open var batteryCount: Int? 21 | open var nameplatePower: Double? 22 | open var nameplateEnergy: Double? 23 | open var installationTimeZone: String 24 | open var offGridVehicleChargingReservePercent: Double? 25 | 26 | open var userSettings: UserSettings 27 | open var touSettings: TOUSettings? 28 | open var components: Components 29 | // tariff_content_v2 30 | 31 | enum CodingKeys: String, CodingKey { 32 | case id 33 | case siteName = "site_name" 34 | case siteNumber = "site_number" 35 | case backupReservePercent = "backup_reserve_percent" 36 | case defaultRealMode = "default_real_mode" 37 | case installationDate = "installation_date" 38 | case userSettings = "user_settings" 39 | case components 40 | case version 41 | case batteryCount = "battery_count" 42 | case touSettings = "tou_settings" 43 | case nameplatePower = "nameplate_power" 44 | case nameplateEnergy = "nameplate_energy" 45 | case installationTimeZone = "installation_time_zone" 46 | case offGridVehicleChargingReservePercent = "off_grid_vehicle_charging_reserve_percent" 47 | } 48 | 49 | // MARK: - Components 50 | open class Components: Codable { 51 | open var solar: Bool 52 | open var solarType: String? 53 | open var battery: Bool 54 | open var grid: Bool 55 | open var backup: Bool 56 | open var gateway: String 57 | open var loadMeter: Bool 58 | open var touCapable: Bool? 59 | open var stormModeCapable: Bool? 60 | open var flexEnergyRequestCapable: Bool? 61 | open var carChargingDataSupported: Bool? 62 | open var offGridVehicleChargingReserveSupported: Bool 63 | open var vehicleChargingPerformanceViewEnabled: Bool 64 | open var vehicleChargingSolarOffsetViewEnabled: Bool 65 | open var batterySolarOffsetViewEnabled: Bool 66 | open var setIslandingModeEnabled: Bool? 67 | open var backupTimeRemainingEnabled: Bool? 68 | open var batteryType: String? 69 | open var configurable: Bool? 70 | open var gridServicesEnabled: Bool? 71 | open var energyServiceSelfSchedulingEnabled: Bool? 72 | open var wallConnectors: [WallConnectors]? 73 | open var nbtSupported: Bool? 74 | open var systemAlertsEnabled: Bool? 75 | 76 | enum CodingKeys: String, CodingKey { 77 | case solar 78 | case solarType = "solar_type" 79 | case battery, grid, backup, gateway 80 | case loadMeter = "load_meter" 81 | case touCapable = "tou_capable" 82 | case stormModeCapable = "storm_mode_capable" 83 | case flexEnergyRequestCapable = "flex_energy_request_capable" 84 | case carChargingDataSupported = "car_charging_data_supported" 85 | case offGridVehicleChargingReserveSupported = "off_grid_vehicle_charging_reserve_supported" 86 | case vehicleChargingPerformanceViewEnabled = "vehicle_charging_performance_view_enabled" 87 | case vehicleChargingSolarOffsetViewEnabled = "vehicle_charging_solar_offset_view_enabled" 88 | case batterySolarOffsetViewEnabled = "battery_solar_offset_view_enabled" 89 | case setIslandingModeEnabled = "set_islanding_mode_enabled" 90 | case backupTimeRemainingEnabled = "backup_time_remaining_enabled" 91 | case batteryType = "battery_type" 92 | case configurable 93 | case gridServicesEnabled = "grid_services_enabled" 94 | case energyServiceSelfSchedulingEnabled = "energy_service_self_scheduling_enabled" 95 | case wallConnectors = "wall_connectors" 96 | case nbtSupported = "nbt_supported" 97 | case systemAlertsEnabled = "system_alerts_enabled" 98 | } 99 | } 100 | 101 | // MARK: - TouSettings 102 | open class TOUSettings: Codable { 103 | open var optimizationStrategy: String 104 | open var schedule: [Schedule] 105 | 106 | enum CodingKeys: String, CodingKey { 107 | case optimizationStrategy = "optimization_strategy" 108 | case schedule 109 | } 110 | } 111 | 112 | // MARK: - Schedule 113 | open class Schedule: Codable { 114 | open var target: String 115 | open var weekDays: [Int] 116 | open var startSeconds: Int 117 | open var endSeconds: Int 118 | 119 | enum CodingKeys: String, CodingKey { 120 | case target 121 | case weekDays = "week_days" 122 | case startSeconds = "start_seconds" 123 | case endSeconds = "end_seconds" 124 | } 125 | } 126 | 127 | // MARK: - UserSettings 128 | open class UserSettings: Codable { 129 | open var stormModeEnabled: Bool? 130 | open var syncGridAlertEnabled: Bool? 131 | open var breakerAlertEnabled: Bool? 132 | open var goOffGridTestBannerEnabled: Bool? 133 | open var powerwallOnboardingSettingsSet: Bool? 134 | open var powerwallTeslaElectricInterestedIn: Bool? 135 | open var vppTourEnabled: Bool? 136 | open var offGridVehicleChargingEnabled: Bool? 137 | 138 | enum CodingKeys: String, CodingKey { 139 | case stormModeEnabled = "storm_mode_enabled" 140 | case syncGridAlertEnabled = "sync_grid_alert_enabled" 141 | case breakerAlertEnabled = "breaker_alert_enabled" 142 | case goOffGridTestBannerEnabled = "go_off_grid_test_banner_enabled" 143 | case powerwallOnboardingSettingsSet = "powerwall_onboarding_settings_set" 144 | case powerwallTeslaElectricInterestedIn = "powerwall_tesla_electric_interested_in" 145 | case vppTourEnabled = "vpp_tour_enabled" 146 | case offGridVehicleChargingEnabled = "off_grid_vehicle_charging_enabled" 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/EnergySiteLiveStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnergySiteLiveStatus.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - EnergySiteLiveStatus 12 | open class EnergySiteLiveStatus: Codable { 13 | open var solarPower: Double? 14 | open var energyLeft: Double? 15 | open var totalPackEnergy: Double? 16 | open var percentageCharged: Double? 17 | open var backupCapable: Bool? 18 | open var batteryPower: Double? 19 | open var loadPower: Double? 20 | open var gridStatus: String? 21 | open var gridServicesActive: Bool? 22 | open var gridPower: Double? 23 | open var gridServicesPower: Double? 24 | open var generatorPower: Double? 25 | open var islandStatus: String? 26 | open var stormModeActive: Bool? 27 | open var wallConnectors: [WallConnector] 28 | open var timestamp: Date 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case solarPower = "solar_power" 32 | case energyLeft = "energy_left" 33 | case totalPackEnergy = "total_pack_energy" 34 | case percentageCharged = "percentage_charged" 35 | case backupCapable = "backup_capable" 36 | case batteryPower = "battery_power" 37 | case loadPower = "load_power" 38 | case gridStatus = "grid_status" 39 | case gridServicesActive = "grid_services_active" 40 | case gridPower = "grid_power" 41 | case gridServicesPower = "grid_services_power" 42 | case generatorPower = "generator_power" 43 | case islandStatus = "island_status" 44 | case stormModeActive = "storm_mode_active" 45 | case wallConnectors = "wall_connectors" 46 | case timestamp 47 | } 48 | 49 | open class WallConnector: Codable { 50 | open var din: String? 51 | open var vin: String? 52 | open var wallConnectorState: Int? 53 | open var wallConnectorFaultState: Int? 54 | open var wallConnectorPower: Int? 55 | open var ocppStatus: Int? 56 | open var powershareSessionState: Int? 57 | 58 | enum CodingKeys: String, CodingKey { 59 | case din 60 | case vin 61 | case wallConnectorState = "wall_connector_state" 62 | case wallConnectorFaultState = "wall_connector_fault_state" 63 | case wallConnectorPower = "wall_connector_power" 64 | case ocppStatus = "ocpp_status" 65 | case powershareSessionState = "powershare_session_state" 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/EnergySiteStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnergySiteStatus.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - EnergySiteStatus 12 | open class EnergySiteStatus: Codable { 13 | open var resourceType: String 14 | open var siteName: String 15 | open var assetSiteId: String? 16 | open var goOffGridTestBannerEnabled: Bool? 17 | open var stormModeEnabled: Bool? 18 | open var powerwallOnboardingSettingsSet: Bool? 19 | open var powerwallTeslaElectricInterestedIn: Bool? 20 | open var vppTourEnabled: Bool? 21 | open var solarPower: Int? 22 | open var gatewayID: String? 23 | open var energyLeft: Double? 24 | open var totalPackEnergy: Double? 25 | open var percentageCharged: Double? 26 | open var batteryType: String? 27 | open var backupCapable: Bool? 28 | open var batteryPower: Double? 29 | open var syncGridAlertEnabled: Bool? 30 | open var breakerAlertEnabled: Bool? 31 | 32 | enum CodingKeys: String, CodingKey { 33 | case resourceType = "resource_type" 34 | case siteName = "site_name" 35 | case solarPower = "solar_power" 36 | case assetSiteId = "asset_site_id" 37 | case goOffGridTestBannerEnabled = "go_off_grid_test_banner_enabled" 38 | case stormModeEnabled = "storm_mode_enabled" 39 | case powerwallOnboardingSettingsSet = "powerwall_onboarding_settings_set" 40 | case powerwallTeslaElectricInterestedIn = "powerwall_tesla_electric_interested_in" 41 | case vppTourEnabled = "vpp_tour_enabled" 42 | case gatewayID = "gateway_id" 43 | case energyLeft = "energy_left" 44 | case totalPackEnergy = "total_pack_energy" 45 | case percentageCharged = "percentage_charged" 46 | case batteryType = "battery_type" 47 | case backupCapable = "backup_capable" 48 | case batteryPower = "battery_power" 49 | case syncGridAlertEnabled = "sync_grid_alert_enabled" 50 | case breakerAlertEnabled = "breaker_alert_enabled" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ErrorMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorMessage.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 28/02/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ErrorMessage: Codable { 12 | 13 | open var error: String? 14 | open var description: String? 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case error = "error" 18 | case description = "error_description" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/GenericResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericResponse.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 24/06/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class Response: Decodable { 12 | open var response: T 13 | 14 | public init(response: T) { 15 | self.response = response 16 | } 17 | 18 | // MARK: Codable protocol 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case response 22 | } 23 | } 24 | 25 | open class ArrayResponse: Decodable { 26 | open var response: [T] = [] 27 | 28 | // MARK: Codable protocol 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case response 32 | } 33 | } 34 | 35 | open class BoolResponse: Decodable { 36 | open var response: Bool 37 | 38 | public init(response: Bool) { 39 | self.response = response 40 | } 41 | 42 | // MARK: Codable protocol 43 | 44 | enum CodingKeys: String, CodingKey { 45 | case response 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/GuiSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuiSettings.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 17/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class GuiSettings: Codable { 12 | open var distanceUnits: String? 13 | open var temperatureUnits: String? 14 | open var chargeRateUnits: String? 15 | open var time24Hours: Bool? 16 | open var rangeDisplay: String? 17 | open var timeStamp: Double? 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case distanceUnits = "gui_distance_units" 21 | case temperatureUnits = "gui_temperature_units" 22 | case chargeRateUnits = "gui_charge_rate_units" 23 | case time24Hours = "gui_24_hour_time" 24 | case rangeDisplay = "gui_range_display" 25 | case timeStamp = "timestamp" 26 | } 27 | 28 | required public init(from decoder: Decoder) throws { 29 | let container = try decoder.container(keyedBy: CodingKeys.self) 30 | distanceUnits = try? container.decode(String.self, forKey: .distanceUnits) 31 | temperatureUnits = try? container.decode(String.self, forKey: .temperatureUnits) 32 | chargeRateUnits = try? container.decode(String.self, forKey: .chargeRateUnits) 33 | time24Hours = try? container.decode(Bool.self, forKey: .time24Hours) 34 | rangeDisplay = try? container.decode(String.self, forKey: .rangeDisplay) 35 | timeStamp = try? container.decode(Double.self, forKey: .timeStamp) 36 | } 37 | 38 | public func encode(to encoder: Encoder) throws { 39 | var container = encoder.container(keyedBy: CodingKeys.self) 40 | try container.encodeIfPresent(distanceUnits, forKey: .distanceUnits) 41 | try container.encodeIfPresent(temperatureUnits, forKey: .temperatureUnits) 42 | try container.encodeIfPresent(chargeRateUnits, forKey: .chargeRateUnits) 43 | try container.encodeIfPresent(time24Hours, forKey: .time24Hours) 44 | try container.encodeIfPresent(rangeDisplay, forKey: .rangeDisplay) 45 | try container.encodeIfPresent(timeStamp, forKey: .timeStamp) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/HomeLinkCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeLinkCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 19/10/2019. 6 | // Copyright © 2019 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | open class HomeLinkCommandOptions: Encodable { 13 | 14 | open var latitude: CLLocationDegrees 15 | open var longitude: CLLocationDegrees 16 | 17 | public init(coordinates: CLLocation) { 18 | self.latitude = coordinates.coordinate.latitude 19 | self.longitude = coordinates.coordinate.longitude 20 | } 21 | 22 | enum CodingKeys: String, CodingKey { 23 | case latitude = "lat" 24 | case longitude = "lon" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/MaxDefrostCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaxDefrostCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 19/10/2019. 6 | // Copyright © 2019 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class MaxDefrostCommandOptions: Encodable { 12 | open var on: Bool 13 | 14 | public init(state: Bool) { 15 | on = state 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case on 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Me.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Me.swift 3 | // TeslaSwiftDemoTests 4 | // 5 | // Created by João Nunes on 29/10/2023. 6 | // Copyright © 2023 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Me: Codable { 12 | public var email: String 13 | public var fullName: String 14 | public var profileImageUrl: String 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case email 18 | case fullName = "full_name" 19 | case profileImageUrl = "profile_image_url" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/NearbyChargingSites.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NearbyChargingSites.swift 3 | // TeslaSwift 4 | // 5 | // Created by Jordan Owens on 7/4/19. 6 | // Copyright © 2019 Jordan Owens. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | public protocol Charger { 13 | var name: String? { get } 14 | var type: String? { get } 15 | var distance: Distance? { get } 16 | var location: NearbyChargingSites.ChargerLocation? { get } 17 | } 18 | 19 | open class NearbyChargingSites: Codable { 20 | 21 | open var congestionSyncTimeUTCSecs: Int? 22 | open var destinationChargers: [DestinationCharger]? 23 | open var superchargers: [Supercharger]? 24 | open var timestamp: Double? 25 | 26 | enum CodingKeys: String, CodingKey { 27 | case congestionSyncTimeUTCSecs = "congestion_sync_time_utc_secs" 28 | case destinationChargers = "destination_charging" 29 | case superchargers = "superchargers" 30 | case timestamp = "timestamp" 31 | } 32 | 33 | required public init(from decoder: Decoder) throws { 34 | let container = try decoder.container(keyedBy: CodingKeys.self) 35 | congestionSyncTimeUTCSecs = try? container.decode(Int.self, forKey: .congestionSyncTimeUTCSecs) 36 | destinationChargers = try? container.decode([DestinationCharger].self, forKey: .destinationChargers) 37 | superchargers = try? container.decode([Supercharger].self, forKey: .superchargers) 38 | timestamp = try? container.decode(Double.self, forKey: .timestamp) 39 | } 40 | 41 | public func encode(to encoder: Encoder) throws { 42 | var container = encoder.container(keyedBy: CodingKeys.self) 43 | try container.encodeIfPresent(congestionSyncTimeUTCSecs, forKey: .congestionSyncTimeUTCSecs) 44 | try container.encodeIfPresent(destinationChargers, forKey: .destinationChargers) 45 | try container.encodeIfPresent(superchargers, forKey: .superchargers) 46 | try container.encodeIfPresent(timestamp, forKey: .timestamp) 47 | } 48 | 49 | public struct DestinationCharger: Codable, Charger { 50 | public var distance: Distance? 51 | public var location: ChargerLocation? 52 | public var name: String? 53 | public var type: String? 54 | 55 | enum CodingKeys: String, CodingKey { 56 | case distance = "distance_miles" 57 | case name = "name" 58 | case location = "location" 59 | case type = "type" 60 | } 61 | } 62 | 63 | public struct Supercharger: Codable, Charger { 64 | public var availableStalls: Int? 65 | public var distance: Distance? 66 | public var location: ChargerLocation? 67 | public var name: String? 68 | public var siteClosed: Bool? 69 | public var totalStalls: Int? 70 | public var type: String? 71 | 72 | enum CodingKeys: String, CodingKey { 73 | case availableStalls = "available_stalls" 74 | case distance = "distance_miles" 75 | case location = "location" 76 | case name = "name" 77 | case siteClosed = "site_closed" 78 | case totalStalls = "total_stalls" 79 | case type = "type" 80 | } 81 | } 82 | 83 | public struct ChargerLocation: Codable { 84 | public var latitude: CLLocationDegrees? 85 | public var longitude: CLLocationDegrees? 86 | 87 | enum CodingKeys: String, CodingKey { 88 | case latitude = "lat" 89 | case longitude = "long" 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/OpenTrunkOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenTrunkOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 16/04/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum OpenTrunkOptions: String, Codable { 12 | 13 | case rear 14 | case front 15 | 16 | enum CodingKeys: String, CodingKey { 17 | typealias RawValue = String 18 | 19 | case whichTrunk = "which_trunk" 20 | } 21 | 22 | public init(from decoder: Decoder) throws { 23 | let values = try decoder.container(keyedBy: CodingKeys.self) 24 | 25 | if let trunk = try? values.decode(String.self, forKey: .whichTrunk), 26 | trunk == "front" { 27 | self = .front 28 | } else { 29 | self = .rear 30 | } 31 | } 32 | 33 | public func encode(to encoder: Encoder) throws { 34 | var container = encoder.container(keyedBy: CodingKeys.self) 35 | 36 | switch self { 37 | case .rear: 38 | try container.encode("rear", forKey: .whichTrunk) 39 | case .front: 40 | try container.encode("front", forKey: .whichTrunk) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Partner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Partner.swift 3 | // TeslaSwiftDemoTests 4 | // 5 | // Created by João Nunes on 29/10/2023. 6 | // Copyright © 2023 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct PartnerBody: Codable { 12 | let domain: String 13 | } 14 | 15 | public struct PartnerResponse: Codable { 16 | let response: PartnerResponseBody 17 | } 18 | 19 | public struct PartnerResponseBody: Codable { 20 | let domain: String 21 | let name: String 22 | let description: String 23 | let clientId: String 24 | let ca: String? 25 | let createdAt: Date 26 | let updatedAt: Date 27 | let enterpriseTier: String 28 | let publicKey: String 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case domain 32 | case name 33 | case description 34 | case clientId = "client_id" 35 | case ca 36 | case createdAt = "created_at" 37 | case updatedAt = "updated_at" 38 | case enterpriseTier = "enterprise_tier" 39 | case publicKey = "public_key" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Product.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Product.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/23/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class Product: Codable { 12 | open var vehicle: Vehicle? 13 | open var energySite: EnergySite? 14 | 15 | open var isValidProduct: Bool { vehicle != nil || energySite != nil} 16 | 17 | required public init(from decoder: Decoder) throws { 18 | let vehicleContainer = try decoder.container(keyedBy: Vehicle.CodingKeys.self) 19 | let energySiteContainer = try decoder.container(keyedBy: EnergySite.CodingKeys.self) 20 | 21 | if vehicleContainer.contains(Vehicle.CodingKeys.vehicleID) { 22 | self.vehicle = try Vehicle(from: decoder) 23 | self.energySite = nil 24 | } else if energySiteContainer.contains(EnergySite.CodingKeys.energySiteID) { 25 | self.vehicle = nil 26 | self.energySite = try EnergySite(from: decoder) 27 | } else { 28 | self.vehicle = nil 29 | self.energySite = nil 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Region.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Region.swift 3 | // TeslaSwiftDemoTests 4 | // 5 | // Created by João Nunes on 29/10/2023. 6 | // Copyright © 2023 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Region: Codable { 12 | public var region: String 13 | public var fleetApiBaseUrl: String 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case region 17 | case fleetApiBaseUrl = "fleet_api_base_url" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/RemoteSeatHeaterRequestOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteSeatHeaterRequestOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Jordan Owens on 2/17/19. 6 | // Copyright © 2019 Jordan Owens. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class RemoteSeatHeaterRequestOptions: Encodable { 12 | 13 | open var seat: HeatedSeat 14 | open var level: HeatLevel 15 | 16 | public init(seat: HeatedSeat, level: HeatLevel) { 17 | self.seat = seat 18 | self.level = level 19 | } 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case seat = "heater" 23 | case level = "level" 24 | } 25 | } 26 | 27 | public enum HeatedSeat: Int, Encodable { 28 | case driver = 0 29 | case passenger = 1 30 | case rearLeft = 2 31 | case rearLeftBack = 3 32 | case rearCenter = 4 33 | case rearRight = 5 34 | case rearRightBack = 6 35 | case thirdRowLeft = 7 36 | case thirdRowRight = 8 37 | } 38 | 39 | public enum HeatLevel: Int, Encodable { 40 | case off = 0 41 | case low = 1 42 | case mid = 2 43 | case high = 3 44 | } 45 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/RemoteStartDriveCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteStartDriveCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 10/11/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class RemoteStartDriveCommandOptions: Encodable { 12 | 13 | open var password: String? 14 | public init(password: String) { 15 | self.password = password 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case password 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/RemoteSteeringWheelHeaterRequestOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteSteeringWheelHeaterRequestOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Jordan Owens on 2/17/19. 6 | // Copyright © 2019 Jordan Owens. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class RemoteSteeringWheelHeaterRequestOptions: Encodable { 12 | open var on: Bool 13 | 14 | public init(on: Bool) { 15 | self.on = on 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case on 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ScheduledChargingCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScheduledChargingCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Philip Engberg on 07/11/2021. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ScheduledChargingCommandOptions: Encodable { 12 | 13 | open var enable: Bool 14 | open var time: Int 15 | 16 | public init(enable: Bool, time: Int) { 17 | self.enable = enable 18 | self.time = time 19 | } 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case enable 23 | case time 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ScheduledDepartureCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScheduledDepartureCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Philip Engberg on 07/11/2021. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ScheduledDepartureCommandOptions: Encodable { 12 | 13 | open var enable: Bool 14 | open var departureTime: Int 15 | open var preconditioningEnabled: Bool 16 | open var preconditioningWeekdaysOnly: Bool 17 | open var offPeakChargingEnabled: Bool 18 | open var endOffPeakTime: Int 19 | open var offPeakChargingWeekdaysOnly: Bool 20 | 21 | public init(enable: Bool, departureTime: Int, preconditioningEnabled: Bool, preconditioningWeekdaysOnly: Bool, offPeakChargingEnabled: Bool, endOffPeakTime: Int, offPeakChargingWeekdaysOnly: Bool) { 22 | self.enable = enable 23 | self.departureTime = departureTime 24 | self.preconditioningEnabled = preconditioningEnabled 25 | self.preconditioningWeekdaysOnly = preconditioningWeekdaysOnly 26 | self.offPeakChargingEnabled = offPeakChargingEnabled 27 | self.endOffPeakTime = endOffPeakTime 28 | self.offPeakChargingWeekdaysOnly = offPeakChargingWeekdaysOnly 29 | } 30 | 31 | enum CodingKeys: String, CodingKey { 32 | case enable 33 | case departureTime = "departure_time" 34 | case preconditioningEnabled = "preconditioning_enabled" 35 | case preconditioningWeekdaysOnly = "preconditioning_weekdays_only" 36 | case offPeakChargingEnabled = "off_peak_charging_enabled" 37 | case endOffPeakTime = "end_off_peak_time" 38 | case offPeakChargingWeekdaysOnly = "off_peak_charging_weekdays_only" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/SentryModeCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SentryModeCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Jordan Owens on 3/18/19. 6 | // Copyright © 2019 Jordan Owens. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class SentryModeCommandOptions: Encodable { 12 | open var on: Bool 13 | 14 | public init(activated: Bool) { 15 | self.on = activated 16 | } 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case on 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/SetSunRoofCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetSunRoofCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 10/11/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum RoofState: String, Codable { 12 | case close 13 | case vent 14 | } 15 | 16 | open class SetSunRoofCommandOptions: Encodable { 17 | 18 | open var state: RoofState 19 | open var percent: Int? 20 | public init(state: RoofState, percent: Int?) { 21 | self.state = state 22 | self.percent = percent 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/SetTemperatureCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetTemperatureCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 10/11/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class SetTemperatureCommandOptions: Encodable { 12 | 13 | open var driverTemp: Double? 14 | open var passengerTemp: Double? 15 | public init(driverTemperature: Double, passengerTemperature: Double) { 16 | driverTemp = driverTemperature 17 | passengerTemp = passengerTemperature 18 | } 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case driverTemp = "driver_temp" 22 | case passengerTemp = "passenger_temp" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ShareToVehicleOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareToVehicleOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Derek Johnson on 10/14/18. 6 | // 7 | 8 | import Foundation 9 | import CoreLocation 10 | 11 | open class ShareToVehicleOptions: Codable { 12 | 13 | public let type: String 14 | public let value: ShareToVehicleValue 15 | public let locale: String 16 | public let timestamp_ms: String 17 | 18 | public init(address: String) { 19 | self.value = ShareToVehicleValue(address: address) 20 | self.type = "share_ext_content_raw" 21 | self.locale = "en-US" 22 | self.timestamp_ms = "12345" 23 | } 24 | 25 | public init(coordinate: CLLocationCoordinate2D) { 26 | self.value = ShareToVehicleValue(coordinate: coordinate) 27 | self.type = "share_ext_content_raw" 28 | self.locale = "en-US" 29 | self.timestamp_ms = "12345" 30 | } 31 | 32 | public class ShareToVehicleValue: Codable { 33 | public let intentAction: String 34 | public let intentType: String 35 | public let intentText: String 36 | 37 | init(address: String) { 38 | self.intentText = "Place Name\n\(address)\n(123) 123-1234\nhttps://maps.google.com/?cid=12345" 39 | self.intentAction = "android.intent.action.SEND" 40 | self.intentType = "text%2F%0Aplain" 41 | } 42 | 43 | init(coordinate: CLLocationCoordinate2D) { 44 | self.intentText = "\(coordinate.latitude),\(coordinate.longitude)" 45 | self.intentAction = "android.intent.action.SEND" 46 | self.intentType = "text%2F%0Aplain" 47 | } 48 | 49 | enum CodingKeys: String, CodingKey { 50 | case intentAction = "android.intent.ACTION" 51 | case intentType = "android.intent.TYPE" 52 | case intentText = "android.intent.extra.TEXT" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/SoftwareUpdate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SoftwareUpdate.swift 3 | // TeslaSwift 4 | // 5 | // Created by Derek Johnson on 10/31/18. 6 | // Copyright © 2018 Derek Johnson. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class SoftwareUpdate: Codable { 12 | 13 | open var status: String? 14 | open var expectedDuration: Int? 15 | open var scheduledTime: Double? 16 | open var warningTimeRemaining: Double? 17 | open var downloadPercentage: Int? 18 | open var installPercentage: Int? 19 | open var version: String? 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case status = "status" 23 | case expectedDuration = "expected_duration_sec" 24 | case scheduledTime = "scheduled_time_ms" 25 | case warningTimeRemaining = "warning_time_remaining_ms" 26 | case downloadPercentage = "download_perc" 27 | case installPercentage = "install_perc" 28 | case version = "version" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Speed.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Speed.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 21/03/2020. 6 | // Copyright © 2020 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Speed: Codable { 12 | public var value: Measurement 13 | 14 | public init(milesPerHour: Double?) { 15 | let tempValue = milesPerHour ?? 0.0 16 | value = Measurement(value: tempValue, unit: UnitSpeed.milesPerHour) 17 | } 18 | public init(kilometersPerHour: Double) { 19 | value = Measurement(value: kilometersPerHour, unit: UnitSpeed.kilometersPerHour) 20 | } 21 | 22 | public init(from decoder: Decoder) throws { 23 | let container = try decoder.singleValueContainer() 24 | if let tempValue = try? container.decode(Double.self) { 25 | value = Measurement(value: tempValue, unit: UnitSpeed.milesPerHour) 26 | } else { 27 | value = Measurement(value: 0, unit: UnitSpeed.milesPerHour) 28 | } 29 | } 30 | 31 | public func encode(to encoder: Encoder) throws { 32 | 33 | var container = encoder.singleValueContainer() 34 | try container.encode(value.converted(to: .milesPerHour).value) 35 | 36 | } 37 | 38 | public var milesPerHour: Double { return value.converted(to: .milesPerHour).value } 39 | public var kilometersPerHour: Double { return value.converted(to: .kilometersPerHour).value } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/SpeedLimitOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpeedLimitOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 15/12/2018. 6 | // Copyright © 2018 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class SetSpeedLimitOptions: Codable { 12 | 13 | open var limit: Measurement 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case limit = "limit_mph" 17 | } 18 | 19 | public init(limit: Measurement) { 20 | self.limit = limit.converted(to: UnitSpeed.milesPerHour) 21 | } 22 | 23 | required public init(from decoder: Decoder) throws { 24 | 25 | let container = try decoder.container(keyedBy: CodingKeys.self) 26 | 27 | if let value = try? container.decode(Int.self, forKey: .limit) { 28 | limit = Measurement(value: Double(value), unit: UnitSpeed.milesPerHour) 29 | } else { 30 | limit = Measurement(value: 0, unit: UnitSpeed.milesPerHour) 31 | } 32 | } 33 | 34 | public func encode(to encoder: Encoder) throws { 35 | var container = encoder.container(keyedBy: CodingKeys.self) 36 | 37 | let milesUnit = limit.converted(to: UnitSpeed.milesPerHour) 38 | try container.encode(Int(milesUnit.value), forKey: .limit) 39 | } 40 | 41 | } 42 | 43 | open class SpeedLimitPinOptions: Codable { 44 | 45 | open var pin: String // 4 digits pin 46 | 47 | public init(pin: String) { 48 | self.pin = pin 49 | } 50 | 51 | enum CodingKeys: String, CodingKey { 52 | case pin 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/ValetCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValetCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 12/04/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class ValetCommandOptions: Codable { 12 | open var on: Bool = false 13 | open var password: String? 14 | 15 | public init(valetActivated: Bool, pin: String?) { 16 | on = valetActivated 17 | password = pin 18 | } 19 | 20 | enum CodingKeys: String, CodingKey { 21 | case on 22 | case password 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/Vehicle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Vehicle.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 05/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class Vehicle: Codable { 12 | 13 | open var backseatToken: String? 14 | open var backseatTokenUpdatedAt: Date? 15 | open var calendarEnabled: Bool? 16 | open var color: String? 17 | open var displayName: String? 18 | open var id: VehicleId? { 19 | get { 20 | guard let value = idInt else { return nil } 21 | return VehicleId(id: value) 22 | } 23 | set { 24 | guard let newValue = newValue?.id else { idInt = nil; return } 25 | idInt = newValue 26 | } 27 | } 28 | open var idInt: Int64? 29 | open var idS: String? 30 | open var inService: Bool? 31 | open var optionCodes: String? 32 | open var state: String? 33 | open var tokens: [String]? 34 | open var vehicleID: Int64? 35 | open var vin: String? 36 | 37 | // fields via /api/1/products 38 | open var userId: Int64? 39 | open var accessType: String? 40 | open var cachedData: String? 41 | open var apiVersion: Int? 42 | open var bleAutopairEnrolled: Bool? 43 | open var commandSigning: Bool? 44 | open var releaseNotesSupported: Bool? 45 | 46 | // MARK: Codable protocol 47 | 48 | enum CodingKeys: String, CodingKey { 49 | 50 | case backseatToken = "backseat_token" 51 | case backseatTokenUpdatedAt = "backseat_token_updated_at" 52 | case calendarEnabled = "calendar_enabled" 53 | case color = "color" 54 | case displayName = "display_name" 55 | case idInt = "id" 56 | case idS = "id_s" 57 | case inService = "in_service" 58 | case optionCodes = "option_codes" 59 | case state = "state" 60 | case tokens = "tokens" 61 | case vehicleID = "vehicle_id" 62 | case vin = "vin" 63 | case userId = "user_id" 64 | case accessType = "access_type" 65 | case cachedData = "cached_data" 66 | case apiVersion = "api_version" 67 | case bleAutopairEnrolled = "ble_autopair_enrolled" 68 | case commandSigning = "command_signing" 69 | case releaseNotesSupported = "release_notes_supported" 70 | 71 | } 72 | 73 | required public init(from decoder: Decoder) throws { 74 | 75 | let container = try decoder.container(keyedBy: CodingKeys.self) 76 | backseatToken = try? container.decode(String.self, forKey: .backseatToken) 77 | backseatTokenUpdatedAt = try? container.decode(Date.self, forKey: .backseatTokenUpdatedAt) 78 | calendarEnabled = { 79 | if let boolValue = try? container.decode(Bool.self, forKey: .calendarEnabled) { 80 | return boolValue 81 | } else if let intValue = try? container.decode(Int.self, forKey: .calendarEnabled) { 82 | return intValue > 0 83 | } else { 84 | return nil 85 | } 86 | }() 87 | color = try? container.decode(String.self, forKey: .color) 88 | displayName = try? container.decode(String.self, forKey: .displayName) 89 | idInt = try? container.decode(Int64.self, forKey: .idInt) 90 | idS = try? container.decode(String.self, forKey: .idS) 91 | inService = { 92 | if let boolValue = try? container.decode(Bool.self, forKey: .inService) { 93 | return boolValue 94 | } else if let intValue = try? container.decode(Int.self, forKey: .inService) { 95 | return intValue > 0 96 | } else { 97 | return nil 98 | } 99 | }() 100 | optionCodes = try? container.decode(String.self, forKey: .optionCodes) 101 | state = try? container.decode(String.self, forKey: .state) 102 | tokens = try? container.decode([String].self, forKey: .tokens) 103 | vehicleID = try? container.decode(Int64.self, forKey: .vehicleID) 104 | vin = try? container.decode(String.self, forKey: .vin) 105 | userId = try? container.decode(Int64.self, forKey: .userId) 106 | accessType = try? container.decode(String.self, forKey: .accessType) 107 | cachedData = try? container.decode(String.self, forKey: .cachedData) 108 | apiVersion = try? container.decode(Int.self, forKey: .apiVersion) 109 | bleAutopairEnrolled = try? container.decode(Bool.self, forKey: .bleAutopairEnrolled) 110 | commandSigning = try? container.decode(Bool.self, forKey: .commandSigning) 111 | releaseNotesSupported = try? container.decode(Bool.self, forKey: .releaseNotesSupported) 112 | } 113 | 114 | public func encode(to encoder: Encoder) throws { 115 | 116 | var container = encoder.container(keyedBy: CodingKeys.self) 117 | try container.encodeIfPresent(backseatToken, forKey: .backseatToken) 118 | try container.encodeIfPresent(backseatTokenUpdatedAt, forKey: .backseatTokenUpdatedAt) 119 | try container.encodeIfPresent(calendarEnabled, forKey: .calendarEnabled) 120 | try container.encodeIfPresent(color, forKey: .color) 121 | try container.encodeIfPresent(displayName, forKey: .displayName) 122 | try container.encodeIfPresent(idInt, forKey: .idInt) 123 | try container.encodeIfPresent(idS, forKey: .idS) 124 | try container.encodeIfPresent(inService, forKey: .inService) 125 | try container.encodeIfPresent(optionCodes, forKey: .optionCodes) 126 | try container.encodeIfPresent(state, forKey: .state) 127 | try container.encodeIfPresent(tokens, forKey: .tokens) 128 | try container.encodeIfPresent(vehicleID, forKey: .vehicleID) 129 | try container.encodeIfPresent(vin, forKey: .vin) 130 | try container.encodeIfPresent(userId, forKey: .userId) 131 | try container.encodeIfPresent(accessType, forKey: .accessType) 132 | try container.encodeIfPresent(cachedData, forKey: .cachedData) 133 | try container.encodeIfPresent(apiVersion, forKey: .apiVersion) 134 | try container.encodeIfPresent(bleAutopairEnrolled, forKey: .bleAutopairEnrolled) 135 | try container.encodeIfPresent(commandSigning, forKey: .commandSigning) 136 | try container.encodeIfPresent(releaseNotesSupported, forKey: .releaseNotesSupported) 137 | } 138 | } 139 | 140 | public class VehicleId { 141 | public let id: Int64 142 | 143 | init(id: Int64) { 144 | self.id = id 145 | } 146 | } 147 | 148 | public class SiteId { 149 | public let id: Decimal 150 | 151 | init(id: Decimal) { 152 | self.id = id 153 | } 154 | } 155 | 156 | public class BatteryId { 157 | public let id: String 158 | 159 | init(id: String) { 160 | self.id = id 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/VehicleConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VehicleConfig.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 12/03/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class VehicleConfig: Codable { 12 | open var canAcceptNavigationRequests: Bool? 13 | open var canActuateTrunks: Bool? 14 | open var carSpecialType: String? 15 | open var carType: String? 16 | open var chargePortType: String? 17 | open var euVehicle: Bool? 18 | open var exteriorColor: String? 19 | open var hasAirSuspension: Bool? 20 | open var hasLudicrousMode: Bool? 21 | open var motorizedChargePort: Bool? 22 | open var perfConfig: String? 23 | open var plg: Bool? 24 | open var rearSeatHeaters: Int? 25 | open var rearSeatType: Int? 26 | open var rhd: Bool? 27 | open var roofColor: String? // "None" for panoramic roof 28 | open var seatType: Int? 29 | open var spoilerType: String? 30 | open var sunRoofInstalled: Int? 31 | open var thirdRowSeats: String? 32 | open var timeStamp: Double? 33 | open var trimBadging: String? 34 | open var wheelType: String? 35 | 36 | enum CodingKeys: String, CodingKey { 37 | case canAcceptNavigationRequests = "can_accept_navigation_requests" 38 | case canActuateTrunks = "can_actuate_trunks" 39 | case carSpecialType = "car_special_type" 40 | case carType = "car_type" 41 | case chargePortType = "charge_port_type" 42 | case euVehicle = "eu_vehicle" 43 | case exteriorColor = "exterior_color" 44 | case hasAirSuspension = "has_air_suspension" 45 | case hasLudicrousMode = "has_ludicrous_mode" 46 | case motorizedChargePort = "motorized_charge_port" 47 | case perfConfig = "perf_config" 48 | case plg = "plg" 49 | case rearSeatHeaters = "rear_seat_heaters" 50 | case rearSeatType = "rear_seat_type" 51 | case rhd = "rhd" 52 | case roofColor = "roof_color" 53 | case seatType = "seat_type" 54 | case spoilerType = "spoiler_type" 55 | case sunRoofInstalled = "sun_roof_installed" 56 | case thirdRowSeats = "third_row_seats" 57 | case timeStamp = "timestamp" 58 | case trimBadging = "trim_badging" 59 | case wheelType = "wheel_type" 60 | } 61 | 62 | required public init(from decoder: Decoder) throws { 63 | let container = try decoder.container(keyedBy: CodingKeys.self) 64 | 65 | canAcceptNavigationRequests = try? container.decode(Bool.self, forKey: .canAcceptNavigationRequests) 66 | canActuateTrunks = try? container.decode(Bool.self, forKey: .canActuateTrunks) 67 | carSpecialType = try? container.decode(String.self, forKey: .carSpecialType) 68 | carType = try? container.decode(String.self, forKey: .carType) 69 | chargePortType = try? container.decode(String.self, forKey: .chargePortType) 70 | euVehicle = try? container.decode(Bool.self, forKey: .euVehicle) 71 | hasAirSuspension = try? container.decode(Bool.self, forKey: .hasAirSuspension) 72 | exteriorColor = try? container.decode(String.self, forKey: .exteriorColor) 73 | hasLudicrousMode = try? container.decode(Bool.self, forKey: .hasLudicrousMode) 74 | motorizedChargePort = try? container.decode(Bool.self, forKey: .motorizedChargePort) 75 | perfConfig = try? container.decode(String.self, forKey: .perfConfig) 76 | plg = try? container.decode(Bool.self, forKey: .plg) 77 | 78 | rearSeatHeaters = try? container.decode(Int.self, forKey: .rearSeatHeaters) 79 | 80 | rearSeatType = try? container.decode(Int.self, forKey: .rearSeatType) 81 | rhd = try? container.decode(Bool.self, forKey: .rhd) 82 | roofColor = try? container.decode(String.self, forKey: .roofColor) // "None" for panoramic roof 83 | seatType = try? container.decode(Int.self, forKey: .seatType) 84 | spoilerType = try? container.decode(String.self, forKey: .spoilerType) 85 | sunRoofInstalled = try? container.decode(Int.self, forKey: .sunRoofInstalled) 86 | thirdRowSeats = try? container.decode(String.self, forKey: .thirdRowSeats) 87 | timeStamp = try? container.decode(Double.self, forKey: .timeStamp) 88 | trimBadging = try? container.decode(String.self, forKey: .trimBadging) 89 | wheelType = try? container.decode(String.self, forKey: .wheelType) 90 | } 91 | 92 | public func encode(to encoder: Encoder) throws { 93 | var container = encoder.container(keyedBy: CodingKeys.self) 94 | 95 | try container.encodeIfPresent(canAcceptNavigationRequests, forKey: .canAcceptNavigationRequests) 96 | try container.encodeIfPresent(canActuateTrunks, forKey: .canActuateTrunks) 97 | try container.encodeIfPresent(carSpecialType, forKey: .carSpecialType) 98 | try container.encodeIfPresent(carType, forKey: .carType) 99 | try container.encodeIfPresent(chargePortType, forKey: .chargePortType) 100 | try container.encodeIfPresent(euVehicle, forKey: .euVehicle) 101 | try container.encodeIfPresent(exteriorColor, forKey: .exteriorColor) 102 | try container.encodeIfPresent(hasAirSuspension, forKey: .hasAirSuspension) 103 | try container.encodeIfPresent(hasLudicrousMode, forKey: .hasLudicrousMode) 104 | try container.encodeIfPresent(motorizedChargePort, forKey: .motorizedChargePort) 105 | try container.encodeIfPresent(perfConfig, forKey: .perfConfig) 106 | try container.encodeIfPresent(plg, forKey: .plg) 107 | try container.encodeIfPresent(rearSeatHeaters, forKey: .rearSeatHeaters) 108 | 109 | try container.encodeIfPresent(rearSeatType, forKey: .rearSeatType) 110 | try container.encodeIfPresent(rhd, forKey: .rhd) 111 | try container.encodeIfPresent(roofColor, forKey: .roofColor) 112 | try container.encodeIfPresent(seatType, forKey: .seatType) 113 | try container.encodeIfPresent(spoilerType, forKey: .spoilerType) 114 | try container.encodeIfPresent(sunRoofInstalled, forKey: .sunRoofInstalled) 115 | try container.encodeIfPresent(thirdRowSeats, forKey: .thirdRowSeats) 116 | try container.encodeIfPresent(timeStamp, forKey: .timeStamp) 117 | try container.encodeIfPresent(trimBadging, forKey: .trimBadging) 118 | try container.encodeIfPresent(wheelType, forKey: .wheelType) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/VehicleExtended.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VehicleExtended.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 12/03/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | open class VehicleExtended: Vehicle { 12 | open var chargeState: ChargeState? 13 | open var climateState: ClimateState? 14 | open var driveState: DriveState? 15 | open var guiSettings: GuiSettings? 16 | open var vehicleConfig: VehicleConfig? 17 | open var vehicleState: VehicleState? 18 | 19 | private enum CodingKeys: String, CodingKey { 20 | case userId = "user_id" 21 | case chargeState = "charge_state" 22 | case climateState = "climate_state" 23 | case driveState = "drive_state" 24 | case guiSettings = "gui_settings" 25 | case vehicleConfig = "vehicle_config" 26 | case vehicleState = "vehicle_state" 27 | 28 | case superWorkaround = "super" // We need this to be able to decode from the Tesla API and from an encoded string 29 | } 30 | 31 | required public init(from decoder: Decoder) throws { 32 | let container = try decoder.container(keyedBy: CodingKeys.self) 33 | chargeState = try container.decodeIfPresent(ChargeState.self, forKey: .chargeState) 34 | climateState = try container.decodeIfPresent(ClimateState.self, forKey: .climateState) 35 | driveState = try container.decodeIfPresent(DriveState.self, forKey: .driveState) 36 | guiSettings = try container.decodeIfPresent(GuiSettings.self, forKey: .guiSettings) 37 | vehicleConfig = try container.decodeIfPresent(VehicleConfig.self, forKey: .vehicleConfig) 38 | vehicleState = try container.decodeIfPresent(VehicleState.self, forKey: .vehicleState) 39 | if container.contains(.superWorkaround) { 40 | try super.init(from: container.superDecoder() ) 41 | } else { 42 | try super.init(from: decoder) 43 | } 44 | } 45 | 46 | override open func encode(to encoder: Encoder) throws { 47 | var container = encoder.container(keyedBy: CodingKeys.self) 48 | try container.encodeIfPresent(chargeState, forKey: .chargeState) 49 | try container.encodeIfPresent(climateState, forKey: .climateState) 50 | try container.encodeIfPresent(driveState, forKey: .driveState) 51 | try container.encodeIfPresent(guiSettings, forKey: .guiSettings) 52 | try container.encodeIfPresent(vehicleConfig, forKey: .vehicleConfig) 53 | try container.encodeIfPresent(vehicleState, forKey: .vehicleState) 54 | 55 | let superEncoder = container.superEncoder() 56 | try super.encode(to: superEncoder) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/Model/WindowControlCommandOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowControlCommandOptions.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 19/10/2019. 6 | // Copyright © 2019 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | public enum WindowState: String, Codable { 13 | case close 14 | case vent 15 | } 16 | 17 | open class WindowControlCommandOptions: Encodable { 18 | 19 | open var latitude: CLLocationDegrees = 0 20 | open var longitude: CLLocationDegrees = 0 21 | open var command: WindowState 22 | 23 | public init(command: WindowState) { 24 | self.command = command 25 | } 26 | 27 | enum CodingKeys: String, CodingKey { 28 | case latitude = "lat" 29 | case longitude = "lon" 30 | case command = "command" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/TeslaEndpoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TeslaEndpoint.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 16/04/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Endpoint { 12 | case revoke 13 | case oAuth2Authorization(auth: AuthCodeRequest) 14 | case oAuth2AuthorizationCN(auth: AuthCodeRequest) 15 | case oAuth2Token 16 | case oAuth2TokenCN 17 | case oAuth2revoke(token: String) 18 | case oAuth2revokeCN(token: String) 19 | 20 | case me 21 | case region 22 | 23 | case partnerAccounts 24 | case vehicles 25 | case vehicleSummary(vehicleID: VehicleId) 26 | case mobileAccess(vehicleID: VehicleId) 27 | case allStates(vehicleID: VehicleId, endpoints: [AllStatesEndpoints]) 28 | case chargeState(vehicleID: VehicleId) 29 | case climateState(vehicleID: VehicleId) 30 | case driveState(vehicleID: VehicleId) 31 | case nearbyChargingSites(vehicleID: VehicleId) 32 | case guiSettings(vehicleID: VehicleId) 33 | case vehicleState(vehicleID: VehicleId) 34 | case vehicleConfig(vehicleID: VehicleId) 35 | case wakeUp(vehicleID: VehicleId) 36 | case command(vehicleID: VehicleId, command: VehicleCommand) 37 | case signedCommand(vehicleID: VehicleId) 38 | case products 39 | case chargeHistory(vehicleID: VehicleId) 40 | case getEnergySiteStatus(siteID: SiteId) 41 | case getEnergySiteLiveStatus(siteID: SiteId) 42 | case getEnergySiteInfo(siteID: SiteId) 43 | case getEnergySiteHistory(siteID: SiteId, period: EnergySiteHistory.Period) 44 | case getBatteryStatus(batteryID: BatteryId) 45 | case getBatteryData(batteryID: BatteryId) 46 | case getBatteryPowerHistory(batteryID: BatteryId) 47 | } 48 | 49 | public enum AllStatesEndpoints: String { 50 | case chargeState = "charge_state" 51 | case climateState = "climate_state" 52 | case closuresState = "closures_state" 53 | case driveState = "drive_state" 54 | case guiSettings = "gui_settings" 55 | case locationData = "location_data" // Same as driveState but with location 56 | case vehicleConfig = "vehicle_config" 57 | case vehicleState = "vehicle_state" 58 | case vehicleDataCombo = "vehicle_data_combo" 59 | 60 | public static var all: [AllStatesEndpoints] = [.chargeState, .climateState, .closuresState, .driveState, .guiSettings, .vehicleConfig, .vehicleState] 61 | public static var allWithLocation: [AllStatesEndpoints] = [.chargeState, .climateState, .closuresState, .locationData, .guiSettings, .vehicleConfig, .vehicleState] 62 | } 63 | 64 | extension Endpoint { 65 | 66 | var path: String { 67 | switch self { 68 | // Auth 69 | case .revoke: 70 | return "/oauth/revoke" 71 | case .oAuth2Authorization, .oAuth2AuthorizationCN: 72 | return "/oauth2/v3/authorize" 73 | case .oAuth2Token, .oAuth2TokenCN: 74 | return "/oauth2/v3/token" 75 | case .oAuth2revoke, .oAuth2revokeCN: 76 | return "/oauth2/v3/revoke" 77 | case .partnerAccounts: 78 | return "/api/1/partner_accounts" 79 | 80 | case .me: 81 | return "/api/1/users/me" 82 | case .region: 83 | return "/api/1/users/region" 84 | // Vehicle Data and Commands 85 | case .vehicles: 86 | return "/api/1/vehicles" 87 | case .vehicleSummary(let vehicleID): 88 | return "/api/1/vehicles/\(vehicleID.id)" 89 | case .mobileAccess(let vehicleID): 90 | return "/api/1/vehicles/\(vehicleID.id)/mobile_enabled" 91 | case .allStates(let vehicleID, _): 92 | return "/api/1/vehicles/\(vehicleID.id)/vehicle_data" 93 | case .chargeState(let vehicleID): 94 | return "/api/1/vehicles/\(vehicleID.id)/data_request/charge_state" 95 | case .climateState(let vehicleID): 96 | return "/api/1/vehicles/\(vehicleID.id)/data_request/climate_state" 97 | case .driveState(let vehicleID): 98 | return "/api/1/vehicles/\(vehicleID.id)/data_request/drive_state" 99 | case .guiSettings(let vehicleID): 100 | return "/api/1/vehicles/\(vehicleID.id)/data_request/gui_settings" 101 | case .nearbyChargingSites(let vehicleID): 102 | return "/api/1/vehicles/\(vehicleID.id)/nearby_charging_sites" 103 | case .vehicleState(let vehicleID): 104 | return "/api/1/vehicles/\(vehicleID.id)/data_request/vehicle_state" 105 | case .vehicleConfig(let vehicleID): 106 | return "/api/1/vehicles/\(vehicleID.id)/data_request/vehicle_config" 107 | case .wakeUp(let vehicleID): 108 | return "/api/1/vehicles/\(vehicleID.id)/wake_up" 109 | case let .command(vehicleID, command): 110 | return "/api/1/vehicles/\(vehicleID.id)/\(command.path())" 111 | case let .signedCommand(vehicleID): 112 | return "/api/1/vehicles/\(vehicleID.id)/signed_command" 113 | case .products: 114 | return "/api/1/products" 115 | case let .chargeHistory(vehicleID): 116 | return "/api/1/vehicles/\(vehicleID.id)/charge_history" 117 | 118 | // Energy Data 119 | case .getEnergySiteStatus(let siteID): 120 | return "/api/1/energy_sites/\(siteID.id)/site_status" 121 | case .getEnergySiteLiveStatus(let siteID): 122 | return "/api/1/energy_sites/\(siteID.id)/live_status" 123 | case .getEnergySiteInfo(let siteID): 124 | return "/api/1/energy_sites/\(siteID.id)/site_info" 125 | case .getEnergySiteHistory(let siteID, _): 126 | return "/api/1/energy_sites/\(siteID.id)/history" 127 | case .getBatteryStatus(let batteryID): 128 | return "/api/1/powerwalls/\(batteryID.id)/status" 129 | case .getBatteryData(let batteryID): 130 | return "/api/1/powerwalls/\(batteryID.id)/" 131 | case .getBatteryPowerHistory(let batteryID): 132 | return "/api/1/powerwalls/\(batteryID.id)/powerhistory" 133 | } 134 | } 135 | 136 | var method: String { 137 | switch self { 138 | case .revoke, .oAuth2Token, .oAuth2TokenCN, .wakeUp, .partnerAccounts, .chargeHistory, .command, .signedCommand: 139 | return "POST" 140 | case .me, .region, .vehicles, .vehicleSummary, .mobileAccess, .allStates, .chargeState, .climateState, .driveState, .guiSettings, .vehicleState, .vehicleConfig, .nearbyChargingSites, .oAuth2Authorization, .oAuth2revoke, .oAuth2AuthorizationCN, .oAuth2revokeCN, .products, .getEnergySiteStatus, .getEnergySiteLiveStatus, .getEnergySiteInfo, .getEnergySiteHistory, .getBatteryStatus, .getBatteryData, .getBatteryPowerHistory: 141 | return "GET" 142 | } 143 | } 144 | 145 | var queryParameters: [URLQueryItem] { 146 | switch self { 147 | case let .oAuth2Authorization(auth): 148 | return auth.parameters() 149 | case let .oAuth2revoke(token): 150 | return [URLQueryItem(name: "token", value: token)] 151 | case let .getEnergySiteHistory(_, period): 152 | return [URLQueryItem(name: "period", value: period.rawValue), URLQueryItem(name: "kind", value: "energy")] 153 | case let .allStates(_, endpoints): 154 | return [URLQueryItem(name: "endpoints", value: endpoints.map({ $0.rawValue }).joined(separator: ";"))] 155 | default: 156 | return [] 157 | } 158 | } 159 | 160 | func baseURL(teslaAPI: TeslaAPI) -> String { 161 | switch self { 162 | case .oAuth2Authorization, .oAuth2Token, .oAuth2revoke: 163 | return "https://auth.tesla.com" 164 | case .oAuth2AuthorizationCN, .oAuth2TokenCN, .oAuth2revokeCN: 165 | return "https://auth.tesla.cn" 166 | default: 167 | return teslaAPI.url 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Sources/TeslaSwift/VehicleCommands.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VehicleCommands.swift 3 | // TeslaSwift 4 | // 5 | // Created by João Nunes on 02/04/2022. 6 | // Copyright © 2022 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation.CLLocation 11 | 12 | public enum VehicleCommand { 13 | case valetMode(valetActivated: Bool, pin: String?) 14 | case resetValetPin 15 | case openChargeDoor 16 | case closeChargeDoor 17 | case chargeLimitStandard 18 | case chargeLimitMaxRange 19 | case chargeLimitPercentage(limit: Int) 20 | case startCharging 21 | case stopCharging 22 | case scheduledCharging(enable: Bool, time: Int) 23 | case scheduledDeparture(options: ScheduledDepartureCommandOptions) 24 | case flashLights 25 | case triggerHomeLink(location: CLLocation) 26 | case honkHorn 27 | case unlockDoors 28 | case lockDoors 29 | case remoteBoombox 30 | case setTemperature(driverTemperature: Double, passengerTemperature: Double) 31 | case setMaxDefrost(on: Bool) 32 | case startAutoConditioning 33 | case stopAutoConditioning 34 | case setSunRoof(state: RoofState, percentage: Int?) 35 | case startVehicle(password: String) 36 | case openTrunk(options: OpenTrunkOptions) 37 | case togglePlayback 38 | case nextTrack 39 | case previousTrack 40 | case nextFavorite 41 | case previousFavorite 42 | case volumeUp 43 | case volumeDown 44 | case shareToVehicle(options: ShareToVehicleOptions) 45 | case cancelSoftwareUpdate 46 | case scheduleSoftwareUpdate 47 | case speedLimitSetLimit(speed: Measurement) 48 | case speedLimitActivate(pin: String) 49 | case speedLimitDeactivate(pin: String) 50 | case speedLimitClearPin(pin: String) 51 | case setSeatHeater(seat: HeatedSeat, level: HeatLevel) 52 | case setSteeringWheelHeater(on: Bool) 53 | case sentryMode(activated: Bool) 54 | case windowControl(state: WindowState) 55 | case setCharging(amps: Int) 56 | 57 | func path() -> String { 58 | switch self { 59 | case .valetMode: 60 | return "command/set_valet_mode" 61 | case .resetValetPin: 62 | return "command/reset_valet_pin" 63 | case .openChargeDoor: 64 | return "command/charge_port_door_open" 65 | case .closeChargeDoor: 66 | return "command/charge_port_door_close" 67 | case .chargeLimitStandard: 68 | return "command/charge_standard" 69 | case .chargeLimitMaxRange: 70 | return "command/charge_max_range" 71 | case .chargeLimitPercentage: 72 | return "command/set_charge_limit" 73 | case .startCharging: 74 | return "command/charge_start" 75 | case .stopCharging: 76 | return "command/charge_stop" 77 | case .scheduledCharging: 78 | return "command/set_scheduled_charging" 79 | case .scheduledDeparture: 80 | return "command/set_scheduled_departure" 81 | case .flashLights: 82 | return "command/flash_lights" 83 | case .triggerHomeLink: 84 | return "command/trigger_homelink" 85 | case .honkHorn: 86 | return "command/honk_horn" 87 | case .unlockDoors: 88 | return "command/door_unlock" 89 | case .lockDoors: 90 | return "command/door_lock" 91 | case .remoteBoombox: 92 | return "command/remote_boombox" 93 | case .setTemperature: 94 | return "command/set_temps" 95 | case .setMaxDefrost: 96 | return "command/set_preconditioning_max" 97 | case .startAutoConditioning: 98 | return "command/auto_conditioning_start" 99 | case .stopAutoConditioning: 100 | return "command/auto_conditioning_stop" 101 | case .setSunRoof: 102 | return "command/sun_roof_control" 103 | case .startVehicle: 104 | return "command/remote_start_drive" 105 | case .openTrunk: 106 | return "command/actuate_trunk" 107 | case .togglePlayback: 108 | return "command/media_toggle_playback" 109 | case .nextTrack: 110 | return "command/media_next_track" 111 | case .previousTrack: 112 | return "command/media_prev_track" 113 | case .nextFavorite: 114 | return "command/media_next_fav" 115 | case .previousFavorite: 116 | return "command/media_prev_fav" 117 | case .volumeUp: 118 | return "command/media_volume_up" 119 | case .volumeDown: 120 | return "command/media_volume_down" 121 | case .shareToVehicle: 122 | return "command/share" 123 | case .scheduleSoftwareUpdate: 124 | return "command/schedule_software_update" 125 | case .cancelSoftwareUpdate: 126 | return "command/cancel_software_update" 127 | case .speedLimitSetLimit: 128 | return "command/speed_limit_set_limit" 129 | case .speedLimitActivate: 130 | return "command/speed_limit_activate" 131 | case .speedLimitDeactivate: 132 | return "command/speed_limit_deactivate" 133 | case .speedLimitClearPin: 134 | return "command/speed_limit_clear_pin" 135 | case .setSeatHeater: 136 | return "command/remote_seat_heater_request" 137 | case .setSteeringWheelHeater: 138 | return "command/remote_steering_wheel_heater_request" 139 | case .sentryMode: 140 | return "command/set_sentry_mode" 141 | case .windowControl: 142 | return "command/window_control" 143 | case .setCharging: 144 | return "command/set_charging_amps" 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 04/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TeslaSwift 11 | 12 | // Change this! 13 | let clientID = "ABC" 14 | let clientSecret = "DEF" 15 | let redirectURI = "teslaswift://teslaswift" 16 | 17 | @UIApplicationMain 18 | class AppDelegate: UIResponder, UIApplicationDelegate { 19 | 20 | var window: UIWindow? 21 | var api: TeslaSwift! 22 | 23 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 24 | // Override point for customization after application launch. 25 | 26 | let teslaAPI = TeslaAPI.fleetAPI(region: .europeMiddleEastAfrica, clientID: clientID, clientSecret: clientSecret, redirectURI: redirectURI) 27 | api = TeslaSwift(teslaAPI: teslaAPI) 28 | api.debuggingEnabled = true 29 | 30 | if let jsonString = UserDefaults.standard.object(forKey: "tesla.token") as? String, 31 | let token: AuthToken = jsonString.decodeJSON(), 32 | let email = UserDefaults.standard.object(forKey: "tesla.email") as? String { 33 | api.reuse(token: token, email: email) 34 | } 35 | 36 | return true 37 | } 38 | 39 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 40 | print(url) 41 | Task { @MainActor in 42 | do { 43 | _ = try await api.authenticateWebNative(url: url) 44 | NotificationCenter.default.post(name: Notification.Name.nativeLoginDone, object: nil) 45 | } catch { 46 | print("Error") 47 | } 48 | } 49 | 50 | return true 51 | } 52 | 53 | func applicationWillResignActive(_ application: UIApplication) { 54 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 55 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 56 | } 57 | 58 | func applicationDidEnterBackground(_ application: UIApplication) { 59 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 60 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 61 | 62 | UserDefaults.standard.set(api.token?.jsonString, forKey: "tesla.token") 63 | UserDefaults.standard.set(api.email, forKey: "tesla.email") 64 | UserDefaults.standard.synchronize() 65 | } 66 | 67 | func applicationWillEnterForeground(_ application: UIApplication) { 68 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 69 | } 70 | 71 | func applicationDidBecomeActive(_ application: UIApplication) { 72 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 73 | } 74 | 75 | func applicationWillTerminate(_ application: UIApplication) { 76 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /TeslaSwiftDemo/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TeslaSwiftDemo/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasman/TeslaSwift/6fa8ce145451f63a6399fe153e439bfb1b20b500/TeslaSwiftDemo/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /TeslaSwiftDemo/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TeslaSwiftDemo/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasman/TeslaSwift/6fa8ce145451f63a6399fe153e439bfb1b20b500/TeslaSwiftDemo/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /TeslaSwiftDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/FirstViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 04/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | #if canImport(Combine) 11 | import Combine 12 | #endif 13 | import TeslaSwift 14 | 15 | class FirstViewController: UIViewController, UITableViewDataSource { 16 | 17 | @IBOutlet weak var tableView: UITableView! 18 | 19 | var data: [Product]? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | getVehicles() 25 | 26 | NotificationCenter.default.addObserver(forName: Notification.Name.loginDone, object: nil, queue: nil) { [weak self] (notification: Notification) in 27 | 28 | self?.getVehicles() 29 | } 30 | } 31 | 32 | override func viewDidAppear(_ animated: Bool) { 33 | super.viewDidAppear(animated) 34 | 35 | tableView.estimatedRowHeight = 50.0 36 | 37 | } 38 | 39 | func getVehicles() { 40 | Task { @MainActor in 41 | let products = try await api.getProducts() 42 | self.data = products 43 | self.tableView.reloadData() 44 | } 45 | } 46 | 47 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 48 | return data?.count ?? 0 49 | } 50 | 51 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 52 | 53 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 54 | 55 | let product = data![(indexPath as NSIndexPath).row] 56 | 57 | cell.textLabel?.text = product.vehicle?.displayName ?? "" 58 | cell.detailTextLabel?.text = product.vehicle?.vin ?? product.energySite?.id 59 | 60 | 61 | return cell 62 | } 63 | 64 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 65 | super.prepare(for: segue, sender: sender) 66 | 67 | if segue.identifier == "toDetail" { 68 | 69 | if let indexPath = tableView.indexPathForSelectedRow { 70 | let vc = segue.destination as! VehicleViewController 71 | if let vehicle = data![indexPath.row].vehicle { 72 | vc.vehicle = vehicle 73 | } 74 | } 75 | 76 | } 77 | } 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLName 27 | 28 | CFBundleURLSchemes 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | NSAppTransportSecurity 37 | 38 | NSAllowsArbitraryLoads 39 | 40 | 41 | UILaunchStoryboardName 42 | LaunchScreen.storyboard 43 | UIMainStoryboardFile 44 | Main 45 | UIRequiredDeviceCapabilities 46 | 47 | armv7 48 | 49 | UIStatusBarTintParameters 50 | 51 | UINavigationBar 52 | 53 | Style 54 | UIBarStyleDefault 55 | Translucent 56 | 57 | 58 | 59 | UISupportedInterfaceOrientations 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 05/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | 12 | extension Notification.Name { 13 | static let loginDone = Notification.Name("loginDone") 14 | static let nativeLoginDone = Notification.Name("nativeLoginDone") 15 | } 16 | 17 | class LoginViewController: UIViewController { 18 | @IBOutlet weak var messageLabel: UILabel! 19 | 20 | @IBAction func webLoginAction(_ sender: AnyObject) { 21 | let webloginViewController = api.authenticateWeb(delegate: self) 22 | 23 | guard let webloginViewController else { return } 24 | 25 | self.present(webloginViewController, animated: true, completion: nil) 26 | 27 | NotificationCenter.default.addObserver(forName: Notification.Name.nativeLoginDone, object: nil, queue: nil) { [weak self] (notification: Notification) in 28 | self?.dismiss(animated: false) { 29 | self?.dismiss(animated: false) 30 | } 31 | 32 | NotificationCenter.default.post(name: Notification.Name.loginDone, object: nil) 33 | } 34 | } 35 | 36 | @IBAction func nativeLoginAction(_ sender: AnyObject) { 37 | if let url = api.authenticateWebNativeURL() { 38 | UIApplication.shared.open(url) 39 | } 40 | NotificationCenter.default.addObserver(forName: Notification.Name.nativeLoginDone, object: nil, queue: nil) { [weak self] (notification: Notification) in 41 | NotificationCenter.default.post(name: Notification.Name.loginDone, object: nil) 42 | 43 | self?.dismiss(animated: true, completion: nil) 44 | } 45 | } 46 | } 47 | 48 | extension LoginViewController: SFSafariViewControllerDelegate { 49 | public func safariViewControllerDidFinish(_ controller: SFSafariViewController) { 50 | self.dismiss(animated: false) 51 | print("cancelled") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/ProductViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProductViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Alec on 11/24/21. 6 | // Copyright © 2021 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import TeslaSwift 12 | 13 | class ProductViewController: UIViewController { 14 | @IBOutlet private weak var textView: UITextView! 15 | 16 | var product: Product? 17 | 18 | var energySite: EnergySite? { 19 | return product?.energySite 20 | } 21 | 22 | override func viewWillAppear(_ animated: Bool) { 23 | super.viewWillAppear(animated) 24 | // This page is best suited for Energy Sites, but Vehicles are also returned in the Product API 25 | guard energySite != nil else { 26 | textView.text = "Select the Vehicle tab to interact with a vehicle" 27 | textView.isEditable = false 28 | return 29 | } 30 | textView.isEditable = true 31 | } 32 | 33 | 34 | 35 | @IBAction func getEnergySiteStatus(_ sender: Any) { 36 | guard let energySite = energySite else { return } 37 | Task { @MainActor in 38 | do { 39 | let response = try await api.getEnergySiteStatus(siteID: energySite.siteId) 40 | self.textView.text = response.jsonString 41 | } catch let error { 42 | self.textView.text = error.localizedDescription 43 | } 44 | } 45 | } 46 | 47 | @IBAction func getEnergySiteLiveStatus(_ sender: Any) { 48 | guard let energySite = energySite else { return } 49 | Task { @MainActor in 50 | do { 51 | let response = try await api.getEnergySiteLiveStatus(siteID: energySite.siteId) 52 | self.textView.text = response.jsonString 53 | } catch let error { 54 | self.textView.text = error.localizedDescription 55 | } 56 | } 57 | } 58 | 59 | @IBAction func getEnergySiteInfo(_ sender: Any) { 60 | guard let energySite = energySite else { return } 61 | Task { @MainActor in 62 | do { 63 | let response = try await api.getEnergySiteInfo(siteID: energySite.siteId) 64 | self.textView.text = response.jsonString 65 | } catch let error { 66 | self.textView.text = error.localizedDescription 67 | } 68 | } 69 | } 70 | 71 | @IBAction func getEnergySiteHistory(_ sender: Any) { 72 | guard let energySite = energySite else { return } 73 | Task { @MainActor in 74 | do { 75 | let response = try await api.getEnergySiteHistory(siteID: energySite.siteId, period: EnergySiteHistory.Period.day) 76 | self.textView.text = response.jsonString 77 | } catch let error { 78 | self.textView.text = error.localizedDescription 79 | } 80 | } 81 | } 82 | 83 | @IBAction func getBatteryStatus(_ sender: Any) { 84 | guard let energySiteId = energySite?.batteryId else { return } 85 | Task { @MainActor in 86 | do { 87 | let response = try await api.getBatteryStatus(batteryID: energySiteId) 88 | self.textView.text = response.jsonString 89 | } catch let error { 90 | self.textView.text = error.localizedDescription 91 | } 92 | } 93 | } 94 | 95 | @IBAction func getBatteryData(_ sender: Any) { 96 | guard let energySiteId = energySite?.batteryId else { return } 97 | Task { @MainActor in 98 | do { 99 | let response = try await api.getBatteryData(batteryID: energySiteId) 100 | self.textView.text = response.jsonString 101 | } catch let error { 102 | self.textView.text = error.localizedDescription 103 | } 104 | } 105 | } 106 | 107 | @IBAction func getBatteryPowerHistory(_ sender: Any) { 108 | guard let energySiteId = energySite?.batteryId else { return } 109 | Task { @MainActor in 110 | do { 111 | let response = try await api.getBatteryPowerHistory(batteryID: energySiteId) 112 | self.textView.text = response.jsonString 113 | } catch let error { 114 | self.textView.text = error.localizedDescription 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/SecondViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 04/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | #if canImport(Combine) 11 | import Combine 12 | #endif 13 | import TeslaSwift 14 | 15 | class SecondViewController: UIViewController, UITableViewDataSource { 16 | @IBOutlet weak var tableView: UITableView! 17 | 18 | var data:[Product]? 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | getProducts() 24 | 25 | NotificationCenter.default.addObserver(forName: Notification.Name.loginDone, object: nil, queue: nil) { [weak self] (notification: Notification) in 26 | 27 | self?.getProducts() 28 | } 29 | } 30 | 31 | override func viewDidAppear(_ animated: Bool) { 32 | super.viewDidAppear(animated) 33 | tableView.estimatedRowHeight = 50.0 34 | } 35 | 36 | func getProducts() { 37 | Task { @MainActor in 38 | self.data = try await api.getProducts() 39 | self.tableView.reloadData() 40 | } 41 | } 42 | 43 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 44 | return data?.count ?? 0 45 | } 46 | 47 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 48 | let cell = tableView.dequeueReusableCell(withIdentifier: "product-cell", for: indexPath) 49 | 50 | let product = data![(indexPath as NSIndexPath).row] 51 | 52 | if let vehicle = product.vehicle { 53 | cell.textLabel?.text = vehicle.displayName 54 | cell.detailTextLabel?.text = vehicle.vin 55 | } else if let energySite = product.energySite { 56 | cell.textLabel?.text = energySite.id 57 | cell.detailTextLabel?.text = energySite.resourceType 58 | } else { 59 | cell.textLabel?.text = "Unknown" 60 | cell.detailTextLabel?.text = "Unknown" 61 | } 62 | 63 | return cell 64 | } 65 | 66 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 67 | super.prepare(for: segue, sender: sender) 68 | if segue.identifier == "toProductDetail" { 69 | if let indexPath = tableView.indexPathForSelectedRow { 70 | let vc = segue.destination as! ProductViewController 71 | vc.product = data![indexPath.row] 72 | } 73 | } 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/StreamViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 10/11/2017. 6 | // Copyright © 2017 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TeslaSwift 11 | import TeslaSwiftStreaming 12 | 13 | class StreamViewController: UIViewController { 14 | @IBOutlet weak var textView: UITextView! 15 | 16 | var streaming = false 17 | var vehicle: Vehicle? 18 | var stream: TeslaStreaming! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | stream = TeslaStreaming(teslaSwift: api) 23 | } 24 | 25 | override func viewWillDisappear(_ animated: Bool) { 26 | super.viewWillDisappear(animated) 27 | stopStream(self) 28 | } 29 | 30 | @IBAction func stream(_ sender: Any) { 31 | if !streaming { 32 | guard let vehicle = vehicle else { return } 33 | self.textView.text = "" 34 | Task { @MainActor in 35 | for try await event in try await stream.openStream(vehicle: vehicle) { 36 | self.processEvent(event: event) 37 | } 38 | self.streaming = true 39 | } 40 | } 41 | } 42 | 43 | func processEvent(event: TeslaStreamingEvent) { 44 | switch event { 45 | case .error(let error): 46 | textView.text = error.localizedDescription 47 | case .event(let event): 48 | textView.text = "\(self.textView.text ?? "")\nevent:\n \(event.descriptionKm)" 49 | case .disconnected: 50 | break 51 | case .open: 52 | textView.text = "open" 53 | } 54 | } 55 | 56 | @IBAction func stopStream(_ sender: Any) { 57 | stream.closeStream() 58 | streaming = false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TabController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 05/03/16. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TabController: UITabBarController { 12 | 13 | override func viewDidAppear(_ animated: Bool) { 14 | super.viewDidAppear(animated) 15 | 16 | if (!api.isAuthenticated) { 17 | 18 | performSegue(withIdentifier: "loginSegue", sender: self) 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwift.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwiftDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwiftDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwiftDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Starscream", 6 | "repositoryURL": "https://github.com/daltoniam/Starscream.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "ac6c0fc9da221873e01bd1a0d4818498a71eef33", 10 | "version": "4.0.6" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/TeslaSwiftDemo.xcodeproj/xcshareddata/xcschemes/TeslaSwift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 77 | 79 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/VehicleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VehicleViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 22/10/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import TeslaSwift 12 | 13 | class VehicleViewController: UIViewController { 14 | @IBOutlet weak var textView: UITextView! 15 | 16 | var vehicle: Vehicle? 17 | 18 | @IBAction func getVehicle(_ sender: Any) { 19 | guard let vehicle = vehicle else { return } 20 | Task { @MainActor in 21 | let vehicle2 = try await api.getVehicle(vehicle) 22 | self.textView.text = "Vehicle: \(vehicle2.jsonString!)" 23 | } 24 | } 25 | 26 | @IBAction func gettAll(_ sender: Any) { 27 | guard let vehicle = vehicle else { return } 28 | Task { @MainActor in 29 | let extendedVehicle = try await api.getAllData(vehicle, endpoints: AllStatesEndpoints.allWithLocation) 30 | self.textView.text = "All data:\n" + 31 | extendedVehicle.jsonString! 32 | } 33 | } 34 | 35 | @IBAction func command(_ sender: AnyObject) { 36 | guard let vehicle = vehicle else { return } 37 | Task { @MainActor in 38 | let response = try await api.sendCommandToVehicle(vehicle, command: VehicleCommand.setMaxDefrost(on: false)) 39 | self.textView.text = (response.result! ? "true" : "false") 40 | if let reason = response.reason { 41 | self.textView.text.append(reason) 42 | } 43 | } 44 | } 45 | 46 | @IBAction func wakeup(_ sender: Any) { 47 | guard let vehicle = vehicle else { return } 48 | Task { @MainActor in 49 | let response = try await api.wakeUp(vehicle) 50 | self.textView.text = response.state 51 | } 52 | } 53 | 54 | 55 | @IBAction func speedLimit(_ sender: Any) { 56 | guard let vehicle = vehicle else { return } 57 | Task { @MainActor in 58 | let response = try await api.sendCommandToVehicle(vehicle, command: VehicleCommand.speedLimitClearPin(pin: "1234")) 59 | self.textView.text = (response.result! ? "true" : "false") 60 | if let reason = response.reason { 61 | self.textView.text.append(reason) 62 | } 63 | } 64 | } 65 | 66 | @IBAction func getNearbyChargingSites(_ sender: Any) { 67 | guard let vehicle = vehicle else { return } 68 | Task { @MainActor in 69 | let nearbyChargingSites = try await api.getNearbyChargingSites(vehicle) 70 | self.textView.text = "NearbyChargingSites:\n" + 71 | nearbyChargingSites.jsonString! 72 | } 73 | } 74 | 75 | @IBAction func refreshToken(_ sender: Any) { 76 | Task { @MainActor in 77 | do { 78 | let token = try await api.refreshToken() 79 | self.textView.text = "New access Token:\n \(token)" 80 | } catch { 81 | self.textView.text = "Refresh Token:\n CATCH" 82 | } 83 | } 84 | } 85 | 86 | @IBAction func ampsTo16(_ sender: Any) { 87 | guard let vehicle = vehicle else { return } 88 | Task { @MainActor in 89 | let response = try await api.sendCommandToVehicle(vehicle, command: VehicleCommand.setCharging(amps: 16)) 90 | self.textView.text = (response.result! ? "true" : "false") 91 | if let reason = response.reason { 92 | self.textView.text.append(reason) 93 | } 94 | } 95 | } 96 | 97 | @IBAction func revokeToken(_ sender: Any) { 98 | Task { @MainActor in 99 | do { 100 | let status = try await api.revokeToken() 101 | self.textView.text = "Revoked: \(status)" 102 | } catch { 103 | self.textView.text = "Revoke Token:\n CATCH" 104 | } 105 | } 106 | } 107 | 108 | @IBAction func logout(_ sender: Any) { 109 | api.logout() 110 | } 111 | @IBAction func sendKeyToVehicle(_ sender: Any) { 112 | let yourDomain = "orange-dune-0e6c58803.5.azurestaticapps.net" 113 | if let url = api.urlToSendPublicKeyToVehicle(domain: yourDomain) { 114 | UIApplication.shared.open(url) 115 | } 116 | } 117 | 118 | 119 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 120 | super.prepare(for: segue, sender: sender) 121 | if segue.identifier == "toStream" { 122 | let vc = segue.destination as! StreamViewController 123 | vc.vehicle = self.vehicle 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /TeslaSwiftDemo/ViewController+TeslaSwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIViewController.swift 3 | // TeslaSwift 4 | // 5 | // Created by Joao Nunes on 03/12/2016. 6 | // Copyright © 2016 Joao Nunes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TeslaSwift 11 | 12 | extension UIViewController { 13 | public var api: TeslaSwift { 14 | return (UIApplication.shared.delegate as! AppDelegate).api 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TeslaSwiftTests/AllStates.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "id": 1234, 4 | "user_id": 1234, 5 | "vehicle_id": 123, 6 | "vin": "12345", 7 | "display_name": "Tesla", 8 | "option_codes": "MS02,REEU,AU01,BS00,BT85,CH01,PMMB,CW01,DRLH,HP00,IDPB,IPMG,LP01,PA00,PF00,PK01,PS01,RFPO,SC01,SP01,SU01,TM00,TP01,TR00,WT19,X001,X003,X007,X009,X011,X013,X020,X025,X027,X031,YF00,CONO", 9 | "color": null, 10 | "tokens": [ 11 | "gfdsgfdsgf", 12 | "gfdsgfdsgfsdgfds" 13 | ], 14 | "state": "online", 15 | "in_service": false, 16 | "id_s": "604565436545487257", 17 | "remote_start_enabled": true, 18 | "calendar_enabled": true, 19 | "notifications_enabled": true, 20 | "backseat_token": null, 21 | "backseat_token_updated_at": null, 22 | "climate_state": { 23 | "inside_temp": 12.8, 24 | "outside_temp": 7.5, 25 | "driver_temp_setting": 17, 26 | "passenger_temp_setting": 17, 27 | "left_temp_direction": 118, 28 | "right_temp_direction": 118, 29 | "is_auto_conditioning_on": true, 30 | "is_front_defroster_on": 3, 31 | "is_rear_defroster_on": false, 32 | "fan_status": 0, 33 | "is_climate_on": false, 34 | "min_avail_temp": 15, 35 | "max_avail_temp": 28, 36 | "seat_heater_left": 0, 37 | "seat_heater_right": 0, 38 | "seat_heater_rear_left": 0, 39 | "seat_heater_rear_right": 0, 40 | "seat_heater_rear_center": 0, 41 | "seat_heater_rear_right_back": 0, 42 | "seat_heater_rear_left_back": 0, 43 | "smart_preconditioning": false, 44 | "timestamp": 1488311473437 45 | }, 46 | "drive_state": { 47 | "shift_state": null, 48 | "speed": null, 49 | "power": 0, 50 | "latitude": 59.957517, 51 | "longitude": 10.695389, 52 | "heading": 191, 53 | "gps_as_of": 1488311472, 54 | "timestamp": 1488311473445 55 | }, 56 | "gui_settings": { 57 | "gui_distance_units": "km/hr", 58 | "gui_temperature_units": "C", 59 | "gui_charge_rate_units": "km/hr", 60 | "gui_24_hour_time": true, 61 | "gui_range_display": "Ideal", 62 | "timestamp": 1488311473450 63 | }, 64 | "vehicle_state": { 65 | "api_version": 3, 66 | "autopark_state": "unavailable", 67 | "autopark_state_v2": "unavailable", 68 | "calendar_supported": true, 69 | "car_type": "s", 70 | "car_version": "2.52.22", 71 | "center_display_state": 0, 72 | "dark_rims": false, 73 | "df": 0, 74 | "dr": 0, 75 | "exterior_color": "Blue", 76 | "ft": 0, 77 | "has_spoiler": false, 78 | "locked": true, 79 | "notifications_supported": true, 80 | "odometer": 66347.902989, 81 | "parsed_calendar_supported": true, 82 | "perf_config": "P1", 83 | "pf": 0, 84 | "pr": 0, 85 | "rear_seat_heaters": 1, 86 | "rear_seat_type": 0, 87 | "remote_start": false, 88 | "remote_start_supported": true, 89 | "rhd": false, 90 | "roof_color": "None", 91 | "rt": 0, 92 | "seat_type": 0, 93 | "spoiler_type": "None", 94 | "sun_roof_installed": 1, 95 | "sun_roof_percent_open": 0, 96 | "sun_roof_state": "unknown", 97 | "third_row_seats": "None", 98 | "timestamp": 1488311473456, 99 | "valet_mode": false, 100 | "valet_pin_needed": true, 101 | "vehicle_name": "Elsa", 102 | "wheel_type": "Base19" 103 | }, 104 | "charge_state": { 105 | "charging_state": "Disconnected", 106 | "charge_limit_soc": 90, 107 | "charge_limit_soc_std": 90, 108 | "charge_limit_soc_min": 50, 109 | "charge_limit_soc_max": 100, 110 | "charge_to_max_range": false, 111 | "battery_heater_on": false, 112 | "not_enough_power_to_heat": false, 113 | "max_range_charge_counter": 2, 114 | "fast_charger_present": false, 115 | "fast_charger_type": "", 116 | "battery_range": 280.0, 117 | "est_battery_range": 186.31, 118 | "ideal_battery_range": 225.98, 119 | "battery_level": 96, 120 | "usable_battery_level": 96, 121 | "battery_current": -0.6, 122 | "charge_energy_added": 12.63, 123 | "charge_miles_added_rated": 49, 124 | "charge_miles_added_ideal": 39, 125 | "charger_voltage": 0, 126 | "charger_pilot_current": 32, 127 | "charger_actual_current": 0, 128 | "charger_power": 0, 129 | "time_to_full_charge": 0, 130 | "trip_charging": false, 131 | "charge_rate": 0, 132 | "charge_port_door_open": false, 133 | "scheduled_charging_start_time": null, 134 | "scheduled_charging_pending": false, 135 | "user_charge_enable_request": null, 136 | "charge_enable_request": true, 137 | "charger_phases": null, 138 | "charge_port_latch": "Blocking", 139 | "charge_current_request": 32, 140 | "charge_current_request_max": 32, 141 | "charge_port_led_color": "Off", 142 | "managed_charging_active": false, 143 | "managed_charging_user_canceled": false, 144 | "managed_charging_start_time": null, 145 | "motorized_charge_port": false, 146 | "eu_vehicle": true, 147 | "timestamp": 1488311473464 148 | }, 149 | "vehicle_config": { 150 | "car_special_type": "base", 151 | "car_type": "s", 152 | "eu_vehicle": true, 153 | "exterior_color": "Blue", 154 | "has_ludicrous_mode": false, 155 | "motorized_charge_port": false, 156 | "perf_config": "P1", 157 | "rear_seat_heaters": 1, 158 | "rear_seat_type": 0, 159 | "rhd": false, 160 | "roof_color": "None", 161 | "seat_type": 0, 162 | "spoiler_type": "None", 163 | "sun_roof_installed": 1, 164 | "third_row_seats": "None", 165 | "timestamp": 1488311473475, 166 | "trim_badging": "85", 167 | "wheel_type": "Base19" 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /TeslaSwiftTests/Authentication.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "abc123-mock", 3 | "token_type": "bearer", 4 | "expires_in": 7776000, 5 | "created_at": 1457385291 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/AuthenticationFailed.json: -------------------------------------------------------------------------------- 1 | { 2 | "error" : "invalid_grant", 3 | "error_description" : "The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client." 4 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/ChargeLimitMaxRange.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test charge max range" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/ChargeLimitPercentage.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test charge percentage" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/ChargeLimitStandard.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test charge standard" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/ChargeState.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "charging_state": "Complete", 4 | "charge_to_max_range": false, 5 | "max_range_charge_counter": 0, 6 | "fast_charger_present": false, 7 | "battery_range": 200.00, 8 | "est_battery_range": 155.79, 9 | "ideal_battery_range": 275.09, 10 | "battery_level": 91, 11 | "battery_current": -0.6, 12 | "charge_starting_range": null, 13 | "charge_starting_soc": null, 14 | "charger_voltage": 0, 15 | "charger_pilot_current": 40, 16 | "charger_actual_current": 0, 17 | "charger_power": 0, 18 | "time_to_full_charge": null, 19 | "charge_rate": -1, 20 | "charge_port_door_open": true, 21 | "battery_heater_on": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeslaSwiftTests/ClimateSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "driver_temp_setting" : 24, 4 | "fan_status" : 0, 5 | "inside_temp" : 18.0, 6 | "is_auto_conditioning_on" : 0, 7 | "is_climate_on" : 0, 8 | "is_front_defroster_on" : 3, 9 | "is_rear_defroster_on" : 0, 10 | "left_temp_direction" : 583, 11 | "max_avail_temp" : 28, 12 | "min_avail_temp" : 15, 13 | "outside_temp" : -1.5, 14 | "passenger_temp_setting" : 24, 15 | "right_temp_direction" : 583, 16 | "seat_heater_left" : 0, 17 | "seat_heater_rear_center" : 0, 18 | "seat_heater_rear_left" : 0, 19 | "seat_heater_rear_left_back" : 0, 20 | "seat_heater_rear_right" : 0, 21 | "seat_heater_rear_right_back" : 0, 22 | "seat_heater_right" : 0, 23 | "smart_preconditioning" : 0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeslaSwiftTests/DriveState.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "shift_state": null, 4 | "speed": null, 5 | "latitude": 33.794839, 6 | "longitude": -84.401593, 7 | "heading": 10, 8 | "gps_as_of": 1359863204 9 | } 10 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/FlashLights.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test FlashLights" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/GuiSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "gui_distance_units": "km/hr", 4 | "gui_temperature_units": "C", 5 | "gui_charge_rate_units": "km/hr", 6 | "gui_24_hour_time": false, 7 | "gui_range_display": "Rated" 8 | } 9 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/HonkHorn.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test HonkHorn" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /TeslaSwiftTests/LockDoors.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test LockDoors" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/MobileAccess.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": false 3 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/NearbyChargingSites.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "congestion_sync_time_utc_secs": 1545091987, 4 | "destination_charging": [ 5 | { 6 | "location": { 7 | "lat": 33.811484, 8 | "long": -118.138451 9 | }, 10 | "name": "Long Beach Marriott", 11 | "type": "destination", 12 | "distance_miles": 2.201606 13 | }, 14 | { 15 | "location": { 16 | "lat": 33.767198, 17 | "long": -118.191987 18 | }, 19 | "name": "Renaissance Long Beach Hotel", 20 | "type": "destination", 21 | "distance_miles": 4.071068 22 | }, 23 | { 24 | "location": { 25 | "lat": 33.757146, 26 | "long": -118.19861 27 | }, 28 | "name": "Hotel Maya, a Doubletree by Hilton", 29 | "type": "destination", 30 | "distance_miles": 4.843953 31 | }, 32 | { 33 | "location": { 34 | "lat": 33.832254, 35 | "long": -118.079218 36 | }, 37 | "name": "The Gardens Casino", 38 | "type": "destination", 39 | "distance_miles": 6.449794 40 | } 41 | ], 42 | "superchargers": [ 43 | { 44 | "location": { 45 | "lat": 33.934471, 46 | "long": -118.121217 47 | }, 48 | "name": "Downey, CA - Stonewood Street", 49 | "type": "supercharger", 50 | "distance_miles": 2.196721, 51 | "available_stalls": 5, 52 | "total_stalls": 12, 53 | "site_closed": false 54 | }, 55 | { 56 | "location": { 57 | "lat": 33.953385, 58 | "long": -118.112905 59 | }, 60 | "name": "Downey, CA - Lakewood Boulevard", 61 | "type": "supercharger", 62 | "distance_miles": 9.587273, 63 | "available_stalls": 6, 64 | "total_stalls": 12, 65 | "site_closed": false 66 | }, 67 | { 68 | "location": { 69 | "lat": 33.921063, 70 | "long": -118.330074 71 | }, 72 | "name": "Hawthorne, CA", 73 | "type": "supercharger", 74 | "distance_miles": 12.197322, 75 | "available_stalls": 3, 76 | "total_stalls": 6, 77 | "site_closed": false 78 | }, 79 | { 80 | "location": { 81 | "lat": 33.894227, 82 | "long": -118.367407 83 | }, 84 | "name": "Redondo Beach, CA", 85 | "type": "supercharger", 86 | "distance_miles": 13.125912, 87 | "available_stalls": 3, 88 | "total_stalls": 8, 89 | "site_closed": false 90 | } 91 | ], 92 | "timestamp": 1545092157769 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /TeslaSwiftTests/OpenChargeDoor.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test open charge door" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/OpenTrunk.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test OpenTrunk" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/ResetValetPin.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test resetValet" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/SetSpeedLimit.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": true, 4 | "reason": "Test Set SpeedLimit" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TeslaSwiftTests/SetSunRoof.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test SetSunRoof" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/SetTemperature.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test SetTemperature" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/SetValetMode.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test valet" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/SpeedLimitPin.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": true, 4 | "reason": "Test Set SpeedLimit Pin" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TeslaSwiftTests/StartAutoConditioning.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test StartAutoConditioning" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/StartCharging.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test start charging" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/StartVehicle.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test StartVehicle" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/StopAutoConditioning.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test StopAutoConditioning" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/StopCharging.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test stop charging" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/StreamingData.txt: -------------------------------------------------------------------------------- 1 | 1493496113857,,5241.9,84,17,142,60.164031,24.925366,-3,,289,150,142 2 | -------------------------------------------------------------------------------- /TeslaSwiftTests/UnlockDoors.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "result": false, 4 | "reason": "Test UnlockDoors" 5 | } 6 | } -------------------------------------------------------------------------------- /TeslaSwiftTests/VehicleConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "can_accept_navigation_requests": true, 4 | "car_special_type": "base", 5 | "car_type": "s", 6 | "eu_vehicle": true, 7 | "exterior_color": "Blue", 8 | "has_ludicrous_mode": false, 9 | "motorized_charge_port": false, 10 | "perf_config": "P1", 11 | "rear_seat_heaters": 1, 12 | "rear_seat_type": 0, 13 | "rhd": false, 14 | "roof_color": "None", 15 | "seat_type": 0, 16 | "spoiler_type": "None", 17 | "sun_roof_installed": 1, 18 | "third_row_seats": "None", 19 | "timestamp": 1488311473475, 20 | "trim_badging": "85", 21 | "wheel_type": "Base19" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeslaSwiftTests/VehicleState.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "df": 0, 4 | "dr": 0, 5 | "pf": 0, 6 | "pr": 0, 7 | "ft": 0, 8 | "rt": 0, 9 | "car_verson": "1.19.42", 10 | "locked": true, 11 | "sun_roof_installed": false, 12 | "sun_roof_state": "unknown", 13 | "sun_roof_percent_open": 0, 14 | "dark_rims": true, 15 | "wheel_type": "Base19", 16 | "has_spoiler": false, 17 | "roof_color": "Colored", 18 | "perf_config": "Base" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TeslaSwiftTests/Vehicles.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "response": [ 4 | { 5 | "color": null, 6 | "display_name": "mockCar", 7 | "id": 321, 8 | "option_codes": "MS01,RENA,TM00,DRLH,PF00,BT85,PBCW,RFPO,WT19,IBMB,IDPB,TR00,SU01,SC01,TP01,AU01,CH00,HP00,PA00,PS00,AD02,X020,X025,X001,X003,X007,X011,X013", 9 | "user_id": 123, 10 | "vehicle_id": 1234567890, 11 | "vin": "5YJSA1CN5CFP01657", 12 | "tokens": [ 13 | "x", 14 | "x" 15 | ], 16 | "state": "online", 17 | "backseat_token": "111", 18 | "backseat_token_updated_at": 1488311472, 19 | "calendar_enabled": true, 20 | "id_s": "321", 21 | "in_service": true 22 | 23 | } 24 | ], 25 | "count": 1 26 | } 27 | -------------------------------------------------------------------------------- /TeslaSwiftTests/WakeUp.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "response": 4 | { 5 | "color": null, 6 | "display_name": "mockCar", 7 | "id": 321, 8 | "option_codes": "MS01,RENA,TM00,DRLH,PF00,BT85,PBCW,RFPO,WT19,IBMB,IDPB,TR00,SU01,SC01,TP01,AU01,CH00,HP00,PA00,PS00,AD02,X020,X025,X001,X003,X007,X011,X013", 9 | "user_id": 123, 10 | "vehicle_id": 1234567890, 11 | "vin": "5YJSA1CN5CFP01657", 12 | "tokens": [ 13 | "x", 14 | "x" 15 | ], 16 | "state": "online", 17 | "backseat_token": "111", 18 | "backseat_token_updated_at": 1488311472, 19 | "calendar_enabled": true, 20 | "id_s": "321", 21 | "in_service": true 22 | 23 | } 24 | } 25 | --------------------------------------------------------------------------------