Quectel.Modem

class Quectel.Modem(modem_port: str = '/dev/ttyUSB3', modem_baud: int = 115200)

AT-command interface for Quectel/Telit cellular modems.

Wraps a serial.Serial connection to the modem’s AT-command port and provides high-level helpers around the standard 3GPP AT commands (AT+CSQ, AT+CREG?, AT+CGREG?, AT+CEREG?, AT+ICCID, AT+CGSN, ATI3). The vendor is auto-detected via identify_module(), which probes USB devices through drivers.DeviceDetector.DeviceDetector.DeviceDetector.

Variables:
  • portwrite (str) – Serial device path for AT commands (e.g. /dev/ttyUSB3).

  • module_name (str | None) – Detected module vendor – "Quectel", "Telit" or None if detection failed.

  • detector (DeviceDetector) – USB device scanner used during port discovery.

  • network_towers (list[str]) – Unicode block characters used to render signal-strength bar graphs.

  • signalMap (list[tuple[int, int, str, int]]) – RSSI-to-(dBm, label, bars) lookup table populated from the AT+CSQ specification.

  • cmd_delay (float) – Inter-command delay in seconds.

  • dataDelay (int) – Generic data-wait delay in seconds.

  • DEBUG (bool) – Verbose-logging flag.

  • TIMEOUT (int) – Read timeout in milliseconds.

  • port_write (str | None) – Class-level default AT port (overridden per instance).

Example

>>> from drivers.Quectel.Quectel import Modem
>>> modem = Modem()
>>> modem.signalstrength()
-73

Auto-detect the modem module and open the serial AT port.

Instantiates a drivers.DeviceDetector.DeviceDetector.DeviceDetector, calls identify_module() to bind portwrite, opens a serial.Serial connection with a 10 s read timeout and a 3 s write timeout, and immediately closes it so subsequent helpers can re-open the port idempotently.

Parameters:
  • modem_port (str) – Fallback serial device path used if auto-detection fails. Defaults to /dev/ttyUSB3.

  • modem_baud (int) – UART baud rate for the AT-command port. Defaults to 115200.

Returns:

Constructor.

Return type:

None

Raises:

serial.SerialException – If the underlying /dev/ttyUSB* device cannot be opened.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> modem = Modem("/dev/ttyUSB3", 115200)

Note

This constructor does not issue any AT command – the first command goes out when one of the get* accessors is invoked.

getGPRSNetwork() str | None | Literal[0]

Query GPRS / packet-domain registration status via AT+CGREG?.

Issues AT+CGREG?, discards the echo line, and parses the <n>,<stat> payload from the second response line. The raw comma-separated string is returned for caller-side interpretation.

Parameters:

None.

Returns:

Trimmed registration status string (e.g. "0,1"), None if the response did not contain the expected +CGREG: prefix, or 0 if a serial exception was caught.

Return type:

str | None | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getGPRSNetwork()
'0,1'

Note

AT command: AT+CGREG? – GPRS Network Registration Status (3GPP TS 27.007 section 7.2).

getIMEI() str | None | Literal[0]

Read the modem IMEI via AT+CGSN=1.

Issues AT+CGSN=1 (the SVN-tagged variant that prefixes the response with +CGSN:), strips the surrounding quotes, and returns the bare 15-digit identifier.

Parameters:

None.

Returns:

15-digit IMEI string, None when the +CGSN: prefix was missing, or 0 on serial exception.

Return type:

str | None | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getIMEI()
'352099001761481'

Note

AT command: AT+CGSN=1 – Request Product Serial Number Identification (3GPP TS 27.007 section 5.4).

getIccid() str | None | Literal[0]

Read the SIM-card ICCID via AT+ICCID.

Issues AT+ICCID, drops the echo, and extracts the Integrated Circuit Card Identifier from the response line beginning with +ICCID:.

Parameters:

None.

Returns:

19-/20-digit ICCID string trimmed of whitespace, None when the prefix was missing, or 0 on serial exception.

Return type:

str | None | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getIccid()
'8991000900000000123'

Note

AT command: AT+ICCID (Quectel proprietary alias for AT+CRSM SIM-file read of EF-ICCID, 3GPP TS 51.011).

getLTECheck() str | None | Literal[0]

Query LTE / EPS registration status via AT+CEREG?.

Issues AT+CEREG?, discards the echo, and parses the <n>,<stat> payload from the response.

Parameters:

None.

Returns:

Trimmed registration status string (e.g. "0,1"), None when the response lacked the +CEREG: prefix, or 0 on serial exception.

Return type:

str | None | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getLTECheck()
'0,1'

Note

AT command: AT+CEREG? – EPS Network Registration Status (3GPP TS 27.007 section 7.10).

getModuleInfo() tuple[str, str] | Literal[0]

Read the modem manufacturer and model identification via ATI3.

Issues ATI3, treats the first response line as the manufacturer (_company) and the second as the model (_model), and returns them as a tuple.

Parameters:

None.

Returns:

(company, model) strings (e.g. ("Quectel", "EC25")) or 0 on serial exception.

