Automatic connection to internet on startup with the Telit modem

Hi,
I’m using the LTE HAT with the Telit LE910C1 modem.
I’d like it to connect to the internet immediately on startup if power was disconnected.
Now to connect the internet I first need to run minicom and inside it the AT#ECM=1,0 command (as in the tutorial Internet Connection with Telit LE910C1 module using ECM Mode).
Is there a way to configure the modem to connect automatically on startup? or otherwise to run minicom and the connection command on startup?

Thanks

I’d recommend using the cu command and the python pexpect library.

cu is a command-line oriented tool that does basically what minicom does, but doesn’t make you mess with escape sequences or deal with its escaped output. So it is easier to process AT commands and their results with it.

Hi,
Thank you for your comment.
Can you please give an example for the use of cu for AT commands?

Ok, this code grabs GPS coordinates from the Telit modem and runs on a raspberry Pi with raspbian. This code handles a lot of error cases and is fairly robust when the modem gets confused.

You need to install cu with:

sudo apt-get install cu

You need to install pexpect with:

pip install pexpect

… and you really ought to install pexpect in a virtual environment.

#!/usr/bin/env python3

"""exerciser for gps functions of telit/sixfab wireless card

    Usage:

    python3 gpstest.py [--verbose] [--count n] [--hdop v] --device /dev/ttyUSB2

    --device sets the device to send AT commands to and is passed to cu, assuming 8-bit and 115200 bps
    --hdop v max horizontal dilution of precision acceptable (default 99, 1.5 or so is good)
    --verbose enables lots of debugging output
    --count n performs n GPS fixes (default 1)
"""

import argparse
import time
import os
import pexpect
import datetime

verbose = False
child: pexpect.pty_spawn.spawn  # so pycharm does not complain

# TODO:  command line argument to set how many times you try to get a decent fix (now hardcoded to 30)
# TODO:  best-effort (min hdop value) as well as threshold
# TODO:  check_device() needs to do a better job
# TODO:  full-on class interface


class GPSError(Exception):
    pass


class GPSDataError(GPSError):
    pass


class GPSBusted(GPSError):
    pass


class GPSTimeout(GPSError):
    pass


def wait_for_result():
    """wait for OK, ERROR, or timeout return "OK" or raise an exception"""
    i = child.expect([pexpect.TIMEOUT, "OK\r\n", "ERROR\r\n"], timeout=10)

    if i == 0:
        if verbose:
            print(f"[gps] Timed out!")

        raise GPSTimeout
    elif i == 1:
        if verbose:
            print(f"[gps] Saw OK")

        return "OK"
    elif i == 2:
        if verbose:
            print(f"[gps] Saw ERROR")

        raise GPSError

    raise GPSBusted


def send_at_ok():
    """simple test to send an at command and make sure things are cool"""

    child.send("\r")
    time.sleep(0.1)

    for k in range(10):
        if verbose:
            print(f"[gps] Trying to send 'AT', try #{k+1}")

        child.send("AT\r")

        try:
            _ = wait_for_result()

        except GPSTimeout:
            time.sleep(0.5)

        else:
            return

    raise GPSTimeout


def get_gpsp_status():
    """get the current state of the gps (in case it is on)"""
    if verbose:
        print(f"[gps] Getting GPS power state with AT$GPSP?")

    child.send(f"AT$GPSP?\r")

    i = child.expect([pexpect.TIMEOUT, r"[$]GPSP:[ ]+([0-9])\r\n"])

    rc = None

    if i == 0:
        if verbose:
            print(f"[gps] Timeout, failing!")

        raise GPSTimeout
    elif i == 1:
        if verbose:
            print(f"[gps] results:  {child.match[1]}")

        rc = child.match[1]
        rc = int(rc.decode('utf-8'))

        if verbose:
            print(f"[gps] GPS power state is {rc}")

    _ = wait_for_result()
    return rc


def send_gpsp(state):
    """turn the gps on or off"""

    child.send(f"AT$GPSP={state}\r")

    _ = wait_for_result()
    return True


def send_gpsp_on():
    """turn on gps"""
    if verbose:
        print(f"[gps] Sending AT$GPSP=1 to power on GPS")

    return send_gpsp(1)


def send_gpsp_off():
    """turn off gps"""
    if verbose:
        print(f"[gps] Sending AT$GPSP=0 to power off GPS")

    return send_gpsp(0)


def get_one_position():
    """get position, matched string or None"""

    if verbose:
        print(f"[gps] Sending AT$GPSACP to get current position")

    child.send("AT$GPSACP\r")

    i = child.expect([pexpect.TIMEOUT, r"[$]GPSACP:[ ]+([0-9A-Z,.]+)\r\n"])
    rc = None

    if i == 0:
        if verbose:
            print(f"[gps] Timeout, failing")

        raise GPSTimeout
    elif i == 1:
        if verbose:
            print(f"[gps] results:  {child.match[1]}")

        rc = child.match[1]
        rc = rc.decode('utf-8')

    _ = wait_for_result()
    return rc


def parse_latitude(lat):
    """produce normal signed fractional latitude from weird gps value"""
    direction = lat[-1:]
    sign = 0
    
    if direction == "N":
        sign = 1
    elif direction == "S":
        sign = -1

    return sign * (float(int(lat[:2])) + float(lat[2:-1]) / 60)


def parse_longitude(lon):
    """produce normal signed fractional longitude from weird gps value"""
    direction = lon[-1:]
    sign = 0

    if direction == "E":
        sign = 1
    elif direction == "W":
        sign = -1

    return sign * (float(int(lon[:3])) + float(lon[3:-1]) / 60)


