Power Saving Mode (PSM) in NB-IoT: problems with reconnecting after deepsleep



  • I've been struggling with gpy lte issues surrounding attachment/connection for ages! I'm using CAT-M1 with 41065 or 43818 modem firmware.

    1. Peter, are the tau/act timers NB-IoT specific or are they relevant to CAT-M1 too?
    2. Jan, I can't find the AT+CPSM cmd in the monarch LR5.1.1.0 manual? Is there a link that explains what this cmd does? It returns an error with my modem

  • Global Moderator

    @jand For PSM, you need to to issue an AT command, to specify two timers to the LTE network:

    • at what intervals will the device connect to the network
    • how long will the LTE modem be active

    See below the code I used.

    I measure 130 uA during deepsleep with a FiPy in Expansion board 3.1 and after wakeup it is still attached, so no time for re-attaching.

    Can you try something similar and report back?

    PS: Note that this is a negotiation. You propose timers, but the network decides. However, I've played a bit with it now and in all my tests, I did always get the values I proposed (Vodafone NL), even nonsensical ones, your mileage may vary.

    Also not that I also skip the lte.disconnect() - it is quite slow and seemingly not needed, deinit(detach=False,reset=False) works just fine.

    from network import LTE
    import time
    import socket
    import machine
    import pycom
    
    def at(cmd):
        response = lte.send_at_cmd(cmd).split('\r\n')
        for line in response:
            if ( len(line) == 0 ):
                continue
            else:
                print(line)
    
    def atv(cmd):
        response = lte.send_at_cmd(cmd).split('\r\n')
        for line in response:
            if ( len(line) == 0 ):
                continue
            elif line == "OK":
                continue
            elif line == "ERROR":
                continue
            else:
                print(line)
                return line
    
    def http_get(url):
        print("get(", url, ")")
        _, _, host, path = url.split('/', 3)
        print("host", host)
        print("path", path)
        addr = socket.getaddrinfo(host, 80)[0][-1]
        print("addr", addr)
        s = socket.socket()
        print("connect")
        s.connect(addr)
        print("send")
        s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8'))
        # s.settimeout(5)
        # while True:
        #     data = s.recv(100)
        #     if data:
        #         print(str(data, 'utf8'), end='')
        #     else:
        #         break
        print("close")
        s.close()
        print("done")
    
    def send():
        url = 'http://detectportal.firefox.com/'
        http_get(url)
    
    def encode_psm_tau(value, unit):
        u = psm_tau_units[unit]
        if value > 31:
            raise Exception("31")
        return '"{:03b}{:05b}"'.format(u, value)
    
    def encode_psm_act(value, unit):
        u = psm_act_units[unit]
        if value > 31:
            raise Exception("31")
        return '"{:03b}{:05b}"'.format(u, value)
    
    def decode_psm_tau(tau):
        if len(tau) != 8:
            raise Exception("8")
        us = tau[0:3]
        vs = tau[3:8]
        ui = int(us,2)
        vi = int(vs,2)
        u = psm_tau_units_rev[ui]
        return (vi, u)
    
    def decode_psm_act(act):
        if len(act) != 8:
            raise Exception("8")
        us = act[0:3]
        vs = act[3:8]
        ui = int(us,2)
        vi = int(vs,2)
        u = psm_act_units_rev[ui]
        return (vi, u)
    
    
    def psm_set(tau, act):
        (tauv, tauu) = decode_psm_tau(tau.strip('"'))
        (actv, actu) = decode_psm_act(act.strip('"'))
        print("psm_set: ( 1 ,", tau, ",", act, ") .. tau=", tauv, tauu, "act=", actv, actu)
        cmd='AT+CPSMS=1,,,' + tau + "," + act
        at(cmd)
    
    def psm_get():
        retv = atv('AT+CPSMS?').split(" ")[1].split(",")
        en = retv[0]
        tau = retv[3]
        act = retv[4]
        (tauv, tauu) = decode_psm_tau(tau.strip('"'))
        (actv, actu) = decode_psm_act(act.strip('"'))
        print("psm_get: (", en, ",", tau, ",", act, ") .. tau=", tauv, tauu, "act=", actv, actu)
        return (tau, act)
    
    
    def doit():
        if do_psm:
            (tau_actual, act_actual) =  psm_get()
            tau = encode_psm_tau(psm_tau[0], psm_tau[1])
            act = encode_psm_act(psm_act[0], psm_act[1])
            # print("PSM tau=", psm_tau, tau)
            # print("PSM act=", psm_act, act)
            psm_set(tau, act)
    
    
        if lte.isattached():
            print("already attached")
        else:
            print("attach")
            start = time.time()
            lte.attach(band=20, apn="spe.inetd.vodafone.nbiot")
            print("attached after", time.time() - start, "seconds")
    
            if do_psm:
                (tau_actual, act_actual) =  psm_get()
                if tau_actual != tau or act_actual != act:
                    print("Network changed PSM from(", tau, ",", act, ") to(", tau_actual, ",", act_actual, ")")
            while not lte.isattached():
                time.sleep(0.5)
    
        if do_psm:
            (tau_actual, act_actual) =  psm_get()
            if tau_actual != tau or act_actual != act:
                print("Network changed PSM from(", tau, ",", act, ") to(", tau_actual, ",", act_actual, ")")
    
        print("connect")
        lte.connect()
        while not lte.isconnected():
            time.sleep(0.5)
        send()
    
    psm_tau_units = {
     "2s": 0b011,
    "30s": 0b100,
     "1m": 0b101,
    "10m": 0b000,
     "1h": 0b001,
    "10h": 0b010,
    "disabled": 0b111,
    }
    # reverse lookup
    psm_tau_units_rev = {psm_tau_units[x] : x for x in psm_tau_units}
    
    
    psm_act_units = {
     "2s": 0b000,
     "1m": 0b001,
     "6m": 0b010,
    "disabled": 0b111,
    }
    psm_act_units_rev = {psm_act_units[x] : x for x in psm_act_units}
    
    ############################################
    do_psm = True
    # psm_tau            = (2, "1h")
    # psm_act            = (2, "1m")
    # deepsleep_duration = 3600000 # 1h
    psm_tau            = ( 1, "1h")
    psm_act            = ( 3, "2s")
    deepsleep_duration = 60000 # 1m
    
    print("init")
    lte = LTE()
    doit()
    if do_psm:
        lte.deinit(detach=False, reset=False)
    else:
        lte.deinit()
    
    print("deepsleep")
    machine.deepsleep(deepsleep_duration)


  • A correction to what I wrote previously: I found that lte.deinit(detach=False, reset=False) followed by a deepsleep does not do what it is supposed to do: power consumption during deepsleep still varies between 27-90mA instead of the typical deepsleep power consumption of 22µA. This is not is line with the statement "lte.deinit(dettach=False) allows the modem to go into power saving mode (PSM, eDRX) without dettaching from the network." as posted here: New LTE Firmware release v1.18.1.r3 & CAT-M1 firmware. So the only thing that works for me is ending with lte.deinit(). If that is done, no reset is needed at the start of the next cycle, and re-connection is established reliably. But it takes time (with the lte.attach(... step taking typically 10s, and the total cycle (lte.init, lte.attach, lte.connect, lte.disconnect, lte.deinit, deepsleep) taking about 20-24s.

    Am I correct that this means that it is not yet possible to switch off the modem completely during deepsleep without detaching from the network (PSM)? Or who managed to do this? How?


Log in to reply
 

Pycom on Twitter