rm pyserial (#38311)

* rm pyserial

* lil more

* lil more
This commit is contained in:
Adeeb Shihadeh
2026-07-08 21:06:53 -07:00
committed by GitHub
parent 3b3f5967ec
commit bf5540c364
8 changed files with 287 additions and 26 deletions
+6 -6
View File
@@ -6,7 +6,6 @@ import fcntl
import hashlib
import os
import requests
import serial
import subprocess
import sys
import termios
@@ -20,6 +19,7 @@ from pathlib import Path
from openpilot.common.time_helpers import system_time_valid
from openpilot.common.esim.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
from openpilot.common.serial import Serial, SerialException
GSMA_CI_BUNDLE = str(Path(__file__).parent / "gsma_ci_bundle.pem")
@@ -133,7 +133,7 @@ class AtClient:
self._device = device
self._baud = baud
self._timeout = timeout
self._serial: serial.Serial | None = None
self._serial: Serial | None = None
def send_raw(self, data: bytes) -> None:
self._ensure_serial()
@@ -185,14 +185,14 @@ class AtClient:
pass
self._serial = None
if self._serial is None:
self._serial = serial.Serial(self._device, baudrate=self._baud, timeout=self._timeout)
self._serial = Serial(self._device, baudrate=self._baud, timeout=self._timeout)
def query(self, cmd: str) -> list[str]:
self._ensure_serial()
try:
self._send(cmd)
return self._expect()
except serial.SerialException:
except SerialException:
self._ensure_serial(reconnect=True)
self._send(cmd)
return self._expect()
@@ -208,7 +208,7 @@ class AtClient:
if self._serial:
try:
self._serial.reset_input_buffer()
except (OSError, serial.SerialException, termios.error):
except (OSError, SerialException, termios.error):
self._ensure_serial(reconnect=True)
for line in self.query(f'AT+CCHO="{ISDR_AID}"'):
if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()):
@@ -230,7 +230,7 @@ class AtClient:
try:
self._open_isdr_once()
return
except (RuntimeError, TimeoutError, termios.error, serial.SerialException):
except (RuntimeError, TimeoutError, termios.error, SerialException):
time.sleep(OPEN_ISDR_RETRY_DELAY_S)
if attempt == OPEN_ISDR_RESET_ATTEMPT:
self._reset_modem()
+4 -3
View File
@@ -3,7 +3,6 @@ import fcntl
import json
import logging
import os
import serial
import signal
import subprocess
import tempfile
@@ -13,6 +12,8 @@ from ipaddress import IPv4Address, AddressValueError
from enum import Enum
from openpilot.common.serial import Serial
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
@@ -96,7 +97,7 @@ class PPPSession:
def reset_data_port():
"""Drop DTR on PPP_PORT so the modem terminates any stuck PPP session."""
try:
with serial.Serial(PPP_PORT, 460800, timeout=1) as s:
with Serial(PPP_PORT, baudrate=460800, timeout=1) as s:
s.dtr = False
time.sleep(0.2)
s.dtr = True
@@ -220,7 +221,7 @@ class Modem:
os.close(fd)
return []
try:
with serial.Serial(AT_PORT, 9600, timeout=5) as ser:
with Serial(AT_PORT, baudrate=9600, timeout=5) as ser:
ser.reset_input_buffer()
ser.write((cmd + "\r").encode())
lines = []
+272
View File
@@ -0,0 +1,272 @@
import errno
import fcntl
import os
import select
import struct
import termios
import time
# Modem control lines (linux/termios.h); fall back to common x86_64 values.
TIOCMBIS = getattr(termios, "TIOCMBIS", 0x5416)
TIOCMBIC = getattr(termios, "TIOCMBIC", 0x5417)
TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002)
TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004)
_TIOCM_DTR = struct.pack("I", TIOCM_DTR)
_TIOCM_RTS = struct.pack("I", TIOCM_RTS)
class SerialException(OSError):
pass
class Serial:
def __init__(self, port: str, baudrate: int = 9600, timeout: float | None = None, *,
rtscts: bool = False, dsrdtr: bool = False, exclusive: bool = False):
self._port = port
self._baudrate = baudrate
self._timeout = timeout
self._rtscts = rtscts
self._dsrdtr = dsrdtr
self._exclusive = exclusive
self._dtr = True
self._fd = -1
self.open()
def __enter__(self):
return self
def __exit__(self, *args) -> None:
self.close()
@property
def fd(self) -> int:
self._ensure_open()
return self._fd
@property
def baudrate(self) -> int:
return self._baudrate
@baudrate.setter
def baudrate(self, value: int) -> None:
self._baudrate = int(value)
if self._fd >= 0:
self._configure()
@property
def dtr(self) -> bool:
return self._dtr
@dtr.setter
def dtr(self, value: bool) -> None:
self._dtr = bool(value)
if self._fd >= 0:
self._set_line(TIOCM_DTR, _TIOCM_DTR, self._dtr)
def open(self) -> None:
if self._fd >= 0:
return
try:
self._fd = os.open(self._port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
except OSError as e:
self._fd = -1
raise SerialException(e.errno, f"could not open port {self._port}: {e}") from e
try:
if self._exclusive:
try:
fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as e:
raise SerialException(e.errno, f"could not exclusively lock port {self._port}: {e}") from e
self._configure()
# When not using hardware DSR/DTR handshaking, drive lines ourselves.
if not self._dsrdtr:
try:
self._set_line(TIOCM_DTR, _TIOCM_DTR, self._dtr)
if not self._rtscts:
self._set_line(TIOCM_RTS, _TIOCM_RTS, True)
except OSError as e:
if e.errno not in (errno.EINVAL, errno.ENOTTY):
raise
self.reset_input_buffer()
except BaseException:
self._close_fd()
raise
def close(self) -> None:
self._close_fd()
def read(self, size: int = 1) -> bytes:
self._ensure_open()
if size <= 0:
return b""
buf = bytearray()
deadline = self._deadline()
while len(buf) < size:
remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
if not self._wait_readable(remaining):
break
try:
chunk = os.read(self._fd, size - len(buf))
except InterruptedError:
continue
except OSError as e:
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
if self._timeout == 0:
break
continue
raise SerialException(e.errno, f"read failed: {e}") from e
if not chunk:
break
buf.extend(chunk)
return bytes(buf)
def readline(self) -> bytes:
self._ensure_open()
buf = bytearray()
deadline = self._deadline()
while True:
remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
if deadline is not None and remaining == 0.0 and not buf:
# match pyserial: timed-out readline returns empty
if not self._wait_readable(0.0):
return b""
elif not self._wait_readable(remaining):
return bytes(buf)
try:
chunk = os.read(self._fd, 1)
except InterruptedError:
continue
except OSError as e:
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
if self._timeout == 0:
return bytes(buf)
continue
raise SerialException(e.errno, f"read failed: {e}") from e
if not chunk:
return bytes(buf)
buf.extend(chunk)
if chunk == b"\n":
return bytes(buf)
def write(self, data: bytes) -> int:
self._ensure_open()
if not data:
return 0
view = memoryview(data)
total = 0
while total < len(data):
try:
n = os.write(self._fd, view[total:])
except InterruptedError:
continue
except OSError as e:
if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
select.select([], [self._fd], [], None)
continue
raise SerialException(e.errno, f"write failed: {e}") from e
if n == 0:
raise SerialException("write returned 0")
total += n
return total
def flush(self) -> None:
self._ensure_open()
termios.tcdrain(self._fd)
def reset_input_buffer(self) -> None:
self._ensure_open()
termios.tcflush(self._fd, termios.TCIFLUSH)
def reset_output_buffer(self) -> None:
self._ensure_open()
termios.tcflush(self._fd, termios.TCOFLUSH)
def _close_fd(self) -> None:
if self._fd >= 0:
try:
if self._exclusive:
fcntl.flock(self._fd, fcntl.LOCK_UN)
except OSError:
pass
try:
os.close(self._fd)
except OSError:
pass
self._fd = -1
def _ensure_open(self) -> None:
if self._fd < 0:
raise SerialException("port is not open")
def _deadline(self) -> float | None:
if self._timeout is None:
return None
if self._timeout == 0:
return time.monotonic()
return time.monotonic() + self._timeout
def _wait_readable(self, timeout: float | None) -> bool:
"""Return True if fd is readable. timeout None blocks; 0 polls."""
if timeout is not None and timeout < 0:
timeout = 0.0
try:
ready, _, _ = select.select([self._fd], [], [], timeout)
except InterruptedError:
return False
return bool(ready)
def _baud_constant(self, baudrate: int) -> int:
try:
return getattr(termios, f"B{baudrate}")
except AttributeError as e:
raise ValueError(f"unsupported baud rate: {baudrate}") from e
def _configure(self) -> None:
self._ensure_open()
try:
attrs = termios.tcgetattr(self._fd)
except termios.error as e:
raise SerialException(f"could not get port attributes: {e}") from e
iflag, oflag, cflag, lflag, _ispeed, _ospeed, cc = attrs
# raw binary 8N1
iflag = 0
oflag = 0
lflag = 0
cflag |= termios.CLOCAL | termios.CREAD
cflag &= ~termios.CSIZE
cflag |= termios.CS8
cflag &= ~(termios.PARENB | termios.PARODD | termios.CSTOPB)
if hasattr(termios, "CRTSCTS"):
if self._rtscts:
cflag |= termios.CRTSCTS
else:
cflag &= ~termios.CRTSCTS
speed = self._baud_constant(self._baudrate)
cc = list(cc)
# Non-blocking reads are handled via select + O_NONBLOCK; keep VMIN/VTIME at 0.
cc[termios.VMIN] = 0
cc[termios.VTIME] = 0
try:
termios.tcsetattr(self._fd, termios.TCSANOW, [iflag, oflag, cflag, lflag, speed, speed, cc])
except termios.error as e:
raise SerialException(f"could not configure port: {e}") from e
# Keep the fd non-blocking so timeout=0 and select work consistently.
flags = fcntl.fcntl(self._fd, fcntl.F_GETFL)
fcntl.fcntl(self._fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
def _set_line(self, _bit: int, packed: bytes, enabled: bool) -> None:
request = TIOCMBIS if enabled else TIOCMBIC
fcntl.ioctl(self._fd, request, packed)
+2 -2
View File
@@ -1,7 +1,8 @@
import select
from serial import Serial
from struct import pack, unpack_from, calcsize
from openpilot.common.serial import Serial
def _gen_crc16_reflected_table(poly: int) -> list[int]:
# poly is the reflected form (e.g. 0x8408 for CRC-16 poly 0x1021)
@@ -25,7 +26,6 @@ def crc16_x25(data: bytes) -> int:
crc = _CRC16_X25_TABLE[(crc ^ b) & 0xFF] ^ (crc >> 8)
return crc ^ 0xFFFF
class ModemDiag:
def __init__(self):
self.serial = self.open_serial()
+1 -1
View File
@@ -6,7 +6,6 @@ import signal
import itertools
import math
import time
from serial import Serial
import datetime
from typing import NoReturn
from struct import unpack_from, calcsize, pack
@@ -17,6 +16,7 @@ from openpilot.common.gpio import gpio_init, gpio_set
from openpilot.common.utils import retry
from openpilot.common.time_helpers import system_time_valid
from openpilot.common.hardware.tici.pins import GPIO
from openpilot.common.serial import Serial
from openpilot.common.swaglog import cloudlog
from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv
from openpilot.system.qcomgpsd.structs import (dict_unpacker, position_report, relist,
+2 -2
View File
@@ -2,7 +2,6 @@
import sys
import time
import signal
import serial
import struct
import requests
import urllib.parse
@@ -11,6 +10,7 @@ from datetime import datetime, UTC
from openpilot.cereal import messaging
from openpilot.common.time_helpers import system_time_valid
from openpilot.common.params import Params
from openpilot.common.serial import Serial
from openpilot.common.swaglog import cloudlog
from openpilot.common.hardware import TICI
from openpilot.common.gpio import gpio_init, gpio_set
@@ -64,7 +64,7 @@ def get_assistnow_messages(token: str) -> list[bytes]:
class TTYPigeon:
def __init__(self):
self.tty = serial.VTIMESerial(UBLOX_TTY, baudrate=9600, timeout=0)
self.tty = Serial(UBLOX_TTY, baudrate=9600, timeout=0)
def send(self, dat: bytes) -> None:
self.tty.write(dat)
-1
View File
@@ -11,7 +11,6 @@ authors = [
dependencies = [
# multiple users
"sounddevice", # micd + soundd
"pyserial", # pigeond + qcomgpsd
"requests", # many one-off uses
"sympy", # rednose + friends
"tqdm", # cars (fw_versions.py) on start + many one-off uses
Generated
-11
View File
@@ -991,7 +991,6 @@ dependencies = [
{ name = "psutil" },
{ name = "pycapnp" },
{ name = "pyjwt" },
{ name = "pyserial" },
{ name = "pyzmq" },
{ name = "qrcode" },
{ name = "requests" },
@@ -1078,7 +1077,6 @@ requires-dist = [
{ name = "psutil" },
{ name = "pycapnp", specifier = "==2.1.0" },
{ name = "pyjwt" },
{ name = "pyserial" },
{ name = "pytest", marker = "extra == 'testing'" },
{ name = "pytest-cpp", marker = "extra == 'testing'" },
{ name = "pytest-mock", marker = "extra == 'testing'" },
@@ -1378,15 +1376,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
]
[[package]]
name = "pyserial"
version = "3.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" },
]
[[package]]
name = "pytest"
version = "9.1.1"