Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Get Started with Super SIM IP Commands and the Raspberry Pi Pico


IP Commands is a new Super SIM feature that allows your cloud to communicate with your IoT devices by exchanging UDP/IP messages. You can use IP Commands to send server-initiated messages from your cloud to your Super SIM-connected devices, and to have the responses directed to the endpoint of your choice. The best part is that you don't actually need to know a device's IP address — Twilio will handle that for you. Nor do you need to maintain a persistent connection between a device and your server.

You can think of IP Commands as a lightweight alternative to using a Virtual Private Network (VPN) to reach a device from your cloud and exchange information.

This information is transferred in the form of User Datagram Protocol (UDP) messages. UDP provides basic, 'fire and forget' data exchange: there's no connection established between sender and receiver — but no guarantee that the message will be delivered. This simplicity makes UDP ideal for lightweight IoT applications: it's a great way to send commands ("change the air con setting to 40C") to a connected device. Such commands might come from your cloud, or from an app running on an end-user's mobile device. Typically, they would be more structured than the colloquial example shown above, but completely flexible: your application, not the transport, defines the command-response interaction.

The Raspberry Pico and Waveshare modem board.

To show you how to use Super SIM IP Commands, we're going to make use of the Raspberry Pi Pico microcontroller development board. The Pico is highly programmable. Though it's intended for traditional embedded applications, and therefore supports C/C++ development, it can also be used with MicroPython(link takes you to an external page), a version of the popular language that's been tailored for use with microcontrollers rather than full PCs. This means that anyone who's used Python can quickly begin prototyping their own IoT devices with the Pico and Super SIM.

The Pico's RP2040 microcontroller is available in commercial quantities, so the development work you do can readily form the basis for production devices later.

(warning)

Warning

This guide requires a Twilio account. Sign up here now if you don't have one(link takes you to an external page). It also requires a configured Super SIM. If you haven't set up your Super SIM in the Console(link takes you to an external page), please do so now. Our Super SIM First Steps guide has extra help if you need it.


Send IP Commands to the device

send-ip-commands-to-the-device page anchor

1. Gather your components

1-gather-your-components page anchor

To complete this tutorial, you will need:

You will also need to install and configure Twilio's CLI tool for your platform.

(warning)

Warning

This tutorial assumes your Pico has MicroPython installed. Out of the box this is not the case, but our tutorial Get Started with Super SIM SMS Commands and the Raspberry Pi Pico will show you how to set up your device and to prepare the other components for use. It will also show you the software you need on your computer. If this is your first Super SIM Raspberry Pi Pico tutorial, hop over there now to get set up. Come back here when you've completed Step 4 .

If you've already finished the Get Started tutorial, you're ready to continue here.

2. Enter some Python code

2-enter-some-python-code page anchor

At this point, you should have your Pico ready to go, fitted to the Waveshare module, and connected up like this:

The development hardware.

You should have Minicom, or a similar terminal emulator, installed on your computer.

Run Minicom. Now hit Ctrl-C to break to the Python REPL. You won't use the REPL directly, but it provides a way to enter large blocks of MicroPython code easily. Hit Ctrl-E. This tells MicroPython to accept a full Python program pasted in. Click on the button at the top right of the following code to copy it and then paste it into Minicom. The copy button will appear when you mouse over the code. When you've pasted the code, hit Ctrl-D.

(information)

Info

You can find the a complete listing of the code, including all subsequent additions, at our public GitHub repo(link takes you to an external page).

If you've copied the code, you can click to jump down the page.


