Skip to content

Latest commit

 

History

History
969 lines (722 loc) · 28.1 KB

File metadata and controls

969 lines (722 loc) · 28.1 KB

lte Module - C Implementation

The lte module provides MicroPython bindings for LTE modem functionality using a robust C implementation built on top of the ESP modem library. This is the new implementation that replaces the Python-based LTE.py module.

Usage

import lte

# Initialize modem
lte.init(carrier='standard')

# Attach to network
lte.attach(apn='iot.1nce.net')

# Start data session
lte.connect()

# Check connection
if lte.isconnected():
    print("Connected!")
    print(lte.ifconfig())

# Clean up
lte.disconnect()
lte.deinit()

Methods

lte.init([carrier='standard'])

Initialize the LTE modem subsystem. This method is idempotent - it's safe to call multiple times.

The modem intelligently handles three possible power states:

  1. Powered off - Powers on and initializes from scratch
  2. Normal AT mode (crash recovery) - Resumes from existing state
  3. CMUX mode (soft reset) - Cleans up and reinitializes

Parameters:

  • carrier (str, optional): Carrier conformance mode for band selection. Options:
    • 'standard' (default)
    • 'verizon'
    • 'att'
    • 'docomo'
    • 'kddi'
    • 'telstra'
    • 'tmo'
    • 'verizon-no-roaming'
    • '3gpp-conformance'

Carrier Conformance Mode Behavior:

The carrier parameter controls the modem's carrier conformance mode (AT+SQNCTM), which affects band selection and carrier-specific optimizations:

  • lte.init() or lte.init(carrier=None): Uses whatever conformance mode is currently configured on the modem. Does not change or check the mode.

  • lte.init(carrier='standard') or any explicit carrier: Checks the modem's current conformance mode and changes it if different. If the mode needs to change, the modem will automatically reset during initialization.

  • Changing conformance mode after initialization: If you call lte.init(carrier='verizon') after already initializing with a different carrier, the modem will automatically change the conformance mode and reset. No need to call lte.deinit() first - the change is handled seamlessly.

Example:

# Use default - keeps whatever mode is configured
lte.init()

# Explicitly set to standard mode
lte.init(carrier='standard')

# Switch to Verizon mode - seamlessly changes and resets modem
lte.init(carrier='verizon')

# Switch back to standard - automatic mode change
lte.init(carrier='standard')

# Attach after mode change
lte.attach(apn='iot.1nce.net')

Note: Changing the conformance mode triggers a modem reset. The initialization process handles this automatically (including the baudrate switch from 115200 to 921600), but it adds a few seconds to the init time.

lte.attach([apn=None, type='IP', cid=None, band=None, bands=None])

Enable radio and attach to the cellular network. This is a non-blocking operation - use lte.isattached() to poll for registration status.

Note: NB-IoT initial attachment may take several minutes.

Parameters:

  • apn (str, optional): Access Point Name (e.g., 'iot.1nce.net'). Defaults to empty string (carrier default)
  • type (str, optional): PDP context type. Options: 'IP' (default), 'IPV6', 'IPV4V6'
  • cid (int, optional): Context Identifier. Default: 1 (Verizon uses 3)
  • band (int, optional): Single frequency band to use. 0 for auto-select. Valid bands: 1-28, 66, 71
  • bands (list, optional): List of frequency bands (e.g., [2, 4, 12]). Cannot use with band

Example:

# Simple attach with APN
lte.attach(apn='iot.1nce.net')

# Attach with specific band
lte.attach(apn='iot.1nce.net', band=12)

# Attach with multiple bands
lte.attach(apn='iot.1nce.net', bands=[2, 4, 12])

# Wait for attachment
import time
for i in range(180):  # 3 minute timeout
    if lte.isattached():
        print("Attached!")
        break
    time.sleep(1)

lte.connect([cid=None])

Start a data session using CMUX multiplexing and PPP protocol. This is a non-blocking operation - use lte.isconnected() to poll for PPP status.

