Skip to content

Commit

Permalink
wifi fix
Browse files Browse the repository at this point in the history
  • Loading branch information
kkonteh97 committed Sep 4, 2024
1 parent aec42b1 commit 6c028c1
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 315 deletions.
41 changes: 21 additions & 20 deletions Sources/SwiftOBD2/Communication/bleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?) {
Expand All @@ -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
}
}

Expand All @@ -275,15 +272,17 @@ class BLEManager: NSObject, CommProtocol {

try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) 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.
Expand All @@ -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
}
Expand Down Expand Up @@ -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<R>(
seconds: TimeInterval,
Expand Down Expand Up @@ -398,8 +398,9 @@ class BLEManager: NSObject, CommProtocol {
}
}

func resetConfigure() {
private func resetConfigure() {
ecuReadCharacteristic = nil
ecuWriteCharacteristic = nil
connectedPeripheral = nil
connectionState = .disconnected
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftOBD2/Communication/mockManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""

Expand Down
201 changes: 77 additions & 124 deletions Sources/SwiftOBD2/Communication/wifiManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ConnectionState>.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<Void, Error>) 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<Void, Error>) 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<String, Error>) 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<T>(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 {}
}
Loading

0 comments on commit 6c028c1

Please sign in to comment.