_221
import sys
_221
from machine import UART, Pin, I2C
_221
from utime import ticks_ms, sleep
_221
_221
# Functions
_221
_221
'''
_221
Send an AT command - return True if we got an expected
_221
response ('back'), otherwise False
_221
'''
_221
def send_at(cmd, back="OK", timeout=500):
_221
# Send the command and get the response (until timeout)
_221
buffer = send_at_get_resp(cmd, timeout)
_221
if len(buffer) > 0: return (back in buffer)
_221
return False
_221
_221
'''
_221
Send an AT command - just return the response
_221
'''
_221
def send_at_get_resp(cmd, timeout=500):
_221
# Send the AT command
_221
modem.write((cmd + "\r\n").encode())
_221
_221
# Read and return the response (until timeout)
_221
return read_buffer(timeout)
_221
_221
'''
_221
Read in the buffer by sampling the UART until timeout
_221
'''
_221
def read_buffer(timeout):
_221
buffer = bytes()
_221
now = ticks_ms()
_221
while (ticks_ms() - now) < timeout and len(buffer) < 1025:
_221
if modem.any():
_221
buffer += modem.read(1)
_221
return buffer.decode()
_221
_221
'''
_221
Module startup detection
_221
Send a command to see if the modem is powered up
_221
'''
_221
def boot_modem():
_221
state = False
_221
count = 0
_221
while count < 20:
_221
if send_at("ATE1"):
_221
print("The modem is ready")
_221
return True
_221
if not state:
_221
power_module()
_221
state = True
_221
sleep(4)
_221
count += 1
_221
return False
_221
_221
'''
_221
Power the module on/off
_221
'''
_221
def power_module():
_221
print("Powering the modem")
_221
pwr_key = Pin(14, Pin.OUT)
_221
pwr_key.value(1)
_221
sleep(1.5)
_221
pwr_key.value(0)
_221
_221
'''
_221
Check we are attached
_221
'''
_221
def check_network():
_221
is_connected = False
_221
response = send_at_get_resp("AT+COPS?", 1000)
_221
line = split_msg(response, 1)
_221
if "+COPS:" in line:
_221
is_connected = (line.find(",") != -1)
_221
if is_connected: print("Network information:", line)
_221
return is_connected
_221
_221
'''
_221
Attach to the network
_221
'''
_221
def configure_modem():
_221
# AT commands can be sent together, not one at a time.
_221
# Set the error reporting level, set SMS text mode, delete left-over SMS
_221
# select LTE-only mode, select Cat-M only mode, set the APN to 'super' for Super SIM
_221
send_at("AT+CMEE=2;+CMGF=1;+CMGD=,4;+CNMP=38;+CMNB=1;+CGDCONT=1,\"IP\",\"super\"")
_221
# Set SSL version, SSL no verify, set HTTPS request parameters
_221
send_at("AT+CSSLCFG=\"sslversion\",1,3;+SHSSL=1,\"\";+SHCONF=\"BODYLEN\",1024;+SHCONF=\"HEADERLEN\",350")
_221
print("Modem configured for Cat-M and Super SIM")
_221
_221
'''
_221
Open/close a data connection to the server
_221
'''
_221
def open_data_conn():
_221
# Activate a data connection using PDP 0,
_221
# but first check it's not already open
_221
response = send_at_get_resp("AT+CNACT?")
_221
line = split_msg(response, 1)
_221
status = get_field_value(line, 1)
_221
_221
if status == "0":
_221
# There's no active data connection so start one up
_221
success = send_at("AT+CNACT=0,1", "ACTIVE", 2000)
_221
elif status in ("1", "2"):
_221
# Active or operating data connection
_221
success = True
_221
_221
print("Data connection", "active" if success else "inactive")
_221
return success
_221
_221
def close_data_conn():
_221
# Just close the connection down
_221
send_at("AT+CNACT=0,0")
_221
print("Data connection inactive")
_221
_221
'''
_221
Start a UDP session
_221
'''
_221
def start_udp_session():
_221
send_at("AT+CASERVER=0,0,\"UDP\",6969")
_221
_221
'''
_221
Split a response from the modem into separate lines,
_221
removing empty lines and returning what's left or,
_221
if 'want_line' has a non-default value, return that one line
_221
'''
_221
def split_msg(msg, want_line=999):
_221
lines = msg.split("\r\n")
_221
results = []
_221
for i in range(0, len(lines)):
_221
if i == want_line:
_221
return lines[i]
_221
if len(lines[i]) > 0:
_221
results.append(lines[i])
_221
return results
_221
_221
'''
_221
Extract a comma-separated field value from a line
_221
'''
_221
def get_field_value(line, field_num):
_221
parts = line.split(",")
_221
if len(parts) > field_num:
_221
return parts[field_num]
_221
return ""
_221
_221
'''
_221
Flash the Pico LED
_221
'''
_221
def led_blink(blinks):
_221
for i in range(0, blinks):
_221
led_off()
_221
time.sleep(0.25)
_221
led_on()
_221
time.sleep(0.25)
_221
_221
def led_on():
_221
led.value(1)
_221
_221
def led_off():
_221
led.value(0)
_221
_221
'''
_221
Listen for IP commands
_221
'''
_221
def listen():
_221
if open_data_conn():
_221
start_udp_session()
_221
print("Listening for IP commands...")
_221
_221
# Loop to listen for messages
_221
while True:
_221
# Did we receive a Unsolicited Response Code (URC)?
_221
buffer = read_buffer(5000)
_221
if len(buffer) > 0:
_221
lines = split_msg(buffer)
_221
for line in lines:
_221
if "+CANEW:" in line:
_221
# We have a UDP packet, so get the data
_221
resp = send_at_get_resp("AT+CARECV=0,100")
_221
parts = split_msg(resp)
_221
if len(parts) > 1:
_221
# Split at the comma
_221
item = parts[1]
_221
cmd_start = item.find(",")
_221
if cmd_start != -1:
_221
cmd = item[cmd_start + 1:]
_221
print("Command received:",cmd)
_221
break
_221
else:
_221
print("ERROR -- could not open data connection")
_221
_221
'''
_221
Runtime start
_221
'''
_221
# Set up the modem UART
_221
modem = UART(0, 115200)
_221
_221
# Set the LED and turn it off
_221
led = Pin(25, Pin.OUT)
_221
led_off()
_221
_221
# Start the modem
_221
if boot_modem():
_221
configure_modem()
_221
_221
# Check we're attached
_221
state = True
_221
print("Attaching to cellular")
_221
while not check_network():
_221
if state:
_221
led_on()
_221
else:
_221
led_off()
_221
state = not state
_221
_221
# Light the LED
_221
led_on()
_221
listen()
_221
else:
_221
# Error! Blink LED 5 times
_221
led_blink(5)
_221
led_off()