Prerequisites: Network must be attached (lte.isattached() returns True)

Parameters:

  • cid (int, optional): Context Identifier (typically uses CID from attach())

Example:

# Wait for connection
lte.connect()
import time
for i in range(60):
    if lte.isconnected():
        print("Connected!")
        print(lte.ifconfig())
        break
    time.sleep(1)

lte.disconnect()

Disconnect from the data session but keep network attachment.

Example:

lte.disconnect()

lte.detach()

Gracefully detach from the cellular network and disable radio functionality.

Example:

lte.detach()

lte.deinit([detach=True, power_off=True])

Deinitialize the modem with optional control over network detachment and power off.

This method allows flexible power management strategies:

  • Full shutdown (default): Detaches from network and powers off modem
  • Low power mode: Keeps modem powered and attached, using PSM/eDRX for power saving
  • Quick restart: Keeps modem powered for faster re-initialization

Parameters:

  • detach (bool, optional): If True, detach from cellular network. Default: True
  • power_off (bool, optional): If True, power off the modem completely. Default: True

Power Saving Strategy:

When using detach=False, power_off=False, the modem stays attached to the network in a low-power state:

  • Flow control pins are de-asserted, allowing the modem to enter power saving mode
  • Use PSM (Power Saving Mode) or eDRX (extended DRX) for ultra-low power consumption
  • Modem can wake the ESP32 via RING signal for incoming messages or mobile-terminated events
  • ESP32 can enter deep sleep while modem maintains network connection

Example:

# Full shutdown (default)
lte.deinit()

# Keep modem powered for quick restart
lte.deinit(power_off=False)

# Low power mode with network attachment (PSM/eDRX)
# Configure PSM/eDRX first with AT commands
lte.send_at_cmd('AT+CPSMS=1,"","","00000001","00000001"')  # Enable PSM
lte.deinit(detach=False, power_off=False)
# ESP32 can now deep sleep, modem wakes it via RING on incoming data

# Detach but keep powered (for reconfiguration)
lte.deinit(detach=True, power_off=False)

Use Cases:

  1. Normal shutdown: lte.deinit() - Clean disconnect and power off
  2. Quick restart: lte.deinit(power_off=False) - Faster next lte.init()
  3. Ultra-low power with connectivity: lte.deinit(detach=False, power_off=False) + PSM/eDRX
  4. Reconfigure network: lte.deinit(detach=True, power_off=False) - Change APN/bands

lte.reset()

Perform a hardware reset on the modem using AT^RESET. Waits for the modem to shutdown and reboot, then re-detects baudrate.

This function can take up to 10 seconds to complete.

Example:

lte.reset()

lte.isattached()

Check if the modem is attached to the cellular network.

Returns: True if registered (home or roaming), False otherwise

Example:

if lte.isattached():
    print("Attached to network")

lte.is_attached()

Alias for lte.isattached(). Provided for compatibility with legacy scripts.

Returns: True if attached, False otherwise

lte.isconnected()

Check if PPP data connection is active and IP address has been obtained.

Returns: True if connected, False otherwise

Example:

if lte.isconnected():
    ip_info = lte.ifconfig()
    print(f"IP: {ip_info[0]}")

lte.is_connected()

Alias for lte.isconnected(). Provided for compatibility with legacy scripts.

Returns: True if connected, False otherwise

lte.send_at_cmd(cmd='AT', [timeout=-1, wait_ok_error=False, check_error=False, buffer_size=4096])

Send a raw AT command to the modem and return the response.

Important: CMUX allows simultaneous AT commands and data sessions.

Parameters:

  • cmd (str): AT command to send (without \r\n). Default: 'AT'
  • timeout (int, optional): Timeout in milliseconds. -1 for default (5000ms)
  • wait_ok_error (bool, optional): Wait for OK/ERROR response. Default: False
  • check_error (bool, optional): Raise exception on ERROR. Default: False
  • buffer_size (int, optional): Response buffer size (1024-32768). Default: 4096

