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.
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()Initialize the LTE modem subsystem. This method is idempotent - it's safe to call multiple times.
The modem intelligently handles three possible power states:
- Powered off - Powers on and initializes from scratch
- Normal AT mode (crash recovery) - Resumes from existing state
- 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()orlte.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 calllte.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.
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, 71bands(list, optional): List of frequency bands (e.g.,[2, 4, 12]). Cannot use withband
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)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 fromattach())
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)Disconnect from the data session but keep network attachment.
Example:
lte.disconnect()Gracefully detach from the cellular network and disable radio functionality.
Example:
lte.detach()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): IfTrue, detach from cellular network. Default:Truepower_off(bool, optional): IfTrue, 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:
- Normal shutdown:
lte.deinit()- Clean disconnect and power off - Quick restart:
lte.deinit(power_off=False)- Faster nextlte.init() - Ultra-low power with connectivity:
lte.deinit(detach=False, power_off=False)+ PSM/eDRX - Reconfigure network:
lte.deinit(detach=True, power_off=False)- Change APN/bands
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()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")Alias for lte.isattached(). Provided for compatibility with legacy scripts.
Returns: True if attached, False otherwise
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]}")Alias for lte.isconnected(). Provided for compatibility with legacy scripts.
Returns: True if connected, False otherwise
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: Falsecheck_error(bool, optional): Raise exception on ERROR. Default: Falsebuffer_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)Get or set the modem operating mode (CAT-M1 or NB-IoT).
Parameters:
new_mode(int, optional): New mode to set. Uselte.CATM1(0) orlte.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)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}")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")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})")Get comprehensive modem status information.
Returns: Dictionary with the following keys:
powered(bool): Modem power statesim_ready(bool): SIM card present and readynetwork_attached(bool): Attached to networkppp_connected(bool): PPP session activecmux_active(bool): CMUX multiplexing enabledbaudrate(int): Current UART baudraterssi(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']}")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}")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 responsePower 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 offCheck if the modem is currently powered on.
Returns: True if powered, False otherwise
Example:
if lte.is_powered():
print("Modem is powered on")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")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, orNoneto unregisterevent_mask(int, optional): Bitmask of events to subscribe to. Default:lte.EVENT_ALLlock(bool, optional): IfTrue, lock the event handler so it cannot be overridden untillte.deinit()is called. Attempting to set or unregister the handler while locked raisesOSError. 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
"""
passEvent Types:
-
lte.EVENT_REGISTRATION_STATUS(0x0001) - Network registration changesstat(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 Codeci(str, optional): Cell IDact(int, optional): Access Technology (-1=unknown, 7=LTE-M, 9=NB-IoT)cause_type(int, optional): Reject cause typereject_cause(int, optional): Reject cause codeactive_time(str, optional): PSM active timeperiodic_tau(str, optional): PSM periodic TAU
-
lte.EVENT_PPP_CONNECTED(0x0002) - PPP connection establishedip(str): Assigned IP addressnetmask(str): Network maskgateway(str): Gateway addressdns1(str): Primary DNS serverdns2(str, optional): Secondary DNS server
-
lte.EVENT_PPP_DISCONNECTED(0x0004) - PPP connection lost -
lte.EVENT_MODEM_CRASH(0x0008) - Modem crash detectedbreak_count(int): Number of break signals received
-
lte.EVENT_MODEM_RESET(0x0010) - Modem was resetuser_initiated(bool): Whether reset was user-initiatedreason(str, optional): Reset reason
-
lte.EVENT_SIGNAL_QUALITY(0x0020) - Signal strength updaterssi(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)
-
lte.EVENT_URC(0x0040) - Unsolicited response code (raw AT response)data(str): The raw URC string
-
lte.EVENT_ERROR(0x0080) - Error occurrederror_code(int): Error codemessage(str, optional): Error messageoperation(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 nowBasic 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.
lte.CATM1(0): CAT-M1 mode constantlte.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 for use with lte.set_event_handler():
lte.EVENT_REGISTRATION_STATUS(0x0001): Network registration status changeslte.EVENT_PPP_CONNECTED(0x0002): PPP connection establishedlte.EVENT_PPP_DISCONNECTED(0x0004): PPP connection lostlte.EVENT_MODEM_CRASH(0x0008): Modem crash detectedlte.EVENT_MODEM_RESET(0x0010): Modem reset occurredlte.EVENT_SIGNAL_QUALITY(0x0020): Signal strength updatelte.EVENT_URC(0x0040): Raw unsolicited response codelte.EVENT_ERROR(0x0080): Error occurredlte.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)The module raises MicroPython exceptions for errors:
OSError- General modem errors (timeout, not responding, etc.)ValueError- Invalid parametersMemoryError- Memory allocation failureTypeError- 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}")This C implementation provides several improvements over the Python LTE.py module:
-
Better Resource Management:
- Proper UART reservation/release
- Memory-efficient buffers
- WiFi/LTE coexistence handled automatically
-
CMUX Support:
- Simultaneous AT commands and data sessions
- No need for
pause_ppp()/resume_ppp() - More reliable operation
-
Event-Driven Architecture:
- Unified event handler with structured data
- No need to manually poll for status changes
- Real-time event notification with automatic parsing
-
Robust Initialization:
- Handles modem power states intelligently
- Automatic crash recovery
- Idempotent initialization
-
Performance:
- Native C implementation
- Faster command execution
- Lower memory footprint
Most methods are compatible with LTE.py, but note:
pause_ppp()andresume_ppp()are not needed (CMUX handles this)read_rsp()is replaced byset_event_handler()- Initialization is simpler (no explicit baudrate management)
check_power()renamed tois_powered()- Automatic WiFi conflict detection/resolution
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()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()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()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)
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 lteEach 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
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
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
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)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')