This is the basis of the code you'll work on through the remainder of the guide, so you should also paste it into a text editor where you can make the subsequent changes and additions, and then copy everything at each stage over to the Pico as described above.

MicroPython will check the code and, if it's free of syntax errors, run it. You'll see status messages appear in Minicom as the code boots the cellular module and configures it, and then connects to the network. You know you're ready for the next step when you see the message Listening for IP commands... in Minicom.

These states are also reflected in the Pico's green LED. It will be off initially, but blink slowly while the device is attempting to attach to the cellular network. When the Pico has done so, the LED will stay lit.

If the LED blinks rapidly five times, the Pico could not start the module. Check your soldering and that you have connected the Pico to the module correctly.

3. Send an IP Command to the device

3-send-an-ip-command-to-the-device page anchor
  1. Open a second terminal or a new terminal tab on your computer.
  2. Send a command using the twilio tool as follows:


    _10
    twilio api:supersim:v1:ip-commands:create \
    _10
    --sim "<YOUR_SIM_NAME_OR_SID>" \
    _10
    --payload BLUE \
    _10
    --device-port 6969


    Copy and paste the twilio command to the command line (or to a text editor first) and edit it to fill in the identifier of the Super SIM you're using (you can get this from the Console(link takes you to an external page) if you don't have it handy), and your account credentials (also available from the Console(link takes you to an external page)).

The IP command to be sent is the value of the --payload parameter.

The response from Twilio, posted to the terminal, will look something like this:


_10
SID Status Date Created
_10
HGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx queued Jan 26 2022 11:11:50 GMT+0000

As you can see, the message's status is queued: it's in Twilio's message pipeline waiting to be sent. When the IP Command leaves the Twilio Mobile Core for the network the device is attached to, its status will become sent. Later on, you'll use a webhook, which you'll set up in the next section, to receive notifications about message status changes as they occur. For details of the possible status values you may see, take a look at the API documentation.

(information)

Info

If you prefer to use a different port number, just change the value of --device-port in the twilio command above and in the Python code — it's set in the function start_udp_session().

4. See the IP Command's effect on the device

4-see-the-ip-commands-effect-on-the-device page anchor

Look at Minicom's output. Has the Pico reported that it received the command BLUE?

In a real-world application the IP Command would come from your server, not be sent manually, and your device-side application would parse any that arrive and perform actions accordingly. However, the demo gives you a picture of the flow. IP Commands also supports binary data. To send information in this form, you would add the --payload-type parameter to the message-send code and set its value to "binary":


_10
twilio api:supersim:v1:ip-commands:create \
_10
--sim "<YOUR_SIM_NAME_OR_SID>" \
_10
--payload BLUE \
_10
--payload-type binary \
_10
--device-port 6969

