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:
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()`` 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 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.
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, 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)
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 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)
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): 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 offQuick restart:
lte.deinit(power_off=False)- Faster nextlte.init()Ultra-low power with connectivity:
lte.deinit(detach=False, power_off=False)+ PSM/eDRXReconfigure 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: 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)
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. 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)
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 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']}")
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, 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
"""
pass
Event Types:
``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 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 established
ip(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 detected
break_count(int): Number of break signals received
``lte.EVENT_MODEM_RESET`` (0x0010) - Modem was reset
user_initiated(bool): Whether reset was user-initiatedreason(str, optional): Reset reason
``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)
``lte.EVENT_URC`` (0x0040) - Unsolicited response code (raw AT response)
data(str): The raw URC string
``lte.EVENT_ERROR`` (0x0080) - Error occurred
error_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 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 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
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)
Error Handling
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}")
Implementation Notes
Differences from LTE.py
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
Compatibility Notes
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
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=2AT command responses:
AT response: +CEREG: 2,0Operation results:
lte.isattached() -> TrueCallback 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')