From 6c028c1ba9c371c79a13e4e63ae162d5367ce7ee Mon Sep 17 00:00:00 2001 From: kkonteh97 <55326260+kkonteh97@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:02:04 -0700 Subject: [PATCH] wifi fix --- .../SwiftOBD2/Communication/bleManager.swift | 41 +-- .../SwiftOBD2/Communication/mockManager.swift | 2 +- .../SwiftOBD2/Communication/wifiManager.swift | 201 ++++++-------- Sources/SwiftOBD2/Utils.swift | 61 +---- Sources/SwiftOBD2/elm327.swift | 246 ++++++++++-------- Sources/SwiftOBD2/obd2service.swift | 10 +- 6 files changed, 246 insertions(+), 315 deletions(-) diff --git a/Sources/SwiftOBD2/Communication/bleManager.swift b/Sources/SwiftOBD2/Communication/bleManager.swift index bf66b4d..8ac296b 100644 --- a/Sources/SwiftOBD2/Communication/bleManager.swift +++ b/Sources/SwiftOBD2/Communication/bleManager.swift @@ -229,21 +229,18 @@ class BLEManager: NSObject, CommProtocol { } switch characteristic { - case ecuReadCharacteristic: - processReceivedData(characteristicValue, completion: sendMessageCompletion) - - default: - guard let responseString = String(data: characteristicValue, encoding: .utf8) else { - return - } - logger.info("Unknown characteristic: \(characteristic)\nResponse: \(responseString)") + case ecuReadCharacteristic: + processReceivedData(characteristicValue, completion: sendMessageCompletion) + default: + if let responseString = String(data: characteristicValue, encoding: .utf8) { + logger.info("Unknown characteristic: \(characteristic)\nResponse: \(responseString)") + } } } func didFailToConnect(_: CBCentralManager, peripheral: CBPeripheral, error _: Error?) { logger.error("Failed to connect to peripheral: \(peripheral.name ?? "Unnamed")") - connectedPeripheral = nil - disconnectPeripheral() + resetConfigure() } func didDisconnect(_: CBCentralManager, peripheral: CBPeripheral, error _: Error?) { @@ -252,10 +249,10 @@ class BLEManager: NSObject, CommProtocol { } func willRestoreState(_: CBCentralManager, dict: [String: Any]) { - if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] { + if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral], let peripheral = peripherals.first { logger.debug("Restoring peripheral: \(peripherals[0].name ?? "Unnamed")") - peripherals[0].delegate = self - connectedPeripheral = peripherals[0] + connectedPeripheral = peripheral + connectedPeripheral?.delegate = self } } @@ -275,15 +272,17 @@ class BLEManager: NSObject, CommProtocol { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in self.connectionCompletion = { peripheral, error in - if let _ = peripheral { + if peripheral != nil { continuation.resume() } else if let error = error { continuation.resume(throwing: error) } + + self.connectionCompletion = nil } connect(to: peripheral) } - connectionCompletion = nil + self.connectionCompletion = nil } /// Sends a message to the connected peripheral and returns the response. @@ -296,7 +295,7 @@ class BLEManager: NSObject, CommProtocol { /// `BLEManagerError.peripheralNotConnected` if the peripheral is not connected. /// `BLEManagerError.timeout` if the operation times out. /// `BLEManagerError.unknownError` if an unknown error occurs. - func sendCommand(_ command: String) async throws -> [String] { + func sendCommand(_ command: String, retries: Int = 3) async throws -> [String] { guard sendMessageCompletion == nil else { throw BLEManagerError.sendingMessagesInProgress } @@ -361,12 +360,13 @@ class BLEManager: NSObject, CommProtocol { } func scanForPeripherals() async throws { - self.startScanning(nil) - // Wait 10 seconds for the scan to complete without blocking the main thread. + startScanning(nil) try await Task.sleep(nanoseconds: 10_000_000_000) - self.centralManager.stopScan() + stopScan() } + // MARK: - Utility Methods + /// Cancels the current operation and throws a timeout error. func Timeout( seconds: TimeInterval, @@ -398,8 +398,9 @@ class BLEManager: NSObject, CommProtocol { } } - func resetConfigure() { + private func resetConfigure() { ecuReadCharacteristic = nil + ecuWriteCharacteristic = nil connectedPeripheral = nil connectionState = .disconnected } diff --git a/Sources/SwiftOBD2/Communication/mockManager.swift b/Sources/SwiftOBD2/Communication/mockManager.swift index 544acdb..877999b 100644 --- a/Sources/SwiftOBD2/Communication/mockManager.swift +++ b/Sources/SwiftOBD2/Communication/mockManager.swift @@ -31,7 +31,7 @@ class MOCKComm: CommProtocol { var ecuSettings: MockECUSettings = .init() - func sendCommand(_ command: String) async throws -> [String] { + func sendCommand(_ command: String, retries: Int = 3) async throws -> [String] { logger.info("Sending command: \(command)") var header = "" diff --git a/Sources/SwiftOBD2/Communication/wifiManager.swift b/Sources/SwiftOBD2/Communication/wifiManager.swift index 4156e3f..404e0e2 100644 --- a/Sources/SwiftOBD2/Communication/wifiManager.swift +++ b/Sources/SwiftOBD2/Communication/wifiManager.swift @@ -11,7 +11,7 @@ import OSLog import CoreBluetooth protocol CommProtocol { - func sendCommand(_ command: String) async throws -> [String] + func sendCommand(_ command: String, retries: Int) async throws -> [String] func disconnectPeripheral() func connectAsync(timeout: TimeInterval, peripheral: CBPeripheral?) async throws func scanForPeripherals() async throws @@ -25,171 +25,124 @@ enum CommunicationError: Error { } class WifiManager: CommProtocol { - func scanForPeripherals() async throws { - } - + @Published var connectionState: ConnectionState = .disconnected + let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "wifiManager") var obdDelegate: OBDServiceDelegate? - @Published var connectionState: ConnectionState = .disconnected var connectionStatePublisher: Published.Publisher { $connectionState } var tcp: NWConnection? func connectAsync(timeout: TimeInterval, peripheral: CBPeripheral? = nil) async throws { - let host = NWEndpoint.Host("192.168.0.10") - guard let port = NWEndpoint.Port("35000") else { - throw CommunicationError.invalidData - } - tcp = NWConnection(host: host, port: port, using: .tcp) - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - tcp?.stateUpdateHandler = { newState in - switch newState { - case .ready: - print("Connected") - self.connectionState = .connectedToAdapter - continuation.resume(returning: ()) - case let .waiting(error): - print("Waiting \(error)") - case let .failed(error): - print("Failed \(error)") - continuation.resume(throwing: CommunicationError.errorOccurred(error)) - default: - break + let host = NWEndpoint.Host("192.168.0.10") + guard let port = NWEndpoint.Port("35000") else { + throw CommunicationError.invalidData + } + tcp = NWConnection(host: host, port: port, using: .tcp) + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + tcp?.stateUpdateHandler = { [weak self] newState in + guard let self = self else { return } + switch newState { + case .ready: + self.logger.info("Connected to \(host.debugDescription):\(port.debugDescription)") + self.connectionState = .connectedToAdapter + continuation.resume(returning: ()) + case let .waiting(error): + self.logger.warning("Connection waiting: \(error.localizedDescription)") + case let .failed(error): + self.logger.error("Connection failed: \(error.localizedDescription)") + self.connectionState = .disconnected + continuation.resume(throwing: CommunicationError.errorOccurred(error)) + default: + break + } } + tcp?.start(queue: .main) } - tcp?.start(queue: .main) } - } - func sendCommand(_ command: String) async throws -> [String] { + func sendCommand(_ command: String, retries: Int) async throws -> [String] { guard let data = "\(command)\r".data(using: .ascii) else { throw CommunicationError.invalidData } logger.info("Sending: \(command)") - return try await withRetry(retries: 3, delay: 0.3) { [weak self] in - try await self?.sendCommandInternal(data: data) ?? [] - } -// return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[String], Error>) in -// self.tcp?.send(content: data, completion: .contentProcessed { error in -// if let error = error { -// self.logger.error("Error sending data \(error)") -// continuation.resume(throwing: error) -// } -// -// self.tcp?.receive(minimumIncompleteLength: 1, maximumLength: 500, completion: { data, _, _, _ in -// guard let response = data, let string = String(data: response, encoding: .utf8) else { -// return -// } -// if string.contains(">") { -//// self.logger.info("Received \(string)") -// -// var lines = string -// .components(separatedBy: .newlines) -// .filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } -// print(lines.first ?? "") -// if lines.first?.lowercased() == "no data" { -// print("ola") -// } -// lines.removeLast() -// -// continuation.resume(returning: lines) -// } -// }) -// }) -// } + return try await self.sendCommandInternal(data: data, retries: retries) } - private func sendCommandInternal(data: Data, retries: Int = 3) async throws -> [String] { - var attempt = 0 - - while attempt < retries { - attempt += 1 + private func sendCommandInternal(data: Data, retries: Int) async throws -> [String] { + for attempt in 1...retries { + do { + let response = try await sendAndReceiveData(data) + if let lines = processResponse(response) { + return lines + } else if attempt < retries { + logger.info("No data received, retrying attempt \(attempt + 1) of \(retries)...") + try await Task.sleep(nanoseconds: 100_000_000) // 0.5 seconds delay + } + } catch { + if attempt == retries { + throw error + } + logger.warning("Attempt \(attempt) failed, retrying: \(error.localizedDescription)") + } + } + throw CommunicationError.invalidData + } - let result = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[String]?, Error>) in - self.tcp?.send(content: data, completion: .contentProcessed { error in + private func sendAndReceiveData(_ data: Data) async throws -> String { + return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + tcp?.send(content: data, completion: .contentProcessed { error in if let error = error { - self.logger.error("Error sending data: \(error)") - continuation.resume(throwing: error) + self.logger.error("Error sending data: \(error.localizedDescription)") + continuation.resume(throwing: CommunicationError.errorOccurred(error)) return } - self.tcp?.receive(minimumIncompleteLength: 1, maximumLength: 500, completion: { data, _, _, error in + self.tcp?.receive(minimumIncompleteLength: 1, maximumLength: 500) { data, _, _, error in if let error = error { - self.logger.error("Error receiving data: \(error)") - continuation.resume(throwing: error) + self.logger.error("Error receiving data: \(error.localizedDescription)") + continuation.resume(throwing: CommunicationError.errorOccurred(error)) return } - guard let response = data, let string = String(data: response, encoding: .utf8) else { - self.logger.warning("Received empty response") + guard let response = data, let responseString = String(data: response, encoding: .utf8) else { + self.logger.warning("Received invalid or empty data") continuation.resume(throwing: CommunicationError.invalidData) return } - if string.contains(">") { - self.logger.info("Received response: \(string)") - - var lines = string - .components(separatedBy: .newlines) - .filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - lines.removeLast() - - if lines.first?.lowercased() == "no data" { - self.logger.info("No data received on attempt \(attempt)") - if attempt < retries { - // Retry the operation - self.logger.info("Retrying due to 'no data' response (Attempt \(attempt) of \(retries))") - continuation.resume(returning: nil) // Indicate the need to retry - } else { - // No more retries, return an error - self.logger.warning("Max retries reached, failing with 'no data'") - continuation.resume(throwing: CommunicationError.invalidData) - } - return - } else { - continuation.resume(returning: lines) - } - } else { - self.logger.warning("Incomplete response received") - continuation.resume(throwing: CommunicationError.invalidData) - } - }) + continuation.resume(returning: responseString) + } }) } + } - if let result = result { - return result // Success, return the lines - } + private func processResponse(_ response: String) -> [String]? { + logger.info("Processing response: \(response)") + var lines = response.components(separatedBy: .newlines).filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - // Delay before retrying if needed - if attempt < retries { - try await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds delay + guard !lines.isEmpty else { + logger.warning("Empty response lines") + return nil } - } - - throw CommunicationError.invalidData - } + if lines.last?.contains(">") == true { + lines.removeLast() + } - private func withRetry(retries: Int, delay: TimeInterval, task: @escaping () async throws -> T) async throws -> T { - var attempt = 0 - while true { - do { - return try await task() - } catch { - attempt += 1 - if attempt >= retries { - throw error - } - logger.warning("Attempt \(attempt) failed, retrying in \(delay) seconds...") - try await Task.sleep(nanoseconds: UInt64(delay * Double(NSEC_PER_SEC))) - } + if lines.first?.lowercased() == "no data" { + return nil } - } + + return lines + } func disconnectPeripheral() { tcp?.cancel() } + + func scanForPeripherals() async throws {} } diff --git a/Sources/SwiftOBD2/Utils.swift b/Sources/SwiftOBD2/Utils.swift index 3b09158..cbeb0bd 100644 --- a/Sources/SwiftOBD2/Utils.swift +++ b/Sources/SwiftOBD2/Utils.swift @@ -45,7 +45,7 @@ func bytesToInt(_ byteArray: Data) -> Int { // } // } -public enum PROTOCOL: String, Codable { +public enum PROTOCOL: String, Codable, CaseIterable { case protocol1 = "1", protocol2 = "2", @@ -106,63 +106,14 @@ public enum PROTOCOL: String, Codable { } var cmd: String { - switch self { - case .protocol1: - return "ATSP1" - - case .protocol2: - return "ATSP2" - - case .protocol3: - return "ATSP3" - - case .protocol4: - return "ATSP4" - - case .protocol5: - return "ATSP5" - - case .protocol6: - return "ATSP6" - - case .protocol7: - return "ATSP7" - - case .protocol8: - return "ATSP8" - - case .protocol9: - return "ATSP9" - - case .protocolA: - return "ATSPA" - - case .protocolB: - return "ATSPB" - - case .protocolC: - return "ATSPC" - - case .NONE: - return "" - } + return "ATSP\(self.rawValue)" } public static let asArray: [PROTOCOL] = [ - protocol1, - protocol2, - protocol3, - protocol4, - protocol5, - protocol6, - protocol7, - protocol8, - protocol9, - protocolA, - protocolB, - protocolC, - NONE - ] + .protocol1, .protocol2, .protocol3, .protocol4, .protocol5, + .protocol6, .protocol7, .protocol8, .protocol9, .protocolA, + .protocolB, .protocolC, .NONE + ] } // dictionary of all the protocols diff --git a/Sources/SwiftOBD2/elm327.swift b/Sources/SwiftOBD2/elm327.swift index 24073c3..29cd037 100644 --- a/Sources/SwiftOBD2/elm327.swift +++ b/Sources/SwiftOBD2/elm327.swift @@ -18,8 +18,41 @@ import Foundation import OSLog import CoreBluetooth +enum ELM327Error: Error, LocalizedError { + case noProtocolFound + case invalidResponse(message: String) + case adapterInitializationFailed + case ignitionOff + case invalidProtocol + case timeout + case connectionFailed(reason: String) + case unknownError + + var errorDescription: String? { + switch self { + case .noProtocolFound: + return "No compatible OBD protocol found." + case .invalidResponse(let message): + return "Invalid response received: \(message)" + case .adapterInitializationFailed: + return "Failed to initialize adapter." + case .ignitionOff: + return "Vehicle ignition is off." + case .invalidProtocol: + return "Invalid or unsupported OBD protocol." + case .timeout: + return "Operation timed out." + case .connectionFailed(let reason): + return "Connection failed: \(reason)" + case .unknownError: + return "An unknown error occurred." + } + } +} + + class ELM327 { - private var obdProtocol: PROTOCOL = .NONE +// private var obdProtocol: PROTOCOL = .NONE var canProtocol: CANProtocol? private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.com", category: "ELM327") @@ -33,6 +66,8 @@ class ELM327 { } } + private var r100: [String] = [] + var connectionState: ConnectionState = .disconnected { didSet { obdDelegate?.connectionStateChanged(state: connectionState) @@ -41,16 +76,19 @@ class ELM327 { init(comm: CommProtocol) { self.comm = comm - comm.connectionStatePublisher - .sink { [weak self] state in - self?.connectionState = state - } - .store(in: &cancellables) + setupConnectionStateSubscriber() } -// func switchToDemoMode(_ isDemoMode: Bool) { -// stopConnection() -// } + private func setupConnectionStateSubscriber() { + comm.connectionStatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + self?.connectionState = state + self?.obdDelegate?.connectionStateChanged(state: state) + self?.logger.debug("Connection state updated: \(state.hashValue)") + } + .store(in: &cancellables) + } // MARK: - Adapter and Vehicle Setup @@ -66,79 +104,76 @@ class ELM327 { /// - `SetupError.peripheralNotFound` if the peripheral could not be found. /// - `SetupError.ignitionOff` if the vehicle's ignition is not on. /// - `SetupError.invalidProtocol` if the protocol is not recognized. - func setupVehicle(preferedProtocol: PROTOCOL?) async throws -> OBDInfo { - var obdProtocol: PROTOCOL? - - if let desiredProtocol = preferedProtocol { - do { - obdProtocol = try await manualProtocolDetection(desiredProtocol: desiredProtocol) - } catch { - obdProtocol = nil // Fallback to autoProtocol - } - } - - if obdProtocol == nil { - obdProtocol = try await connectToVehicle(autoProtocol: true) - } + func setupVehicle(preferredProtocol: PROTOCOL?) async throws -> OBDInfo { +// var obdProtocol: PROTOCOL? + let detectedProtocol = try await detectProtocol(preferredProtocol: preferredProtocol) - guard let obdProtocol = obdProtocol else { - throw SetupError.noProtocolFound - } +// guard let obdProtocol = detectedProtocol else { +// throw SetupError.noProtocolFound +// } - self.obdProtocol = obdProtocol - self.canProtocol = protocols[obdProtocol] +// self.obdProtocol = obdProtocol + self.canProtocol = protocols[detectedProtocol] let vin = await requestVin() - // try await setHeader(header: ECUHeader.ENGINE) +// try await setHeader(header: "7E0") let supportedPIDs = await getSupportedPIDs() guard let messages = try canProtocol?.parse(r100) else { - throw SetupError.invalidResponse(message: "Invalid response to 0100") + throw ELM327Error.invalidResponse(message: "Invalid response to 0100") } let ecuMap = populateECUMap(messages) connectionState = .connectedToVehicle - return OBDInfo(vin: vin, supportedPIDs: supportedPIDs, obdProtocol: obdProtocol, ecuMap: ecuMap) + return OBDInfo(vin: vin, supportedPIDs: supportedPIDs, obdProtocol: detectedProtocol, ecuMap: ecuMap) } - /// Establishes a connection to the vehicle's ECU. - /// - Parameter autoProtocol: Whether to attempt automatic protocol detection. - /// - Returns: The established OBD protocol. - func connectToVehicle(autoProtocol: Bool) async throws -> PROTOCOL? { - if autoProtocol { - guard let obdProtocol = try await autoProtocolDetection() else { - logger.error("No protocol found") - throw SetupError.noProtocolFound + // MARK: - Protocol Selection + + /// Detects the appropriate OBD protocol by attempting preferred and fallback protocols. + /// - Parameter preferredProtocol: An optional preferred protocol to attempt first. + /// - Returns: The detected `PROTOCOL`. + /// - Throws: `ELM327Error` if detection fails. + private func detectProtocol(preferredProtocol: PROTOCOL? = nil) async throws -> PROTOCOL { + self.logger.info("Starting protocol detection...") + + if let protocolToTest = preferredProtocol { + self.logger.info("Attempting preferred protocol: \(protocolToTest.description)") + if try await testProtocol(protocolToTest) { + return protocolToTest + } else { + self.logger.warning("Preferred protocol \(protocolToTest.description) failed. Falling back to automatic detection.") } - return obdProtocol } else { - guard let obdProtocol = try await manualProtocolDetection(desiredProtocol: nil) else { - logger.error("No protocol found") - throw SetupError.noProtocolFound + do { + return try await detectProtocolAutomatically() + } catch { + return try await detectProtocolManually() } - return obdProtocol } - } - // MARK: - Protocol Selection + self.logger.error("Failed to detect a compatible OBD protocol.") + throw ELM327Error.noProtocolFound + } /// Attempts to detect the OBD protocol automatically. /// - Returns: The detected protocol, or nil if none could be found. /// - Throws: Various setup-related errors. - private func autoProtocolDetection() async throws -> PROTOCOL? { - _ = try await okResponse(message: "ATSP0") + private func detectProtocolAutomatically() async throws -> PROTOCOL { + _ = try await okResponse("ATSP0") try? await Task.sleep(nanoseconds: 1_000_000_000) - _ = try await sendCommand("0100", withTimeoutSecs: 20) + _ = try await sendCommand("0100") let obdProtocolNumber = try await sendCommand("ATDPN") + guard let obdProtocol = PROTOCOL(rawValue: String(obdProtocolNumber[0].dropFirst())) else { - throw SetupError.invalidResponse(message: "Invalid protocol number: \(obdProtocolNumber)") + throw ELM327Error.invalidResponse(message: "Invalid protocol number: \(obdProtocolNumber)") } - try await testProtocol(obdProtocol: obdProtocol) + try await testProtocol(obdProtocol) return obdProtocol } @@ -147,50 +182,42 @@ class ELM327 { /// - Parameter desiredProtocol: An optional preferred protocol to attempt first. /// - Returns: The detected protocol, or nil if none could be found. /// - Throws: Various setup-related errors. - private func manualProtocolDetection(desiredProtocol: PROTOCOL?) async throws -> PROTOCOL? { - if let desiredProtocol = desiredProtocol { - try? await testProtocol(obdProtocol: desiredProtocol) - return desiredProtocol - } - while obdProtocol != .NONE { - do { - try await testProtocol(obdProtocol: obdProtocol) - return obdProtocol /// Exit the loop if the protocol is found successfully - } catch { - // Other errors are propagated - obdProtocol = obdProtocol.nextProtocol() + private func detectProtocolManually() async throws -> PROTOCOL { + for protocolOption in PROTOCOL.allCases where protocolOption != .NONE { + self.logger.info("Testing protocol: \(protocolOption.description)") + _ = try await okResponse(protocolOption.cmd) + if try await testProtocol(protocolOption) { + return protocolOption } } /// If we reach this point, no protocol was found logger.error("No protocol found") - throw SetupError.noProtocolFound + throw ELM327Error.noProtocolFound } // MARK: - Protocol Testing - private var r100: [String] = [] - /// Tests a given protocol by sending a 0100 command and checking for a valid response. /// - Parameter obdProtocol: The protocol to test. /// - Throws: Various setup-related errors. - private func testProtocol(obdProtocol: PROTOCOL) async throws { + private func testProtocol(_ obdProtocol: PROTOCOL) async throws -> Bool { // test protocol by sending 0100 and checking for 41 00 response - _ = try await okResponse(message: obdProtocol.cmd) - -// _ = try await sendCommand("0100", withTimeoutSecs: 10) - let r100 = try await sendCommand("0100", withTimeoutSecs: 10) + do { - if r100.joined().contains("NO DATA") { - throw SetupError.ignitionOff - } - self.r100 = r100 + let response = try await sendCommand("0100", retries: 3) - guard r100.joined().contains("41 00") else { - logger.error("Invalid response to 0100") - throw SetupError.invalidProtocol + if response.joined().contains("4100") { + self.logger.info("Protocol \(obdProtocol.description) is valid.") + self.r100 = response + return true + } else { + self.logger.warning("Protocol \(obdProtocol.rawValue) did not return valid 0100 response.") + return false + } + } catch { + self.logger.warning("Error testing protocol \(obdProtocol.description): \(error.localizedDescription)") + return false } - - logger.info("Protocol \(obdProtocol.rawValue) found") } // MARK: - Adapter Initialization @@ -202,26 +229,25 @@ class ELM327 { /// Initializes the adapter by sending a series of commands. /// - Parameter setupOrder: A list of commands to send in order. /// - Throws: Various setup-related errors. - func adapterInitialization(setupOrder: [OBDCommand.General] = [.ATZ, .ATD, .ATL0, .ATE0, .ATH1, .ATAT1, .ATRV, .ATDPN]) async throws { - for step in setupOrder { - switch step { - case .ATD, .ATL0, .ATE0, .ATH1, .ATAT1, .ATSTFF, .ATH0: - _ = try await okResponse(message: step.properties.command) - case .ATZ: - _ = try await sendCommand(step.properties.command) - case .ATRV: - /// get the voltage - _ = try await sendCommand(step.properties.command) - case .ATDPN: - /// Describe current protocol number - let protocolNumber = try await sendCommand(step.properties.command) - obdProtocol = PROTOCOL(rawValue: protocolNumber[0]) ?? .protocol9 + func adapterInitialization() async throws { +// [.ATZ, .ATD, .ATL0, .ATE0, .ATH1, .ATAT1, .ATRV, .ATDPN] + self.logger.info("Initializing ELM327 adapter...") + do { + try await sendCommand("ATZ") // Reset adapter + try await okResponse("ATE0") // Echo off + try await okResponse("ATL0") // Linefeeds off + try await okResponse("ATS0") // Spaces off + try await okResponse("ATH1") // Headers off + try await okResponse("ATSP0") // Set protocol to automatic + self.logger.info("ELM327 adapter initialized successfully.") + } catch { + self.logger.error("Adapter initialization failed: \(error.localizedDescription)") + throw ELM327Error.adapterInitializationFailed } - } } private func setHeader(header: String) async throws { - _ = try await okResponse(message: "AT SH " + header) + _ = try await okResponse("AT SH " + header) } func stopConnection() { @@ -231,17 +257,17 @@ class ELM327 { // MARK: - Message Sending - func sendCommand(_ message: String, withTimeoutSecs _: TimeInterval = 5) async throws -> [String] { - return try await comm.sendCommand(message) + func sendCommand(_ message: String, retries: Int = 1) async throws -> [String] { + return try await comm.sendCommand(message, retries: retries) } - private func okResponse(message: String) async throws -> [String] { + private func okResponse(_ message: String) async throws -> [String] { let response = try await sendCommand(message) if response.contains("OK") { return response } else { logger.error("Invalid response: \(response)") - throw SetupError.invalidResponse(message: "message: \(message), \(String(describing: response.first))") + throw ELM327Error.invalidResponse(message: "message: \(message), \(String(describing: response.first))") } } @@ -485,16 +511,16 @@ enum ECUHeader { } // Possible setup errors -enum SetupError: Error { - case noECUCharacteristic - case invalidResponse(message: String) - case noProtocolFound - case adapterInitFailed - case timeout - case peripheralNotFound - case ignitionOff - case invalidProtocol -} +//enum SetupError: Error { +// case noECUCharacteristic +// case invalidResponse(message: String) +// case noProtocolFound +// case adapterInitFailed +// case timeout +// case peripheralNotFound +// case ignitionOff +// case invalidProtocol +//} public struct OBDInfo: Codable, Hashable { public var vin: String? diff --git a/Sources/SwiftOBD2/obd2service.swift b/Sources/SwiftOBD2/obd2service.swift index 84efc6f..e63b39b 100644 --- a/Sources/SwiftOBD2/obd2service.swift +++ b/Sources/SwiftOBD2/obd2service.swift @@ -93,7 +93,7 @@ public class OBDService: ObservableObject, OBDServiceDelegate { /// - Returns: Information about the connected vehicle (`OBDInfo`). /// - Throws: Errors if the vehicle initialization process fails. func initializeVehicle(_ preferedProtocol: PROTOCOL?) async throws -> OBDInfo { - let obd2info = try await elm327.setupVehicle(preferedProtocol: preferedProtocol) + let obd2info = try await elm327.setupVehicle(preferredProtocol: preferedProtocol) return obd2info } @@ -165,7 +165,7 @@ public class OBDService: ObservableObject, OBDServiceDelegate { /// - Returns: measurement result /// - Throws: Errors that might occur during the request process. public func requestPIDs(_ commands: [OBDCommand], unit: MeasurementUnit) async throws -> [OBDCommand: MeasurementResult] { - let response = try await sendCommand("01" + commands.compactMap { $0.properties.command.dropFirst(2) }.joined()) + let response = try await sendCommandInternal("01" + commands.compactMap { $0.properties.command.dropFirst(2) }.joined(), retries: 10) guard let responseData = try elm327.canProtocol?.parse(response).first?.data else { return [:] } @@ -185,7 +185,7 @@ public class OBDService: ObservableObject, OBDServiceDelegate { /// - Throws: Errors that might occur during the request process. public func sendCommand(_ command: OBDCommand) async throws -> Result { do { - let response = try await sendCommand(command.properties.command) + let response = try await sendCommandInternal(command.properties.command, retries: 3) guard let responseData = try elm327.canProtocol?.parse(response).first?.data else { return .failure(.noData) } @@ -243,9 +243,9 @@ public class OBDService: ObservableObject, OBDServiceDelegate { /// - Parameter message: The raw command to send. /// - Returns: The raw response from the vehicle. /// - Throws: Errors that might occur during the request process. - public func sendCommand(_ message: String, withTimeoutSecs _: TimeInterval = 5) async throws -> [String] { + public func sendCommandInternal(_ message: String, retries: Int) async throws -> [String] { do { - return try await elm327.sendCommand(message) + return try await elm327.sendCommand(message, retries: retries) } catch { throw OBDServiceError.commandFailed(command: message, error: error) }