Returns: Response string with OK/ERROR lines removed

Example:

# Query network registration
response = lte.send_at_cmd('AT+CEREG?')
print(response)

# Check signal strength
response = lte.send_at_cmd('AT+CSQ')
print(response)

# Long-running command
response = lte.send_at_cmd('AT+SQNINS', wait_ok_error=True, timeout=30000)

lte.mode([new_mode=None])

Get or set the modem operating mode (CAT-M1 or NB-IoT).

Parameters:

  • new_mode (int, optional): New mode to set. Use lte.CATM1 (0) or lte.NBIOT (1)

Returns:

  • If no parameter: Current mode (0 for CAT-M1, 1 for NB-IoT)
  • If parameter provided: None (mode is set)

Note: Setting a new mode causes the modem to reset.

Example:

# Get current mode
current = lte.mode()
print(f"Mode: {'CAT-M1' if current == 0 else 'NB-IoT'}")

# Switch to NB-IoT
lte.mode(lte.NBIOT)

lte.imei()

Get the modem's IMEI (International Mobile Equipment Identity) number.

Returns: String containing the 15-digit IMEI

Example:

imei = lte.imei()
print(f"IMEI: {imei}")

lte.iccid()

Get the SIM card's ICCID (Integrated Circuit Card Identification) number.

This function checks if the SIM is present and ready before reading the ICCID.

Returns: String containing the 19-20 digit ICCID

Raises: OSError if SIM card is not present or not ready

Example:

try:
    iccid = lte.iccid()
    print(f"ICCID: {iccid}")
except OSError:
    print("SIM card not present or not ready")

lte.get_signal_strength()

Get signal strength information from the modem.

Returns: Tuple of (rssi, rssi_dbm, ber) where:

  • rssi: Raw RSSI value (0-31, 99=unknown)
  • rssi_dbm: RSSI in dBm (-113 to -51, -999=unknown)
  • ber: Bit Error Rate (0-7, 99=unknown)

Example:

rssi, rssi_dbm, ber = lte.get_signal_strength()
print(f"Signal: {rssi_dbm} dBm (BER: {ber})")

lte.get_status()

Get comprehensive modem status information.

Returns: Dictionary with the following keys:

  • powered (bool): Modem power state
  • sim_ready (bool): SIM card present and ready
  • network_attached (bool): Attached to network
  • ppp_connected (bool): PPP session active
  • cmux_active (bool): CMUX multiplexing enabled
  • baudrate (int): Current UART baudrate
  • rssi (int): Signal strength (raw RSSI value)
  • ber (int): Bit error rate

Example:

status = lte.get_status()
print(f"Powered: {status['powered']}")
print(f"Network: {status['network_attached']}")
print(f"PPP: {status['ppp_connected']}")
print(f"Signal: {status['rssi']}")

lte.ifconfig()

Get network interface configuration (IP address information).

Returns: Tuple of (ip, netmask, gateway, dns) or None if not connected

Example:

if lte.isconnected():
    ip, netmask, gateway, dns = lte.ifconfig()
    print(f"IP: {ip}")
    print(f"Gateway: {gateway}")
    print(f"DNS: {dns}")

lte.power_on([wait_ok=True])

Power on the LTE modem hardware via IO expander.

Parameters:

  • wait_ok (bool, optional): Wait for modem to respond with OK. Default: True

Example:

lte.power_on()
# or
lte.power_on(wait_ok=False)  # Don't wait for response

lte.power_off([force=False])

Power off the LTE modem hardware via IO expander.

Parameters:

  • force (bool, optional): Force immediate power off without graceful shutdown. Default: False

Example:

lte.power_off()  # Graceful shutdown
# or
lte.power_off(force=True)  # Immediate power off

lte.is_powered()

Check if the modem is currently powered on.

Returns: True if powered, False otherwise

Example:

if lte.is_powered():
    print("Modem is powered on")