Return type:

tuple[str, str] | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getModuleInfo()
('Quectel', 'EC25')

Note

AT command: ATI3 – Display Product Identification Information (V.250 / vendor-specific extension).

getNetworkCheck() str | None | Literal[0]

Query circuit-switched (2G/3G) registration status via AT+CREG?.

Issues AT+CREG?, skips the echo line, and parses the <n>,<stat> payload from the response.

Parameters:

None.

Returns:

Trimmed registration status string (e.g. "0,1"), None when the response lacked the +CREG: prefix, or 0 on serial exception.

Return type:

str | None | Literal[0]

Raises:

None – All errors are caught and logged.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().getNetworkCheck()
'0,1'

Note

AT command: AT+CREG? – Network Registration Status (3GPP TS 27.007 section 7.2).

identify_module() None | Literal['Quectel'] | Literal['Telit']

Auto-detect the modem vendor by scanning USB serial devices.

Polls DeviceDetector.scan_gsm_devices() up to three times (with a one-second back-off between retries) looking for a recognised Quectel or Telit USB descriptor. On a successful match the detected modem/AT port is assigned to portwrite; if the descriptor exists but the named port mapping is incomplete, the driver falls back to /dev/ttyUSB2.

Parameters:

None – Operates on instance state only.

Returns:

The detected vendor tag ("Quectel" or "Telit") or None if no module was found after three retries.

Return type:

None | Literal[“Quectel”] | Literal[“Telit”]

Raises:

None – All scan errors are swallowed and logged via context_logger.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> m = Modem()
>>> m.identify_module()
'Quectel'

Note

Vendor detection is descriptor-based (USB VID/PID parsed in DeviceDetector); it does not issue any AT command yet, so it is safe to call before the serial port is opened.

micros() int

Return the current monotonic-ish wall-clock time in microseconds.

Parameters:

None.

Returns:

Integer microsecond timestamp derived from time.time() scaled by 1_000_000.

Return type:

int

Raises:

None.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().micros()
1721650000123456

Note

Not an AT command – internal timing helper used to bound readline() waits.

millis() int

Return the current wall-clock time in milliseconds.

Parameters:

None.

Returns:

Integer millisecond timestamp derived from time.time() scaled by 1_000.

Return type:

int

Raises:

None.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().millis()
1721650000123

Note

Not an AT command – used to back the TIMEOUT watchdog inside read() and Gps.enable_module().

read() list[bytes]

Wait for and drain all available response lines from the modem.

Polls __modem_port.inWaiting until at least one byte is available, then loops serial.Serial.readline() until the input buffer is empty. Heartbeat seconds are emitted via context_logger while waiting.

Parameters:

None – Operates on the bound serial port.

Returns:

All response lines received from the modem, including the trailing \r\n.

Return type:

list[bytes]

Raises:

serial.SerialException – If the port disappears mid-read.

Example

>>> from drivers.Quectel.Quectel import Modem
>>> m = Modem()
>>> m.read()
[b'OK\r\n']

Note

Low-level helper used by send_command(); does not issue any AT command itself.

send_command(command: str, read_counter: int = 1) None

Send an arbitrary AT command and log every response line received.

Opens __modem_port if needed, appends a carriage return, writes the command, then reads read_counter batches of response lines via read(). Each line is logged through context_logger and the port is always closed in the finally clause.

Parameters:
  • command (str) – AT command string without the trailing \r.

  • read_counter (int) – Number of response read cycles to perform. Defaults to 1. Use a higher value for commands that emit URC follow-ups.

Returns:

Response lines are logged, not returned. Use the specialised get* helpers when you need the value back.

Return type:

None

Raises:

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().send_command("AT")

Note

Generic AT pipe – accepts any 3GPP TS 27.007 command supported by the underlying module.

signalstrength() int | None

Query cellular signal strength via AT+CSQ and translate to dBm.

Issues AT+CSQ, parses the <rssi> field from the response, and looks the value up in signalMap to produce a dBm reading plus a human-readable label (Marginal/OK/Good/Excellent) and a bar-graph string drawn from network_towers. An <rssi> of 31 is treated as the “unknown but strong” sentinel (-51 dBm).

Parameters:

None.

Returns:

Signal strength in dBm (negative integer in the [-113, -51] range), or None on any serial / parsing error.

Return type:

int | None

Raises:
  • None – All errors are caught and logged via

  • context_logger

Example

>>> from drivers.Quectel.Quectel import Modem
>>> Modem().signalstrength()
-73

Note

AT command: AT+CSQ – Signal Quality Report (3GPP TS 27.007 section 8.5).

DEBUG = False
TIMEOUT = 60000
__modem_port
_timeout_ = 60
cmd_delay = 0.1
dataDelay = 2
detector
module_name = 'Quectel'
network_towers: ClassVar[list[str]] = ['▁', '▂', '▄', '▆', '█']
port_write = None
portwrite = '/dev/ttyUSB3'
signalMap: ClassVar[list[tuple[int, int, str, int]]]