Tier 2: WiFi + Bluetooth ELM327 transports
- obdcore/transport.py: pluggable byte transports -- SerialTransport,
TcpTransport (WiFi ELM327, stdlib socket), BleTransport (experimental, via
optional 'bleak'; background asyncio loop buffering notifications). ble_scan().
- ElmLink refactored onto a transport with .serial()/.tcp()/.ble() factories
(close/cmd now go through self.io); no behavior change for serial.
- Controller.connect(conn={kind:serial|wifi|ble,...}); GUI connection bar gains
a transport selector (Serial/USB/BT-SPP | WiFi host:port | Bluetooth LE + Scan).
- Classic-Bluetooth needs no new code (pairs as a serial port); WiFi needs no
extra deps; BLE is opt-in (bleak not bundled, so CI binaries keep building).
- tests/test_transport.py: drives ElmLink over a fake ELM TCP server end-to-end
(connect, RPM, readiness, VIN). All suites pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs
This commit is contained in:
+24
-9
@@ -74,25 +74,40 @@ def find_ports():
|
||||
class ElmLink:
|
||||
PROMPT = b">"
|
||||
|
||||
def __init__(self, port, baud=38400, verbose=False):
|
||||
if serial is None:
|
||||
raise RuntimeError("pyserial not installed (pip install pyserial)")
|
||||
def __init__(self, transport, verbose=False):
|
||||
"""transport: any object with write/read/reset_input_buffer/close.
|
||||
Use the .serial() / .tcp() / .ble() factory helpers to build one."""
|
||||
self.io = transport
|
||||
self.verbose = verbose
|
||||
self.ser = serial.Serial(port, baud, timeout=0.2)
|
||||
self.protocol = "?"
|
||||
time.sleep(0.3)
|
||||
self.ser.reset_input_buffer()
|
||||
self.io.reset_input_buffer()
|
||||
|
||||
@classmethod
|
||||
def serial(cls, port, baud=38400, **kw):
|
||||
from . import transport as tp
|
||||
return cls(tp.SerialTransport(port, baud), **kw)
|
||||
|
||||
@classmethod
|
||||
def tcp(cls, host, port=35000, **kw):
|
||||
from . import transport as tp
|
||||
return cls(tp.TcpTransport(host, port), **kw)
|
||||
|
||||
@classmethod
|
||||
def ble(cls, address, **kw):
|
||||
from . import transport as tp
|
||||
return cls(tp.BleTransport(address), **kw)
|
||||
|
||||
# -- low-level --
|
||||
def cmd(self, s, settle=0.0, timeout=4.0):
|
||||
self.ser.reset_input_buffer()
|
||||
self.ser.write((s + "\r").encode())
|
||||
self.io.reset_input_buffer()
|
||||
self.io.write((s + "\r").encode())
|
||||
if settle:
|
||||
time.sleep(settle)
|
||||
buf = bytearray()
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
chunk = self.ser.read(256)
|
||||
chunk = self.io.read(256)
|
||||
if chunk:
|
||||
buf += chunk
|
||||
if self.PROMPT in buf:
|
||||
@@ -209,6 +224,6 @@ class ElmLink:
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self.ser.close()
|
||||
self.io.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user