lte.check_sim_present()

Check if a SIM card is present and ready.

This function retries up to 5 times with delays, and ensures the radio is enabled to read the SIM.

Returns: True if SIM is ready, False otherwise

Example:

if lte.check_sim_present():
    iccid = lte.iccid()
    print(f"SIM ready: {iccid}")
else:
    print("No SIM card detected")

lte.set_event_handler(handler, event_mask=EVENT_ALL, lock=False)

Register a unified event handler for LTE modem events, providing structured event data with automatic parsing.

Parameters:

  • handler (function): Event handler function that receives a dictionary, or None to unregister
  • event_mask (int, optional): Bitmask of events to subscribe to. Default: lte.EVENT_ALL
  • lock (bool, optional): If True, lock the event handler so it cannot be overridden until lte.deinit() is called. Attempting to set or unregister the handler while locked raises OSError. Default: False

Event Handler Signature:

def handler(event: dict) -> None:
    """
    Args:
        event: Dictionary containing:
            - 'type': Event type constant (lte.EVENT_xxx)
            - Additional keys depending on event type
    """
    pass

Event Types:

  1. lte.EVENT_REGISTRATION_STATUS (0x0001) - Network registration changes

    • stat (int): Registration status (0-10, 80)
      • 0: Not registered (not searching)
      • 1: Registered (home network)
      • 2: Not registered (searching)
      • 3: Registration denied
      • 5: Registered (roaming)
    • tac (str, optional): Tracking Area Code
    • ci (str, optional): Cell ID
    • act (int, optional): Access Technology (-1=unknown, 7=LTE-M, 9=NB-IoT)
    • cause_type (int, optional): Reject cause type
    • reject_cause (int, optional): Reject cause code
    • active_time (str, optional): PSM active time
    • periodic_tau (str, optional): PSM periodic TAU
  2. lte.EVENT_PPP_CONNECTED (0x0002) - PPP connection established

    • ip (str): Assigned IP address
    • netmask (str): Network mask
    • gateway (str): Gateway address
    • dns1 (str): Primary DNS server
    • dns2 (str, optional): Secondary DNS server
  3. lte.EVENT_PPP_DISCONNECTED (0x0004) - PPP connection lost

  4. lte.EVENT_MODEM_CRASH (0x0008) - Modem crash detected

    • break_count (int): Number of break signals received
  5. lte.EVENT_MODEM_RESET (0x0010) - Modem was reset

    • user_initiated (bool): Whether reset was user-initiated
    • reason (str, optional): Reset reason
  6. lte.EVENT_SIGNAL_QUALITY (0x0020) - Signal strength update

    • rssi (int): Raw RSSI value (0-31, 99=unknown)
    • rssi_dbm (int): RSSI in dBm (-113 to -51, -999=unknown)
    • ber (int): Bit Error Rate (0-7, 99=unknown)
  7. lte.EVENT_URC (0x0040) - Unsolicited response code (raw AT response)

    • data (str): The raw URC string
  8. lte.EVENT_ERROR (0x0080) - Error occurred

    • error_code (int): Error code
    • message (str, optional): Error message
    • operation (str, optional): Operation that failed

Event Mask Combinations:

You can combine event types using bitwise OR to subscribe to specific events:

# Subscribe to connection events only
lte.set_event_handler(handler, lte.EVENT_PPP_CONNECTED | lte.EVENT_PPP_DISCONNECTED)

# Subscribe to registration and signal quality
lte.set_event_handler(handler, lte.EVENT_REGISTRATION_STATUS | lte.EVENT_SIGNAL_QUALITY)

# Subscribe to all events (default)
lte.set_event_handler(handler, lte.EVENT_ALL)

# Lock the handler so it cannot be overridden (released on lte.deinit())
lte.set_event_handler(handler, lte.EVENT_ALL, lock=True)

Locking:

When lock=True is passed, the handler is protected from being overridden or unregistered. Any subsequent call to lte.set_event_handler() (including lte.set_event_handler(None)) will raise OSError until lte.deinit() is called, which releases the lock.

This is useful for system-level code (e.g., the CTRL client) that needs to guarantee its event handler remains active while managing the modem connection:

# System code locks the handler
lte.init()
lte.set_event_handler(system_handler, lte.EVENT_ALL, lock=True)

# User code cannot override it
try:
    lte.set_event_handler(my_handler)  # Raises OSError
except OSError as e:
    print(e)  # "event handler is locked, call lte.deinit() first"

# Lock is released on deinit
lte.deinit()
lte.init()
lte.set_event_handler(my_handler)  # Works now

Basic Example:

def lte_event_handler(event):
    event_type = event['type']
    
    if event_type == lte.EVENT_REGISTRATION_STATUS:
        stat = event['stat']
        if stat == 1:
            print("✓ Registered (home network)")
        elif stat == 5:
            print("✓ Registered (roaming)")
        elif stat == 2:
            print("⌛ Searching...")
            
    elif event_type == lte.EVENT_PPP_CONNECTED:
        print(f"✓ Connected! IP: {event['ip']}")
        
    elif event_type == lte.EVENT_PPP_DISCONNECTED:
        print("✗ Disconnected")

# Register handler for all events
lte.init()
lte.set_event_handler(lte_event_handler, lte.EVENT_ALL)
lte.attach(apn='iot.1nce.net')
lte.connect()

# Unregister when done
lte.set_event_handler(None)

Complete Example: See /home/ehlers/sg-sdk/examples/lte/lte_event_handler.py for a comprehensive example showing all event types and how to handle them.

Constants

Mode Constants

  • lte.CATM1 (0): CAT-M1 mode constant
  • lte.NBIOT (1): NB-IoT mode constant

Example:

# Set mode to CAT-M1
lte.mode(lte.CATM1)

# Set mode to NB-IoT
lte.mode(lte.NBIOT)

Event Type Constants

Event type constants for use with lte.set_event_handler():

  • lte.EVENT_REGISTRATION_STATUS (0x0001): Network registration status changes
  • lte.EVENT_PPP_CONNECTED (0x0002): PPP connection established
  • lte.EVENT_PPP_DISCONNECTED (0x0004): PPP connection lost
  • lte.EVENT_MODEM_CRASH (0x0008): Modem crash detected
  • lte.EVENT_MODEM_RESET (0x0010): Modem reset occurred
  • lte.EVENT_SIGNAL_QUALITY (0x0020): Signal strength update
  • lte.EVENT_URC (0x0040): Raw unsolicited response code
  • lte.EVENT_ERROR (0x0080): Error occurred
  • lte.EVENT_ALL (0xFFFF): Subscribe to all events

Example:

# Subscribe to specific events
lte.set_event_handler(my_handler, lte.EVENT_PPP_CONNECTED | lte.EVENT_PPP_DISCONNECTED)

# Subscribe to all events
lte.set_event_handler(my_handler, lte.EVENT_ALL)

Error Handling

The module raises MicroPython exceptions for errors:

  • OSError - General modem errors (timeout, not responding, etc.)
  • ValueError - Invalid parameters
  • MemoryError - Memory allocation failure
  • TypeError - Wrong type for callback

Example:

try:
    lte.init()
    lte.attach(apn='iot.1nce.net')
except OSError as e:
    print(f"LTE error: {e}")
except ValueError as e:
    print(f"Invalid parameter: {e}")

Implementation Notes

Differences from LTE.py

This C implementation provides several improvements over the Python LTE.py module:

  1. Better Resource Management:

    • Proper UART reservation/release
    • Memory-efficient buffers
    • WiFi/LTE coexistence handled automatically
  2. CMUX Support:

    • Simultaneous AT commands and data sessions
    • No need for pause_ppp()/resume_ppp()
    • More reliable operation
  3. Event-Driven Architecture:

    • Unified event handler with structured data
    • No need to manually poll for status changes
    • Real-time event notification with automatic parsing
  4. Robust Initialization:

    • Handles modem power states intelligently
    • Automatic crash recovery
    • Idempotent initialization
  5. Performance:

    • Native C implementation
    • Faster command execution
    • Lower memory footprint

Compatibility Notes

Most methods are compatible with LTE.py, but note:

  • pause_ppp() and resume_ppp() are not needed (CMUX handles this)
  • read_rsp() is replaced by set_event_handler()
  • Initialization is simpler (no explicit baudrate management)
  • check_power() renamed to is_powered()
  • Automatic WiFi conflict detection/resolution

Advanced Usage

Monitoring Network Registration with Event Handler

import lte
import time

def lte_event_handler(event):
    """Monitor network registration and connection events"""
    event_type = event['type']
    
    if event_type == lte.EVENT_REGISTRATION_STATUS:
        stat = event['stat']
        states = {
            0: "Not registered (not searching)",
            1: "Registered (home network)",
            2: "Not registered (searching)",
            3: "Registration denied",
            5: "Registered (roaming)"
        }
        print(f"Network: {states.get(stat, f'Unknown ({stat})')}")
        
        # Show location info if available
        if 'tac' in event and event['tac']:
            print(f"  Tracking Area: {event['tac']}")
        if 'ci' in event and event['ci']:
            print(f"  Cell ID: {event['ci']}")
        if 'act' in event and event['act'] != -1:
            act_names = {7: "LTE-M", 9: "NB-IoT"}
            print(f"  Technology: {act_names.get(event['act'], 'Unknown')}")
    
    elif event_type == lte.EVENT_SIGNAL_QUALITY:
        rssi_dbm = event['rssi_dbm']
        print(f"Signal: {rssi_dbm} dBm")
    
    elif event_type == lte.EVENT_PPP_CONNECTED:
        print(f"Connected! IP: {event['ip']}")
    
    elif event_type == lte.EVENT_PPP_DISCONNECTED:
        print("Disconnected - implementing reconnection...")
        # Application reconnection logic here

lte.init()
lte.set_event_handler(lte_event_handler, lte.EVENT_ALL)
lte.attach(apn='iot.1nce.net')

# Wait and monitor via events
for i in range(180):
    if lte.isattached():
        print("Attached! Connecting...")
        lte.connect()
        break
    time.sleep(1)

# Events continue to be monitored automatically
time.sleep(60)

lte.set_event_handler(None)
lte.disconnect()
lte.deinit()

Connection Events Only

Subscribe only to connection-related events for simple connection monitoring:

import lte

def connection_handler(event):
    """Handle only connection events"""
    if event['type'] == lte.EVENT_PPP_CONNECTED:
        print(f"✓ Connected: {event['ip']}")
    elif event['type'] == lte.EVENT_PPP_DISCONNECTED:
        print("✗ Disconnected - reconnecting...")
        lte.connect()  # Auto-reconnect

lte.init()
# Subscribe only to PPP events
lte.set_event_handler(
    connection_handler,
    lte.EVENT_PPP_CONNECTED | lte.EVENT_PPP_DISCONNECTED
)
lte.attach(apn='iot.1nce.net')
lte.connect()

Complete Connection Example

import lte
import time

def connect_lte(apn, timeout=180):
    """Connect to LTE with timeout"""
    print("Initializing...")
    lte.init()
    
    # Check SIM
    if not lte.check_sim_present():
        raise RuntimeError("No SIM card")
    
    print(f"ICCID: {lte.iccid()}")
    print(f"IMEI: {lte.imei()}")
    
    # Attach
    print("Attaching...")
    lte.attach(apn=apn)
    
    start = time.time()
    while time.time() - start < timeout:
        if lte.isattached():
            rssi, rssi_dbm, ber = lte.get_signal_strength()
            print(f"Attached! Signal: {rssi_dbm} dBm")
            break
        time.sleep(1)
    else:
        raise TimeoutError("Attachment timeout")
    
    # Connect
    print("Connecting...")
    lte.connect()
    
    start = time.time()
    while time.time() - start < 60:
        if lte.isconnected():
            ip_info = lte.ifconfig()
            print(f"Connected! IP: {ip_info[0]}")
            return
        time.sleep(1)
    else:
        raise TimeoutError("Connection timeout")