Logging a received command is a good start, but it's not particularly exciting. Let's make use of the four-digit display so show some values in bold color.

The display needs code to drive it. Rather than include it all here, just grab the code you need from this public GitHub repo(link takes you to an external page). You want the contents of two files. Click the links below to view the raw code in your browser, then copy and paste each one into your text editor, right below the import statements at the top.

With the second file, make sure you copy only the class declaration — ignore the first two lines above it (starting # Import the base class...).

Now add the the following function right after the existing listen() function:


_28
def process_cmd(line):
_28
cmd = line
_28
val = ""
_28
val_start = line.find("=")
_28
if val_start != -1:
_28
val = line[val_start + 1:]
_28
cmd = line[:val_start]
_28
cmd = cmd.upper()
_28
if cmd == "NUM" and len(val) < 5:
_28
process_command_num(val)
_28
else:
_28
print("Command not recognized")
_28
_28
'''
_28
Display the decimal value n after extracting n from the command string
_28
'''
_28
def process_command_num(value):
_28
print("Setting",value,"on the LED")
_28
try:
_28
# Extract the decimal value (string) from 'msg' and convert
_28
# to a hex integer for easy presentation of decimal digits
_28
hex_value = int(value, 16)
_28
display.set_number((hex_value & 0xF000) >> 12, 0)
_28
display.set_number((hex_value & 0x0F00) >> 8, 1)
_28
display.set_number((hex_value & 0x00F0) >> 4, 2)
_28
display.set_number((hex_value & 0x000F), 3).draw()
_28
except:
_28
print("Bad value:",value)

Add this line to the listen() function, right after print("Command received:",cmd):


_10
process_cmd(cmd)

Finally, add the following lines after the # Setup the modem UART block:


_10
# Set up I2C and the display
_10
i2c = I2C(1, scl=Pin(3), sda=Pin(2))
_10
display = HT16K33Segment(i2c)
_10
display.set_brightness(2)
_10
display.clear().draw()

Copy all the new code from your text editor and paste it to the Pico in the usual way.

Now send a slightly different command via the IP Commands API:


_10
twilio api:supersim:v1:ip-commands:create \
_10
--sim "<YOUR_SIM_NAME_OR_SID>" \
_10
--payload "NUM=1234" \
_10
--device-port 6969

Again, you'll need to do this in a separate terminal tab or window, and replace the <...> sections with your own data.

After a moment or two you should see 1234 appear on the LED. If it doesn't appear after a short time, check you have the LED wired to the Pico correctly — take a look at the wiring diagram. If you see Setting 1234 on the LED in Minicom, you know the command was received. If not, did you enter the command above correctly? Perhaps you mis-typed the payload value. You'll see an error message in Minicom in this case.

Now it's time to make the conversation two way.


Receive IP Commands from the device

receive-ip-commands-from-the-device page anchor

The IP Commands API provides a specific IP address, 100.60.0.1, to which all IP Commands, from every device should be sent. Twilio uses a little bit of magic to streamline the relay of your messages to your cloud. How does it know where to send them? You specify a webhook address.

Super SIMs are organized into Fleets: groups of SIMs that have common settings, such as which networks they are able to connect to and whether they can make use of certain cellular services. Fleets are represented in the Super SIM API by Fleet resources, and IP Commands adds a property to each Fleet resource in which you can store your IP Commands webhook address.

So before we can send a message, we need to set up a webhook target URL and add that to your Super SIM's Fleet.

1. Set up a webhook target

1-set-up-a-webhook-target page anchor

Beeceptor(link takes you to an external page) is a handy service for testing webhooks. It's designed for developing and testing APIs of your own, and offers free mock servers — virtual endpoints that can receive webhook calls. Let's set one up to receive messages from the device.

  1. In a web browser tab, go to Beeceptor(link takes you to an external page) .
  2. Enter an endpoint name and click Create Endpoint :
  3. On the screen that appears next, click on the upper of the two clipboard icons to copy the endpoint URL:
  4. Keep the tab open.

2. Update your Super SIM's Fleet

2-update-your-super-sims-fleet page anchor

Now you update your Super SIM's Fleet to add an IP Commands webhook. You can do this with the API, but we'll use the Console for this demo.

  1. Open a second web browser tab and log into the Twilio Console(link takes you to an external page) .
  2. Go to IoT > Super SIM > Fleets and select the Fleet containing the Super SIM you are using.
  3. Scroll down to IP Commands Callback URL and paste in your webhook URL from the previous section. By default, webhooks are triggered with a POST request. Leave it unchanged for now, but if you need to use another method for your own application, you can change it later.
  4. Click Save .
  5. Close the tab if you like.

3. Update the application

3-update-the-application page anchor

To add message sending to the device code, you can add following function after all the other function definitions:


_10
def send_data(data_string):
_10
data_length = len(data_string)
_10
if send_at("AT+CASEND=0," + str(data_length), ">"):
_10
# '>' is the prompt sent by the modem to signal that
_10
# it's waiting to receive the message text.
_10
resp = send_at_get_resp(data_string + chr(26))

Now add these two lines into the function process_cmd(), right after process_command_num(val):


_10
elif cmd == "SEND":
_10
send_data("We can\'t wait to see what you build!")

Finally, add this line to the end of the function start_udp_session():


_10
send_at("AT+CACFG=\"REMOTEADDR\",0,100.64.0.1,6969")

Save the file and copy it across to the Pico.

Let's quickly take a look at that last addition as it's key. The AT command +CACFG is a Simcom-only command that performs a specific configuration option. That option is "REMOTEADDR" — it sets the remote IP address all future UDP messages will be transmitted to, at least until the value is changed.

Which address do we apply? 100.64.0.1, the third parameter passed with the command. This is the standard IP Commands input address. The last parameter, 6969, is the local port number.

4. Send an IP Command to send a message

4-send-an-ip-command-to-send-a-message page anchor

Send a new command, SEND, via different command via the IP Commands API:


_10
twilio api:supersim:v1:ip-commands:create \
_10
--sim "<YOUR_SIM_NAME_OR_SID>" \
_10
--payload SEND \
_10
--device-port 6969

Jump back to the Beeceptor tab in the browser. You should see — or will shortly see — the endpoint has received a POST request. Click on it to see the request body, then on the JSON icon, {:}, to view the data more clearly.

Unlike messages sent to the device, which can be text or binary, messages that are sent from the device always arrive in binary form. The string sent as a result of your SEND command has been base64 encoded, so it'll need decoding before you can read it. Look for the line beginning Payload= and copy the characters that appear after it, up until the end of the line. It'll look something like this:


_10
bm90IGEgcmVhbCBtZXNzYWdlCg==

Switch to a spare terminal and enter:


_10
echo <BASE64_ENCODED_STRING> | base64 -d

making sure you paste the text you copied between the echo and the | symbol as indicated. You'll be presented with the message in English:


_10
We can't wait to see what you build!


Monitor IP Command transmission

monitor-ip-command-transmission page anchor

The webhook URL you just put in place was set up to receive IP Commands from the device. You can also use it in a tutorial context for monitoring messages being sent to the device. Use the same technique you applied in the Send IP Commands to the device section, above, but this time add the following extra command to the twilio call: --callback-url followed by a space and then your Beeceptor endpoint.


_10
twilio api:supersim:v1:ip-commands:create \
_10
--sim "<YOUR_SIM_NAME_OR_SID>" \
_10
--payload SEND \
_10
--device-port 6969
_10
--callback-url <YOUR_WEBHOOK_URL>

Now you'll receive a series of notification messages as the IP Command's status moves from queued to sent. For details of the possible status values you may see, take a look at the API documentation.


You've set up a Raspberry Pi Pico and Simcom SIM7080 cellular module to send and receive UDP messages over a data connection, then you've actually sent those messages through Twilio Super SIM's IP Commands API.

You can try sending some alternative messages, including some binary data.

Try using IP Commands to toggle GPIO pins so the Pico can begin to control things. Create a web page to call the API and trigger the commands.

IP Command Resources are persisted for 30 days, so you can check your send and receive histories at any time. For example, a GET request made to https://supersim.twilio.com/v1/IpCommands will fetch all existing resources for inspection. If you have the SID of a specific Command's resource, you can add it as a path value to the endpoint above. Try it now.

The next stage, of course, is to build a device-side app that listens for a variety of commands, parses them and triggers actions accordingly. You may also want to create a server app that's able to request information from devices: it sends a request command which causes the device to send a second, data-bearing message back. Or you might code up a mobile app that uses IP Commands to control the device remotely.

We can't wait to see what you build with Super SIM IP Commands!


Rate this page: