iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🌊

How MicroCat.1 (MechaTracks) Connects to SORACOM

に公開

Introduction

When communicating from MicroPython using MicroCat.1, the code typically looks like this:

import SIM7672

m = SIM7672.modem()
m.active(True)
m.connect('soracom.io', 'sora', 'sora', 'IP', 3)

While these few lines make the LTE connection available, it is not immediately clear what connect() is doing.

In this blog post, I will dive deep into what kind of sequence occurs within these few lines:

  • Where is the LTE attach performed?
  • When are the APN settings and PDP context established?
  • At which layer does PPP operate?
  • What is the difference between active(True) and connect()?

Based on the implementation of SIM7672.py and actual logs, I will break down and explain the communication processing in MicroCat.1 for each layer.

Overview: MicroPython + PPP Communication Stack

The communication configuration of MicroCat.1 uses the so-called PPP (Point-to-Point Protocol) method.

alt text

Key points of this configuration are as follows:

MicroPython (RP2350) Side Stack

  • HTTP / requests: HTTP client at the application layer
  • TCP/UDP / socket: Transport layer (Python socket API)
  • IP / lwIP: Network layer (lightweight TCP/IP stack)
  • PPP / Network.ppp: Data link layer (PPP protocol)
  • UART / Machine.uart: Physical layer (serial communication)