# Use it
try:
    connect_lte('iot.1nce.net')
    
    # Now you can use sockets!
    import socket
    s = socket.socket()
    s.connect(('example.com', 80))
    # ...
    
finally:
    lte.disconnect()
    lte.deinit()

Logging and Debugging

The lte module uses the SG-SDK structured logging system with two components:

  • espmodem - Low-level ESP modem library operations (UART, AT commands, internal state)
  • modlte - High-level MicroPython bindings (function calls, operations, results)

Enabling Logging

Enable logging at the start of your script before importing lte:

import logs

# Enable the entire 'lte' subsystem
logs.filter_subsystem('lte', True)

# Enable specific components for granular control
logs.filter_component('lte', 'espmodem', True)  # Low-level modem operations
logs.filter_component('lte', 'modlte', True)    # High-level Python bindings

import lte

Logging Levels

Each component logs at different levels:

  • INFO - High-level operations (function calls, status changes)
  • DEBUG - Detailed operation flow (AT commands, responses, state transitions)
  • WARN - Recoverable issues (retries, dropped events)
  • ERROR - Failures requiring attention

What Gets Logged

modlte component logs:

  • Function calls with parameters: lte.init(carrier='standard')
  • AT command execution: AT: AT+CEREG=2
  • AT command responses: AT response: +CEREG: 2,0
  • Operation results: lte.isattached() -> True
  • Callback registration and URC handling
  • High-level operation flow (attach, connect, disconnect)

espmodem component logs:

  • Modem initialization and power state detection
  • UART communication details
  • ESP modem library events
  • CMUX multiplexing operations
  • PPP session management
  • Low-level error handling and retries

Example Output

import logs
logs.filter_component('lte', 'modlte', True)
import lte

lte.init()
lte.attach(apn='iot.1nce.net')
response = lte.send_at_cmd('AT+CEREG?')

Output:

|000:00:01-234|  info  |1:mp_task |lte.init       |   lte   |   modlte    | lte.init(carrier='standard')
|000:00:01-345|  info  |1:mp_task |lte.init       |   lte   |   modlte    | LTE initialization complete
|000:00:02-456|  info  |1:mp_task |lte.attach     |   lte   |   modlte    | lte.attach(apn='iot.1nce.net', type='IP', cid=1)
|000:00:02-567|  info  |1:mp_task |send_at_cmd    |   lte   |   modlte    | AT: AT+CEREG?
|000:00:02-678|  info  |1:mp_task |send_at_cmd    |   lte   |   modlte    | AT response: +CEREG: 2,5,"001E","059B2A79",7

Debugging Tips

For general troubleshooting:

# Enable only high-level operations
logs.filter_component('lte', 'modlte', True)

For deep debugging:

# Enable both components for full visibility
logs.filter_component('lte', 'espmodem', True)
logs.filter_component('lte', 'modlte', True)

For production:

# Disable logging or enable only errors
logs.filter_subsystem('lte', False)

Troubleshooting Event Handler Issues

When debugging event handlers, enable modlte logging to see:

  • Handler registration: "Event handler registered successfully"
  • Event reception: "Event handler called from UART task context"
  • Event scheduling: "Event successfully scheduled for processing"
  • Handler execution: "Handler is valid and callable, invoking Python function"
import logs
logs.filter_component('lte', 'modlte', True)
import lte

def event_handler(event):
    print(f"[Event] {event['type']}: {event}")

lte.init()
lte.set_event_handler(event_handler)  # Watch logs for registration confirmation
lte.attach(apn='iot.1nce.net')