EC25-E How to establish serial communication

How do I establish AT command communication with EC25E directly with the device port?
We are trying to capture phonecall information directly from the modem (as well as incoming caller number).

So taken from modemmanager, details about the firmware and driver:

Driver says it exposes multiple device file handles, namely ttyUSB2 and ttyUSB3 both apparently being used for at commands.

When using modem manager directly to send a raw AT command, we get a reply.
Command: mmcli -m 0 --command=‘ATI’
response: ‘Quectel
Revision: EC25EFAR08A02M4G’

However when we use pyserial module, whatever we write into the port, we just read empty strings back.
Changing parameters as well as sleep timings does not play an effect, we always get empty response.

Example code below:

import serial
import time

port = "/dev/ttyUSB2"
baudrate = 115200
command = "ATI"
payload = bytes(command, 'utf-8')

ser = serial.Serial(port=port, baudrate=baudrate, bytesize=8, timeout=1)
read_val =

You need to include the AT command termination character, so:

command = "ATI\r"

This is in addition to requiring the termination character \r:

ModemManager normally controls both AT ports available on the modem. If you want to use pyserial you can free one of the ports using a udev rule. It can work even if ModemManager uses it, but in my experience the communication is unstable.

You can check if the ports are in use by a program using fuser or lsof.

Example excerpt for a rule:

SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"

ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"

The idVendor and idProduct are for an EG25-G module in this case. Also there are 2 rules bc. we use information from different levels of the udev system.

It should also possible to rewrite the official udev rule instead of creating your own. For my system the file is at /lib/udev/rules.d/77-mm-quectel-port-types.rules.

Search for a line like the following and adjust it (guess ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="0" to disable):

ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"

That’s a good point. I uninstalled ModemManager years ago because of the conflict, and use my own scripts for all modem-related tasks.

In any case, I now also include:

exclusive = True

when opening a serial port in Python to ensure I’ve got the port to myself.