def parse_date_time(date, timeofday):
    """not used and probably not useful as far as I can see"""
    day = int(date[0:2])
    mon = int(date[2:4])
    yy = int(date[4:6])

    hh = int(timeofday[0:2])
    mm = int(timeofday[2:4])
    ss = int(timeofday[4:6])
    us = int(timeofday[7:]) * 1000

    return datetime.datetime(yy, mon, day, hh, mm, ss, us, datetime.timezone(datetime.timedelta(0)))


def parse_values(position):
    """break out values from GPS sentence"""
    if len(position) != 12:
        raise GPSDataError

    rc = dict(
        GMTIME=position[0],
        LATITUDE=position[1],
        LONGITUDE=position[2],
        HDOP=position[3],
        ALTITUDE=position[4],
        fix=position[5],
        COG=position[6],
        SPKM=position[7],
        SPKN=position[8],
        DATE=position[9],
        NSAT_GPS=position[10],
        NSAT_GLONASS=position[11],
    )

    rc["latitude"] = parse_latitude(rc["LATITUDE"])
    rc["longitude"] = parse_longitude(rc["LONGITUDE"])
    rc["altitude"] = float(rc["ALTITUDE"])
    rc["hdop"] = float(rc["HDOP"])
    rc["whence"] = parse_date_time(rc["DATE"], rc["GMTIME"])

    return rc


def get_position(hdop, total=30):
    """
        get gps position.
        retry until a valid sentence is returned with an hdop less or equal to than the passed hdop
        returns a dict
    """
    for k in range(total):
        if k > 0:
            time.sleep(10)

        results = get_one_position()

        if results is None:
            time.sleep(0.1)
            continue

        position = results.split(',')

        if len(position) >= 6 and position[5] == '3' and hdop >= float(position[3]):
            return parse_values(position)

        if verbose:
            print(f"[gps] get_position(): Try #{k+1}, got {position[5]}, hdop was '{position[3]}'")

    if verbose:
        print(f"[gps] Got nothing")

    return None


def check_device(d):
    return os.path.exists(d)


def main():
    global child, verbose

    ap = argparse.ArgumentParser()
    ap.add_argument("--verbose", action='store_true', default=False)
    ap.add_argument("--device", required=True, type=str)
    ap.add_argument("--hdop", required=False, type=float, default=99.0)
    ap.add_argument("--count", required=False, type=int, default=1)

    args = ap.parse_args()
    verbose = args.verbose

    if not check_device(args.device):
        print(f"[gps.error]  {args.device} does not exist")
        exit(1)

    child = pexpect.spawn(f"cu --nostop --parity none --baud 115200 --line {args.device} dir")

    time.sleep(0.5)

    try:
        send_at_ok()

        if get_gpsp_status() == 0:
            _ = send_gpsp_on()

        for k in range(args.count):
            if k > 0:
                time.sleep(15)

            x = get_position(args.hdop)
            if x is not None:
                if verbose:
                    print(f"[gps] ***RESULTS***: {x['latitude']:.3f},{x['longitude']:.3f}")
                else:
                    print(f"{x['latitude']:.3f},{x['longitude']:.3f}")
            else:
                print(f"[gps] nowhere")

        time.sleep(0.1)

        _ = send_gpsp_off()

    except GPSError as e:
        print(f"[gps] ***ERROR***: {e}:")
        print(child)

    child.sendline("~.")
    time.sleep(0.5)


if __name__ == "__main__":
    main()

… and a simpler example with just cu:

pi@raspberrypi(ro):~$ cu --nostop --parity none --baud 115200 --line /dev/ttyUSB3 dir
Connected.
AT
OK
AT+CCLK?
+CCLK: "21/08/19,07:08:16-28"

OK
~.

Disconnected.
pi@raspberrypi(ro):~$ 

~. on a line by itself terminates the cu program.

Thank you David, the “–device” param is like ‘/dev/ttyUSB2’ ?

Yes, generally /dev/ttyUSB2 or /dev/ttyUSB3, unless you mess with AT#USBCFG and then who knows what it is.

1 Like

Thanks, I’ll try your code and I’ll let you know if it solve my problem!

Hey, @a.lancioni ,

Did the code I gave you work? Or do you need more help or ideas?

Hi @davidbonn ,

thanks for your code snippet.
I am using a Sixfab SIM, which repeatedly sends commands over the same serial line and gets an answer in the following form:

+COPS: 0,0,"o2 - de",7

OK

When this message occurs, the program crashes.
Do you have any idea, where I would have to make changes, to prevent that?

Edit:
Int get_one_position() I added a regex to match the above message.
I hoped to solve the problem with this, but it still crashes at the line i = child.expect([pexpect.TIMEOUT, r"[$]GPSACP:[ ]+([0-9A-Z,.]+)\r\n"]) with an EOF exception.

Since my script is never using the AT +COPS... commands, it is hard to see how you get that result unless some other process is also sending commands to the serial port at the same time. If you are running Sixfab Connect a program does periodically connect to the serial port.

The first thing I would try is to switch your program to the other serial port (usually you have both /dev/ttyUSB2 and /dev/ttyUSB3) and see if that fixes the problem.

Architecturally I like to split it out so that GPS tracking happens on one serial port and connection status and health checks happen on the other. Another option is to have one program handle both on one serial port and then use the other one with minicom for debugging.

Yeah the AT+COPS is from a script by sixfab. I believe it is a script asking for the status, to provide that information to Sixfab CORE.

I will try your hint with another port in the next days.
Thank you! :slight_smile:

1 Like