mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-07-25 19:32:03 +08:00
@@ -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()
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user