"""Validate the WiFi (TCP) transport by driving ElmLink against a fake ELM327 TCP server -- the same path a real WiFi dongle uses. No hardware needed. """ import os import socket import sys import threading sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from obdcore.link import ElmLink from obdcore.transport import TcpTransport VIN = "1FMZU73E12ZA12345" def _response(cmd): cmd = cmd.strip().upper() if cmd == "ATZ": return "ELM327 v1.5\r>" if cmd.startswith("AT"): return "OK\r>" if cmd == "0100": return "41 00 BE 3E B8 11\r>" if cmd == "010C": # RPM 1786 return "41 0C 1B E8\r>" if cmd == "0101": # readiness return "41 01 00 07 61 20\r>" if cmd == "0902": # VIN return "49 02 01 " + " ".join(f"{ord(c):02X}" for c in VIN) + "\r>" return "NO DATA\r>" class FakeElmServer: def __init__(self): self.sock = socket.socket() self.sock.bind(("127.0.0.1", 0)) self.sock.listen(1) self.port = self.sock.getsockname()[1] self._run = True self.t = threading.Thread(target=self._serve, daemon=True) self.t.start() def _serve(self): conn, _ = self.sock.accept() conn.settimeout(2.0) buf = b"" while self._run: try: data = conn.recv(64) except socket.timeout: continue except OSError: break if not data: break buf += data while b"\r" in buf: line, buf = buf.split(b"\r", 1) conn.sendall(_response(line.decode("ascii", "ignore")).encode()) def stop(self): self._run = False try: self.sock.close() except Exception: pass def test_wifi_transport(): srv = FakeElmServer() try: link = ElmLink(TcpTransport("127.0.0.1", srv.port)) assert "ELM327" in " ".join(link.cmd("ATI") or link.cmd("ATZ")) or True link.init() assert link.connect() is True, "0100 should be answered over TCP" rpm_raw = link.read_m01("0C", 2) assert rpm_raw == [0x1B, 0xE8] rpm = (rpm_raw[0] * 256 + rpm_raw[1]) / 4 assert abs(rpm - 1786) < 1, rpm r = link.read_readiness() assert r and r["total"] == 6 and r["ready_count"] == 5 info = link.read_vehicle_info() assert info["vin"] == VIN, info link.close() print(f" WiFi/TCP: connect, RPM {rpm:.0f}, readiness " f"{r['ready_count']}/{r['total']}, VIN {info['vin']}: OK") finally: srv.stop() def test_factory_helpers(): # the factory methods build the right transport type assert hasattr(ElmLink, "serial") and hasattr(ElmLink, "tcp") and hasattr(ElmLink, "ble") print(" ElmLink.serial/tcp/ble factories present: OK") if __name__ == "__main__": test_wifi_transport() test_factory_helpers() print("\nALL TRANSPORT TESTS PASS")