Swift-UDS is an implementation of the Unified Diagnostic Services, written in Swift.
This library is an effort to implement various diagnostic protocols originating in the automotive space, such as:
- ISO 14229:2020 : Road vehicles — Unified diagnostic services (UDS)
- ISO 15765-2:2016 : Road vehicles — Diagnostic communication over Controller Area Network (DoCAN)
- SAE J1979:201408 : Surface Vehicle Standard – (R) E/E Diagnostic Test Modes (OBD2)
- GMW 3110:2010 : General Motors Local Area Network Enhanced Diagnostic Test Mode Specification (GMLAN)
This is an SPM-compatible package for the use with Xcode (on macOS) or other SPM-compliant consumer (wherever Swift runs on). First, add the package to your package dependencies:
.package(url: "https://github.com/Automotive-Swift/Swift-UDS", branch: "master")
Then, add the library to your target dependencies:
dependencies: ["Swift-UDS"]
First, make sure you are in an async
hronous context. Then, get a pair of streams to/from your OBD2 adapter. Assuming you are using CornucopiaStreams, this is as simple as:
let streams = try await Stream.CC_getStreamPair(to: url, timeout: 3)
Once you have the streams, create an Adapter
:
let adapter = UDS.GenericSerialAdapter(inputStream: streams.0, outputStream: streams.1)
Make sure you observe its state notifications:
NotificationCenter.default.addObserver(forName: UDS.AdapterDidUpdateState, object: nil, queue: nil) { _ in
...
}
Then, start connecting to the bus:
adapter.connect(via: .auto)
When the adapter's state changes to .connected(busProtocol: BusProtocol)
you can observe the negotiated protocol:
guard case let .connected(busProtocol) = adapter.state else { return }
print("Connected to the bus w/ protocol: \(busProtocol)")
While you could already communicate on a low level with the adapter now, it is recommended
that you install a thread-safe Pipeline
first:
let pipeline = UDS.Pipeline(adapter: adapter)
The final step is creating a session. There are sessions for OBD2 communication and for UDS communication. For this example, let's create the former:
let session = UDS.OBD2Session(via: pipeline)
And this is how we would read the vehicle identification number (VIN) of your connected vehicle:
do {
let vin = try await session.readString(service: .vehicleInformation(pid: UDS.VehicleInformationType.vin))
print("VIN: \(vin)"
} catch {
print("Could not read the VIN: \(error)")
}
In 2016, I started working on automotive diagnostics. I created the iOS app OBD2 Expert, which by now has been downloaded over 500.000 times. I released the underlying framework LTSupportAutomotive, written in Objective-C, as open source.
In 2021, I revisited this domain and attmpted to implement the UDS protocol on top of the existing library.
Pretty soon though it became obvious that there are too many OBD2-isms in LTSupportAutomotive
and extending it with UDS would be overcomplicated.
Together with my new focus on Swift, I decided to start from scratch with the library CornucopiaUDS.
By August 2021, the first working version of CornucopiaUDS
was working and used in the automotive tuning app TPE-Tuning.
From the start though, the plan has been to make this a "transitioning" library, in particular because of the forthcoming
concurrency features debuting in Swift 5.5: Communication with external hardware is asynchronous by nature, so async
/await
and the actor
abstractions is a natural fit.
This library is supposed to become the successor of both LTSupportAutomotive
and CornucopiaUDS
. Due to Swift 5.5, on Apple
platforms it comes with a relatively high deployment target – limiting you to iOS 15, tvOS 15, watchOS 8, and macOS 12 (and above).
This package contains three modules, Swift_UDS
, Swift_UDS_Adapter
, and Swift_UDS_Session
:
Swift_UDS
contains common UDS and OBD2 definitions, types, and structures,Swift_UDS_Adapter
contains generic support for OBD2 adapters with a reference implementation for serial adapters and a thread-safeactor
pipeline,Swift_UDS_Session
contains both a UDS and a OBD2 session abstraction for higher level UDS and OBD2 calls.
This library is hardware-agnostic and is supposed to work with all kinds of OBD2 adapters. The reference adapter implementation is for generic serial streaming adapters, such as
- ELM327 (and its various clones), only for OBD2, the ELM327 is NOT suitable for UDS
- STN11xx-based (e.g., OBDLINK SX),
- STN22xx-based (e.g., OBDLINK MX+, OBDLINK EX, OBDLINK CX),
- WGSoft.de UniCarScan 2100 and later,
Support for direct CAN-adapters (such as the Rusoku TouCAN) is also on the way.
For the actual communication, I advise to use CornucopiaStreams, which transforms WiFi, Bluetooth Classic, BTLE, and TTY into a common stream-based interface.
Currently: Feature-wise on par with CornucopiaUDS, but hardly tested yet
- October 2021: Public version available. Should be on-par with CornucopiaUDS, but did not receive any substantial testing.
- October 2021: Some concurrency issues have been solved in the meantime, hence starting to (re)implement the first bunch of classes.
- September 2021: Hitting real hard blocks with the state of
async
/await
in the yet-to-be-released Swift 5.5. - August 2021: Nothing there yet, I'm still planning.
Although I have successfully used this library as the base for an ECU reprogramming app, it has not yet been battle-tested. Moreoever, while it has been designed with all kind of bus protocols in mind, support for CAN is most advanced. Older bus protocols, such as K-LINE, J1850, and ISO9141-2 should be working at least with OBD2, but your mileage might vary.
UDS is about 50% done – I have started with the necessary calls to upload (TESTER -> ECU) new flash firmwares. The other way is not done yet. There is limited support for the diagnostic commands from KWP and GMLAN and I'm not against adding more, but it's not a personal preference.
Although I plan to implement the full set of OBD2 calls, the primary focus has been on UDS. I have started to implement a bunch of OBD2 calls to lay out the path for contributors, but did not have time yet to do more. You might want to have a look at the messageSpecs, if you want to help.
Note that this might be an appropriate case for a @ResultBuilder
.
Feel free to use this under the obligations of the MIT. I welcome all forms of contributions. Stay safe and sound!