NOTE: This is my fork/implementation. I inject custom OneObdSetting object into OBDService init for wifi and bluetooth configuration.
enum OneObdSettingType: Codable {
case connection
case sensorsSelectedPIDs
}
final class OneObdSetting: Codable {
static let delayNanosecondsTimeoutAdapterInitializationDefault: Int64 = 0
let name: String
var delayNanosecondsTimeoutAdapterInitialization: Int64
var delayNanosecondsWifiRetryCommand: Int64
var delayNanosecondsTimeoutDetectProtocolAutomatically: Int64
var wifiIp: String
var wifiPort: Int
var connectionTimeoutSeconds: CGFloat
var customData: String
var obdProtocol: PROTOCOL?
var obdConnectionName: String?
// 20032025
var oneSecondNanoseconds: Int64 = 1_000_000_000
var delayNanosecondsBTPeripherals: Int64
var bt: Bool
var wifi: Bool {
!bt
}
// Timeout factors
var timeoutMultiplyCustomTimeouts: CGFloat = 2
// 21032025
var timeoutSecondsSendCommandBT: CGFloat
// 26032025
var settingType: OneObdSettingType
var lastUsedSensorsPIDs = [PIDData]()
// 27032025
var sensorsMonitoringUnit: MeasurementUnit = .metric
var sensorsMonitoringDelayBeforeNextUpdate: TimeInterval = 0.3
// 05042025
var sensorsMonitoringAutoRetryCount: Int = 10
var sensorsMonitoringAutoRetryDelay: TimeInterval = 0.5
var sensorsMonitoringAutoRetryDelayFactor: Double = 1.5
var sensorsMonitoringAutoRetryDelayMax: Double = 5.0
// 09042025
var delayOptimizationSafetyFactor: CGFloat = 1.25
var delayOptimizationCap: CGFloat = 1.5
var delayOptimizationLowLimit: CGFloat = 0.05
convenience init(
name: String,
lastUsedSensorsPIDs: [PIDData]
) {
self.init(
name: name,
delayNanosecondsTimeoutAdapterInitialization: Self
.delayNanosecondsTimeoutAdapterInitializationDefault,
delayNanosecondsTimeoutDetectProtocolAutomatically: 0,
connectionTimeoutSeconds: 0,
bt: false
)
self.lastUsedSensorsPIDs = lastUsedSensorsPIDs
self.settingType = .sensorsSelectedPIDs
}
// 26032025
func updateLastMonitoredPIDs(lastUsedSensorsPIDs: [PIDData]) {
self.lastUsedSensorsPIDs = lastUsedSensorsPIDs
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
init(
name: String,
delayNanosecondsTimeoutAdapterInitialization: Int64,
delayNanosecondsTimeoutDetectProtocolAutomatically: Int64,
connectionTimeoutSeconds: CGFloat,
bt: Bool,
delayNanosecondsWifiRetryCommand: Int64 = 0,
delayNanosecondsBTPeripherals: Int64 = 0,
wifiIp: String = String(),
wifiPort: Int = 0,
timeoutSecondsSendCommandBT: CGFloat = 3,
settingType: OneObdSettingType = .connection
) {
self.name = name
self.delayNanosecondsTimeoutAdapterInitialization =
delayNanosecondsTimeoutAdapterInitialization
self.delayNanosecondsWifiRetryCommand =
delayNanosecondsWifiRetryCommand
self.delayNanosecondsTimeoutDetectProtocolAutomatically =
delayNanosecondsTimeoutDetectProtocolAutomatically
self.connectionTimeoutSeconds = connectionTimeoutSeconds
self.wifiIp = wifiIp.fullTrim()
self.wifiPort = wifiPort
self.customData = "\(self.wifiIp):\(self.wifiPort)"
self.delayNanosecondsBTPeripherals = delayNanosecondsBTPeripherals
self.bt = bt
self.timeoutSecondsSendCommandBT = timeoutSecondsSendCommandBT
self.settingType = settingType
}
// 31032025
func updateConnectionOptimizedValues(
delayNanosecondsTimeoutAdapterInitialization: Int64
) {
self.delayNanosecondsTimeoutAdapterInitialization =
delayNanosecondsTimeoutAdapterInitialization
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
// 30032025
func resetAllConnectionSettings() {
// Reset delays/timeouts
self.delayNanosecondsTimeoutAdapterInitialization =
Self.delayNanosecondsTimeoutAdapterInitializationDefault
// Reset obd protocol
self.obdProtocol = nil
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
// 11032025
func resetWifiAndPort() {
updateWifiIpAndPort(
ipAndPort: "\(Obd2Helper.mainIp):\(Obd2Helper.mainPort)"
)
}
func updateWifiIpAndPort(ipAndPort: String) {
let pair = ipAndPort.generateIpAndPort()
self.wifiIp = pair.ip
self.wifiPort = Int(pair.port)!
self.customData = "\(self.wifiIp):\(self.wifiPort)"
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
func updateObdProtocol(obdProtocol: PROTOCOL?) {
self.obdProtocol = obdProtocol
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
// 12032025
func updateObdConnectionName(connectionName: String?) {
self.obdConnectionName = connectionName
// STORE
OilerHelper.masterOilerSettings.storeOilerSettings()
}
}
SwiftOBD2 is a Swift package designed to simplify communication with vehicles using an ELM327 OBD2 adapter. It provides a straightforward and powerful interface for interacting with your vehicle's onboard diagnostics system, allowing you to retrieve real-time data and perform diagnostics. Sample App.
- iOS 14.0+ / macOS 11.0+
- Xcode 13.0+
- Swift 5.0+
Connection Management:
- Establishes connections to the OBD2 adapter via Bluetooth or Wi-Fi.
- Handles the initialization of the adapter and the vehicle connection process.
- Manages connection states (disconnected, connectedToAdapter, connectedToVehicle).Command Interface: Send and receive OBD2 commands for powerful interaction with your vehicle.
Data Retrieval:
- Supports requests for real-time vehicle data (RPM, speed, etc.) using standard OBD2 PIDs (Parameter IDs).
- Provides functions to continuously poll and retrieve updated measurements.
- Can get a list of supported PIDs from the vehicle.
Diagnostics:
- Retrieves and clears diagnostic trouble codes (DTCs).
- Gets the overall status of the vehicle's onboard systems.Sensor Monitoring: Retrieve and view data from various vehicle sensors in real time.
Adaptability and Configuration
- Can switch between Bluetooth and Wi-Fi communication seamlessly.
- Allows for testing and development with a demo mode.
- Connect to an OBD2 adapter via Bluetooth Low Energy (BLE)
- Retrieve error codes (DTCs) stored in the vehicle's OBD2 system
- Retrieve various OBD2 Parameter IDs (PIDs) for monitoring vehicle parameters
- Retrieve real-time vehicle data (RPM, speed, etc.) using standard OBD2 PIDs
- Get supported PIDs from the vehicle
- Clear error codes (DTCs) stored in the vehicle's OBD2 system
- Run tests on the OBD2 system
- Retrieve vehicle status since DTCs cleared
- Connect to an OBD2 adapter via WIFI
- Add support for custom PIDs
Create a New Swift Project:
- Open Xcode and start a new iOS project (You can use a simple "App" template).
Add the SwiftOBD2 Package:
- In Xcode, navigate to File > Add Packages...
- Enter this repository's URL: https://github.com/kkonteh97/SwiftOBD2/
- Select the desired dependency rule (version, branch, or commit).
Permissions and Capabilities:
- If your app will use Bluetooth, you need to request the appropriate permissions and capabilities:
- Add NSBluetoothAlwaysUsageDescription to your Info.plist file with a brief description of why your app needs to use Bluetooth.
- Navigate to the Signing & Capabilities tab in your project settings and add the Background Modes capability. Enable the Uses Bluetooth LE Accessories option.
- If your app will use Bluetooth, you need to request the appropriate permissions and capabilities:
- SwiftUI & Combine: Your code leverages the SwiftUI framework for building the user interface and Combine for reactive handling of updates from the OBDService.
- OBDService: This is the core class within the SwiftOBD2 package. It handles communication with the OBD-II adapter and processes data from the vehicle.
- OBDServiceDelegate: This protocol is crucial for receiving updates about the connection state and other events from the OBDService.
- OBDCommand: These represent specific requests you can make to the vehicle's ECU (Engine Control Unit) for data.
- Import and Setup
- Begin by importing the necessary modules:
import SwiftUI
import SwiftOBD2
import CombineViewModel
- Create a ViewModel class that conforms to the ObservableObject protocol. This allows your SwiftUI views to observe changes in the ViewModel.
- Inside the ViewModel:
- Define a @Published property measurements to store the collected data.
- Initialize an OBDService instance, setting the desired connection type (e.g., Bluetooth, Wi-Fi).
Connection Handling
- Implement the connectionStateChanged method from the OBDServiceDelegate protocol. Update the UI based on connection state changes (disconnected, connected, etc.) or handle any necessary logic.
Starting the Connection
- Create a startConnection function (ideally using async/await) to initiate the connection process with the OBD-II adapter. The OBDService's startConnection method will return useful OBDInfo about the vehicle. Like the Supported PIDs, Protocol, etc.
Stopping the Connection
- Create a stopConnection function to cleanly disconnect the service.
Retrieving Information
- Use the OBDService's methods to retrieve data from the vehicle, such as getting the vehicle's status, scanning for trouble codes, or requesting specific PIDs.
- getTroubleCodes: Retrieve diagnostic trouble codes (DTCs) from the vehicle's OBD-II system.
- getStatus: Retrieves Status since DTCs cleared.
- Use the OBDService's methods to retrieve data from the vehicle, such as getting the vehicle's status, scanning for trouble codes, or requesting specific PIDs.
Continuous Updates
- Use the startContinuousUpdates method to continuously poll and retrieve updated measurements from the vehicle. This method returns a Combine publisher that you can subscribe to for updates.
- Can also add PIDs to the continuous updates using the addPID method.
classViewModel:ObservableObject{@Publishedvarmeasurements:[OBDCommand:MeasurementResult]=[:]@PublishedvarconnectionState:ConnectionState=.disconnected
varcancellables=Set<AnyCancellable>()varrequestingPIDs:[OBDCommand]=[.mode1(.rpm)]{
didSet {addPID(command:requestingPIDs[-1])}}init(){
obdService.$connectionState
.assign(to:&$connectionState)}letobdService=OBDService(connectionType:.bluetooth)func startContinousUpdates(){
obdService.startContinuousUpdates([.mode1(.rpm)]) // You can add more PIDs
.sink{ completion inprint(completion)} receiveValue:{ measurements inself.measurements = measurements
}.store(in:&cancellables)}func addPID(command:OBDCommand){
obdService.addPID(command)}func stopContinuousUpdates(){
cancellables.removeAll()}func startConnection()asyncthrows{letobd2info=tryawait obdService.startConnection(preferedProtocol:.protocol6)print(obd2info)}func stopConnection(){
obdService.stopConnection()}func switchConnectionType(){
obdService.switchConnectionType(.wifi)}func getStatus()async{letstatus=try?await obdService.getStatus()print(status ??"nil")}func getTroubleCodes()async{lettroubleCodes=try?await obdService.scanForTroubleCodes()print(troubleCodes ??"nil")}}structContentView:View{@ObservedObjectvarviewModel=ViewModel()varbody:someView{VStack(spacing:20){Text("Connection State: \(viewModel.connectionState.rawValue)")ForEach(viewModel.requestingPIDs, id: \.self){ pid inText("\(pid.properties.description): \(viewModel.measurements[pid]?.value ??0)\(viewModel.measurements[pid]?.unit.symbol ??"")")}Button("Connect"){Task{do{tryawait viewModel.startConnection()
viewModel.startContinousUpdates()}catch{print(error)}}}.buttonStyle(.bordered)Button("Stop"){
viewModel.stopContinuousUpdates()}.buttonStyle(.bordered)Button("Add PID"){
viewModel.requestingPIDs.append(.mode1(.speed))}}.padding()}}A comprehensive list of supported OBD2 commands will be available in the full documentation (coming soon).
- Ensure you have a compatible ELM327 OBD2 adapter.
- Permissions: If using Bluetooth, your app may need to request Bluetooth permissions from the user.
- Error Handling: Implement robust error handling mechanisms to gracefully handle potential communication issues.
- Background Updates (Optional): If your app needs background OBD2 data updates, explore iOS background fetch capabilities and fine-tune your library and app to work effectively in the background.
This project welcomes your contributions! Feel free to open issues for bug reports or feature requests. To contribute code:
- Fork the repository.
- Create your feature branch.
- Commit your changes with descriptive messages.
- Submit a pull request for review.
The Swift OBD package is distributed under the MIT license. See the LICENSE file for more details.