Protocol Bridge

  • PPP (ATD*99#): PPP session establishment on the modem side
  • UART (serial): Physical serial connection

SIM7672 Modem Side Stack

  • IP: Network layer (PDP Context / APN: soracom.io)
  • PDCP: Packet Data Convergence Protocol (packet compression and encryption)
  • RLC: Radio Link Control (retransmission control)
  • MAC: Medium Access Control (radio resource allocation)
  • PHY: Physical layer (LTE Radio)

Data Flow

  • Left → Center: Sent to the modem via UART as PPP frames
  • Center → Right: IP packets are sent to the LTE network through the PDP Context
  • IP Layer: Logically connected between the microcontroller side and the modem side (dotted line)

In other words, there is a clear division of labor: the microcontroller handles processing above the IP layer, while the modem handles the wireless portion below the IP layer.

This is distinct from the modem-embedded TCP/IP (AT socket method) that uses AT+NETOPEN or AT+CIPOPEN.

Overall Sequence Diagram

Based on the source code, the communication sequence of MicroCat.1 can be summarized as follows. Details for each operation and actual operation logs will be described later.

Role of the SIM7672 Library

SIM7672.py is not just a "communication library," but rather a modem control library to prepare the state where PPP can be started.

Specifically, it handles the following processes:

  • Power and reset control
  • Status check using AT commands
  • Waiting for PS attach completion
  • Consistency check of PDP context settings
  • Triggering PPP dial-up

It does not perform IP communication itself (TCP/UDP/HTTP).

Actual Log Acquisition Procedure and Excerpts

While the logic described above can be inferred from the source code, seeing is believing. Let's actually capture the AT command logs during the execution of connect().

REPL Execution Example (debug=True)

By adding debug=True, the AT command exchange inside connect() is output directly to the REPL. The actual connection sequence is as follows.
Lines starting with # are comments added for explanation.

Full log of execution with `debug=True`
MicroPython v1.27.0-1.gfc07f56013 on 2025-12-11; MechaTracks MicroCat.1 with RP2350
Type "help()" for more information or .help for custom vREPL commands.

>>> import SIM7672
>>> m = SIM7672.modem(debug=True)
>>> m.active(True)
>>> m.connect('soracom.io', 'sora', 'sora', 'IP', 3)
# AT log excerpts start here
# Sending "AT" command
AT

b'AT\r\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" command (Check PS attach status)
AT+CEREG?

b'AT+CEREG?\r\r\n'

# Received "+CEREG: 0,5" (PS attach complete)
# Likely automatically attached upon power-on due to previous connection settings
b'+CEREG: 0,5\r'
b'\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+IFC?" command (Check flow control settings)
AT+IFC?

b'AT+IFC?\r\r\n'

# Received "+IFC: 0,0" (No flow control)
b'+IFC: 0,0\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+IFC=2,2" command (RTS/CTS settings)
AT+IFC=2,2

b'AT+IFC=2,2\r\r\n'

# Received "OK"
b'OK\r\n'

# Sending "AT+IPR?" command (Check baud rate)
AT+IPR?

b'AT+IPR'
b'?\r\r\n'

# Received "+IPR: 115200" (Baud rate 115200)
b'+IPR: 115200\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CPIN?" command (Check SIM status)
AT+CPIN?

b'AT+CPIN?\r\r\n'

# Received "+CPIN: READY" (SIM ready)
b'+CPIN: READ'
b'Y\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CGDCONT?" command (Check PDP context)
AT+CGDCONT?

b'AT+CGDCONT?\r\r\n'

# Received "+CGDCONT: 1,"IP","soracom.io",164.98.251" (Verify PDP settings)
b'+CGDCONT: 1'
b',"IP","sorac'
b'om.io","10.'
b'164.98.251"\r'
b'\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CGAUTH?" command (Check authentication settings)
AT+CGAUTH?

b'AT+CGAUTH?\r\r\n'

# Received "+CGAUTH: 1,2,"sora","sora"" (Verify authentication settings)
b'+CGAUTH: 1,2'
b',"sora","sor'
b'a"\r\n'
b'\r\n'
b'OK\r\n'

# Sending "CFUN=4" command (Suspend RF)
AT+CFUN=4

b'AT+CFUN'
b'=4\r'
b'\r\n'
b'OK\r\n'

# Sending "AT+CGDCONT=1,"IP","soracom.io"" (Update PDP settings)
AT+CFUN=1

b'AT+CFUN=1\r\r\n'
b'OK\r\n'

# Sending "AT+CPIN?" command (Check SIM status)
AT+CPIN?

b'AT+CPIN?\r\r\n'
b'+CPIN: READY\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (First time after reset)
AT+CEREG?

b'AT+CEREG?\r\r\n'

# Received "+CEREG: 0,2" (Still searching)
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (2nd time)
AT+CEREG?

b'AT+CER'
b'EG?\r\r\n'

# Received "+CEREG: 0,2" (Still searching)
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (3rd time)
AT+CEREG?

b'AT+CER'
b'EG?\r\r\n'

# Received "+CEREG: 0,2" (Still searching)
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (4th time)
AT+CEREG?

b'AT+CERE'
b'G?\r\r\n'

# Received "+CEREG: 0,2" (Still searching)
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (5th time)
AT+CEREG?

b'AT+CERE'
b'G?\r\r\n'

# Received "+CEREG: 0,2" (Still searching)
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'

# Sending "AT+CEREG?" (Check PS attach status) (6th time)
AT+CEREG?

b'AT+CER'
b'EG?\r\r\n'

# Received "+CEREG: 0,5" (Roaming registration complete)
b'+CEREG: 0,5\r\n'
b'\r\n'
b'OK\r\n'

# Sending "ATD*99***1#" (PPP dial-up)
ATD*99***1#

b'ATD*99***1#\r'
b'\r'
b'\n'

# Received "CONNECT" (PPP establishment complete)
b'CONNECT\r\n'
>>> 

Brief Explanation

This reveals a series of operations: switching to RTS/CTS (AT+IFC=2,2), checking the baud rate (AT+IPR?), verifying APN and authentication settings (AT+CGDCONT? / AT+CGAUTH?), temporarily suspending RF if necessary (CFUN=4/1), waiting for PS attach completion (CEREG=0,1/0,5), and finally initiating PPP with ATD*99***1#.

From this log, it is clear that after waiting for the CEREG registration to complete, PPP negotiation begins via ATD*99***1#, and IP/DNS are assigned via IPCP.

Processing within active(True)

active(True) is not an API for initiating communication.

Role

Since we cannot output logs for this, let's look at the source code.

    def active(self, activate=None, reset=True):
        if activate is True:
            if reset and self.__status_pin.value() == 1:
                self.reset()
            else:
                self.poweron()
            return
        if activate is False:
            self.poweroff()
            return
        return self.__status_pin.value() == 1
  • Controls modem power ON / OFF
  • Resets the modem if necessary

The poweron() / poweroff() functions called here perform only GPIO operations for power control; they do not include AT commands or PPP control. In other words, they simply turn the modem's power on or off.

Meaning of reset=True

  • By default, active(True) triggers a reset if the power is already on.
    • If the status pin is 1, it executes reset(), restarting everything including RF, PS attach, and PPP.
    • While useful for a complete clean start, this is inefficient if you want to maintain the PS attach state.
  • If the power is off, it simply executes poweron().
  • If you only want to confirm the power is on while preserving the existing state, use active(True, reset=False).

Processing within connect()

connect() is the API for establishing a PPP connection. You provide the APN, username, password, and PDP type when calling it, and specify the authentication method with security=3 (allowing CHAP|PAP).

Actual Processing Flow (from the debug=True logs)

  • It confirms AT communication, switches to RTS/CTS with AT+IFC=2,2, and checks the UART baud rate with AT+IPR?.
  • It checks the SIM status with AT+CPIN? and verifies the registration status with AT+CEREG? (0,1 for home registration, 0,5 for roaming registration).
  • It matches PDP and authentication settings using AT+CGDCONT? / AT+CGAUTH?. If updates are needed, it suspends RF with CFUN=4, configures the settings, and resumes with CFUN=1.
  • It polls CEREG until it becomes 0,1 or 0,5, waiting for the PS attach to complete.
  • It requests a PPP session via ATD*99***1# and negotiates through LCP → PAP/CHAP → IPCP to obtain an IP address and DNS.

Only at this point are the IP address and DNS obtained.

How to Read CEREG (Excerpts)

  • 0,1 Registered (Home) / 0,5 Registered (Roaming) → Continue PPP
  • 0,2 Searching → Wait for a certain period
  • 0,3 Denied / 0,4 Temporarily out of service / 0,90 UTRAN/E-UTRAN denied / 0,99 Unknown → Troubleshooting required (SIM, signal, or APN)

Clarifying the Relationship between PS Attach, PDP, and PPP

Since several types of sessions were mentioned, let's clarify these three often-confused terms.
Here is the protocol stack and library relationship diagram again, mapped to the names used for each session.

alt text

Item Meaning
PS attach (Packet-Switched attach) Registration to the LTE network
PDP context (Packet Data Protocol context) Data session definition including APN and authentication
PPP (Point-to-Point Protocol) Link protocol between the microcontroller and modem (carrying IP as payload)
  • PDP cannot be used until the PS attach is complete.
  • PPP cannot be established without a PDP (the ATD*99***1# command refers to a PDP with a specific CID).
  • The IP address is obtained through PPP's IPCP (lwIP performs IPCP negotiation after the CONNECT message in the log above, which can be verified with ifconfig()).

Differences between disconnect() / active(False) / active(reset=True) / poweroff()

These four methods disconnect at different layers. Looking at the implementation, active(False) simply calls poweroff() internally and has no path to turn off only the RF. active(reset=True) performs a hardware reset and restarts the modem if the power is on.

API What it disconnects Impact/Recovery
disconnect() PPP only Terminates PPP while maintaining PS attach and PDP. Subsequent connect() is faster.
active(False) Modem power (= poweroff()) Power cut via GPIO. All states like PPP / PS attach / PDP are lost. Recovery requires active(True)connect().
active(reset=True) Restarts modem (Reset pin drive) Executes reset() to restart if power is on (PS/PDP/PPP are dropped, but recovery is faster than power off). Executes poweron() if power is off. Use active(True, reset=False) if you want to maintain state.
poweroff() Modem power Resets UART to initial state then powers off, waiting until the status pin becomes 0. High recovery cost (PS attach must be redone).

How can I get information using AT commands not included in the sequence?

To be honest, this blog post was written because I wanted to investigate this.
As described so far, performing connect() does everything from turning on the modem to performing PS attach, establishing a PDP context, and further establishing a PPP session all at once.

Once PPP is established, the UART enters data mode, so sending AT commands in this state treats them as PPP frames.

For example, if you want to retrieve modem information, signal status, or SIM-related information, you must execute AT commands when the LTE modem is in command mode.
There are two such timings:

  1. Immediately after executing active(True) to turn on the modem power (before PS attach).
  2. Immediately after executing disconnect() to terminate the PPP session (maintaining PS attach and PDP).

Based on my tests so far, the first timing worked well, but the second timing did not.
For now, at the first timing, you can send AT commands as follows.

Save the following to a local file, click the RUN button in the IDE, and execute it on MicroCat.1.

import SIM7672, time

def main():
    print("[INIT] modem/debug")
    m = SIM7672.modem(debug=True)
    m.active(True)

    send = getattr(m, '__send')
    recv = getattr(m, '__receive')
    uart = getattr(m, '__uart')

    time.sleep_ms(15000)

    def send_wait(label, cmd, timeout=5000):
        print(f"[CMD] {label} -> {cmd}")
        if not cmd.endswith('\\r\\n'):
            cmd += '\\r\\n'
        uart.read(uart.any())
        send(cmd)
        buf = []
        deadline = time.ticks_add(time.ticks_ms(), timeout)
        while time.ticks_diff(deadline, time.ticks_ms()) > 0:
            line = recv(timeout=50)
            if line is None:
                continue
            buf.append(line)
            if line.endswith('OK') or 'ERROR' in line:
                break
        print(f"[RESP] {label}:", buf)
        time.sleep_ms(200)
        return buf

    # Run all AT commands you want to execute before connect here
    for label, cmd in [
        ("ATI",     "ATI"),
        ("CSQ",     "AT+CSQ"),
        ("CEREG",   "AT+CEREG?"),
        ("CIMI",    "AT+CIMI"),
        ("CICCID",  "AT+CICCID"),
        ("COPS?",   "AT+COPS?"),
        ("CPSI?",   "AT+CPSI?"),
        ("CBC",     "AT+CBC"),
        ("CPMUTEMP","AT+CPMUTEMP"),
    ]:
        send_wait(label, cmd)

    print("[STEP] connect PPP")
    m.connect('soracom.io', 'sora', 'sora', 'IP', 3)

    print("[DONE]")

if __name__ == "__main__":
    main()

The "RUN" button in the IDE is here
alt text

Example execution results (log)

Full execution results
MicroPython v1.27.0-1.gfc07f56013 on 2025-12-11; MechaTracks MicroCat.1 with RP2350
Type "help()" for more information or .help for custom vREPL commands.

>>> 
[INIT] modem/debug
[CMD] ATI -> ATI
b'ATI\r\r\n'
b'Manufacturer: '
b'SIMCOM INCOR'
b'PORATED\r\n'
b'Model: SIM7672'
b'JP-MNGV\r\n'
b'Revision: V1.9'
b'.05\r\n'
b'IMEI: 86985xxxxxxxxxx'
b'430\r\n'
b'\r\n'
b'OK\r\n'
[RESP] ATI: ['ATI', 'Manufacturer: SIMCOM INCORPORATED', 'Model: SIM7672JP-MNGV', 'Revision: V1.9.05', 'IMEI: 86985xxxxxxxxxx', '', 'OK']
[CMD] CSQ -> AT+CSQ
b'AT+CSQ\r\r\n'
b'+CSQ: 99,99\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CSQ: ['AT+CSQ', '+CSQ: 99,99', '', 'OK']
[CMD] CEREG -> AT+CEREG?
b'AT+CEREG?\r\r\n'
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CEREG: ['AT+CEREG?', '+CEREG: 0,2', '', 'OK']
[CMD] CIMI -> AT+CIMI
b'AT+CIMI\r\r\n'
b'90131xxxxxxxxxx\r'
b'\n'
b'\r\n'
b'OK\r\n'
[RESP] CIMI: ['AT+CIMI', '90131xxxxxxxxxx', '', 'OK']
[CMD] CICCID -> AT+CICCID
b'AT+CI'
b'CCID\r\r\n'
b'+ICCID: 8942310'
b'xxxxxxxxxxxxx'
b'\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CICCID: ['AT+CICCID', '+ICCID: 89423xxxxxxxxxx', '', 'OK']
[CMD] COPS? -> AT+COPS?
b'AT+COPS?\r\r\n'
b'+COPS: 0,2,"44010",7'
b'\r\n'
b'\r\n'
b'OK\r\n'
[RESP] COPS?: ['AT+COPS?', '+COPS: 0,2,"44010",7', '', 'OK']
[CMD] CPSI? -> AT+CPSI?
b'AT+CPSI?\r'
b'\r\n'
b'+CPSI: LTE,Online,'
b'440-10,0x169'
b'8,39683652,'
b'181,EUTRAN-B'
b'AND28,9510,3'
b',3,20,61,41'
b',3\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CPSI?: ['AT+CPSI?', '+CPSI: LTE,Online,440-10,0x1698,39683652,181,EUTRAN-BAND28,9510,3,3,20,61,41,3', '', 'OK']
[CMD] CBC -> AT+CBC
b'AT+CBC\r'
b'\r\n'
b'+CBC: 3.978V\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CBC: ['AT+CBC', '+CBC: 3.978V', '', 'OK']
[CMD] CPMUTEMP -> AT+CPMUTEMP
b'AT+CPMUTEMP\r'
b'\r\n'
b'+CPMUTEMP: 23\r\n'
b'\r\n'
b'OK\r\n'
[RESP] CPMUTEMP: ['AT+CPMUTEMP', '+CPMUTEMP: 23', '', 'OK']
[STEP] connect PPP
AT

b'AT\r\r\n'
b'OK\r\n'
AT+CEREG?

b'AT+CEREG?\r'
b'\r\n'
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'
AT+IFC?

b'AT+IFC?\r\r\n'
b'+IFC: 0,0\r\n'
b'\r\n'
b'OK\r\n'
AT+IFC=2,2

b'AT+IFC=2,'
b'2\r\r\n'
b'OK\r\n'
AT+IPR?

b'AT+IPR?\r'
b'\r\n'
b'+IPR: 115200\r\n'
b'\r\n'
b'OK\r\n'
AT+CPIN?

b'AT+CPIN?\r\r\n'
b'+CPIN: READY'
b'\r\n'
b'\r\n'
b'OK\r\n'
AT+CGDCONT?

b'AT+CGDCONT?\r\r\n'
b'+CGDCONT: 1,"I'
b'P","soracom'
b'.io"\r\n'
b'\r\n'
b'OK\r\n'
AT+CGAUTH?

b'AT+CGAUTH?\r\r\n'
b'+CGAUTH: 1,2,'
b'"sora","sor'
b'a"\r\n'
b'\r\n'
b'OK\r\n'
AT+CFUN=4

b'AT+CFUN='
b'4\r'
AT+CFUN=1

b'AT+CFUN'
b'=1\r'
AT+CPIN?

b'AT+CPIN?'
b'\r'
b'\r\n'
b'OK\r\n'
b'AT+CFUN=1\r\r\n'
b'OK\r\n'
AT+CPIN?

b'AT+CPIN?'
b'\r\r\n'
b'+CPIN: READY\r\n'
b'\r\n'
b'OK\r\n'
AT+CEREG?

b'AT+CEREG?\r\r'
b'\n'
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'
AT+CEREG?

(Repeating)

b'AT+CEREG?\r'
b'\r\n'
b'+CEREG: 0,2\r\n'
b'\r\n'
b'OK\r\n'
AT+CEREG?

b'AT+CEREG?\r'
b'\r\n'
b'+CEREG: 0,5\r\n'
b'\r\n'
b'OK\r\n'
ATD*99***1#

b'ATD*99***1#\r'
b'\r\n'
b'CONNECT\r\n'
[DONE]

>>> 

That's all for now.

GitHubで編集を提案

Discussion