circremote is a command-line tool for uploading and running Python code on CircuitPython devices. It supports both local serial connections and networked WebSocket connections (via CircuitPython Web Workflow), making it easy to remotely control and run programs on CircuitPython boards.
The preferred and easiest way is to use PyPI:
pip install circremotepip install git+https://github.com/romkey/circremoteFor development, you can install in editable mode:
git clone https://github.com/romkey/circremote
cd circremote-python
pip install -e .I do not have access to a Windows machine and have not tried to use circremote on one. I cannot make any promises or offer any support for Windows users, sorry.
circremote works with any CircuitPython-compatible board, including:
- Raspberry Pi Pico
- Adafruit Feather boards
- ESP32/ESP8266 boards
- SAMD21/SAMD51 boards
- And many others
You don't. circremote is intended to upload and run a program on a device without disturbing the software that's already stored on it. It does not currently support saving files to the device.
circremote /dev/ttyUSB0 BME280# Show detailed help including tested status
circremote -h BME280
# Help shows:
# - Description of what the command does
# - Status: ✅ Tested, ⚠️ Not tested, or ❓ Unknown
# - Required and optional arguments with defaults
# - Usage examples# Explicit variable assignment
circremote /dev/ttyUSB0 BME280 sda=board.IO1 scl=board.IO2
# Positional arguments (if defined in info.json)
circremote /dev/ttyUSB0 mycommand board.IO1 board.IO2
# Mix of both
circremote /dev/ttyUSB0 mycommand board.IO1 board.IO2 address=0x76
# Using device defaults (configured in config.json)
circremote my-device BME280 # Uses sda/scl/address from device configYou must configure the device to support Web Workflow in order to connect over WiFi. Only devices with integrated WiFi (ESP32s, Raspberry Pi Pico W) support Web Workflow. Devices using an external ESP32 as a WiFi coprocessor, like the Adafruit Matrix Portal M4 - this is a limitation of CircuitPython.
# Using IP address (default port 80)
circremote 192.168.1.100 BME280
# Using IP address with custom port
circremote 192.168.1.100:8080 BME280
# With password
circremote -p mypassword 192.168.1.100 BME280You can configure default values for command variables on a per-device basis in your ~/.circremote/config.json file. This is especially useful for I2C pin assignments that are specific to your board layout.
Configuration Example:
{
"devices": [
{
"name": "my-board",
"device": "/dev/ttyUSB0",
"defaults": {
"sda": "board.IO1",
"scl": "board.IO2",
"address": "0x76"
}
}
]
}Usage:
# Instead of specifying pins every time:
circremote /dev/ttyUSB0 BME280 sda=board.IO1 scl=board.IO2 address=0x76
# You can simply use:
circremote my-board BME280Variable Resolution Priority:
- Command line values (highest priority) -
sda=board.IO5 - Device defaults (from config.json) -
sda: "board.IO1" - Global variable defaults (from config.json) -
sda: "board.SDA" - Command defaults (from info.json) -
"default": "board.SDA"
You can configure global default values for command variables that apply to all devices and commands in your ~/.circremote/config.json file. This is useful for setting common defaults like I2C pins that are consistent across your setup.
Configuration Example:
{
"variable_defaults": {
"sda": "board.SDA",
"scl": "board.SCL",
"address": "0x76"
},
"devices": [
{
"name": "my-board",
"device": "/dev/ttyUSB0",
"defaults": {
"sda": "board.IO1",
"scl": "board.IO2"
}
}
]
}Usage:
# Global defaults apply to all commands unless overridden
circremote /dev/ttyUSB0 BME280 # Uses global sda/scl, command default address
circremote my-board BME280 # Uses device sda/scl, global address
circremote /dev/ttyUSB0 BME280 sda=board.IO5 # Overrides all defaultsBenefits:
- Consistent defaults: Set common values once for all commands
- Device overrides: Device-specific defaults still take precedence
- Command overrides: Command line values always take highest priority
- Reduced typing: No need to specify common variables repeatedly
circremote supports running commands directly from URLs, including GitHub repositories and other web servers.
Remote Command Directory (any URL not ending in .py):
# Fetch code.py, info.json, and requirements.txt from a directory
circremote /dev/ttyUSB0 https://github.com/user/repo/sensor/
circremote /dev/ttyUSB0 https://example.com/sensors/temperature/
circremote /dev/ttyUSB0 https://github.com/user/repo/tree/main/commands/sensor
circremote /dev/ttyUSB0 https://github.com/romkey/circremote/tree/main/circremote/commands/BME680Remote Python File (ends with .py):
# Fetch a Python file and associated metadata
circremote /dev/ttyUSB0 https://example.com/sensor.py
circremote /dev/ttyUSB0 https://raw.githubusercontent.com/user/repo/main/sensor.pySingle File (any other URL):
# Fetch a single file (existing behavior)
circremote /dev/ttyUSB0 https://example.com/script.pyFeatures:
- Automatic metadata: For directory URLs (any URL not ending in .py),
circremotefetchescode.py,info.json, andrequirements.txt - Associated files: For Python files,
circremotetries to fetchinfo.jsonandrequirements.txtin the same directory - Dependency installation: Remote
requirements.txtfiles trigger automatic circup installation - GitHub support: GitHub URLs are automatically converted to raw content URLs
- Variable support: Remote commands support the same variable interpolation as local commands
Example with dependencies:
# This will fetch the command and install any required libraries
circremote /dev/ttyUSB0 https://github.com/user/repo/sensor/
# If requirements.txt exists, circup will be run automaticallycircremote -v /dev/ttyUSB0 BME280# Quiet mode - suppresses all circremote output except device output
circremote -q /dev/ttyUSB0 BME280
# Quiet mode with auto-confirm (skips all prompts)
circremote -q -y /dev/ttyUSB0 BME280
# Quiet mode with skip-circup (if dependencies need to be installed)
circremote -q -c /dev/ttyUSB0 BME280Quiet mode behavior:
- Suppresses: All circremote messages, warnings, progress info, module descriptions
- Shows: Only output from the CircuitPython device
- Exits with error: If any confirmation is needed (untested commands, offline warnings, dependencies)
- Use with
-y: Combine with-yto auto-confirm all prompts in quiet mode - Use with
-c: Combine with-cto skip dependency installation in quiet mode
Note: Cannot be used with -v (verbose) option - they are mutually exclusive.
circremote -c /dev/ttyUSB0 BME280# Command line option
circremote -u /usr/local/bin/circup /dev/ttyUSB0 BME280
circremote -u ~/venv/bin/circup /dev/ttyUSB0 BME280
# Config file optionAdd to your ~/.circremote/config.json:
{
"circup": "/usr/local/bin/circup"
}Precedence order:
- Command line option (
-u PATH) - highest priority - Config file setting (
"circup": "PATH")
# Use a different config file
circremote -C /path/to/custom.json /dev/ttyUSB0 BME280
circremote -C ~/projects/my_config.json 192.168.1.100 BME280
# Useful for:
# - Testing different configurations
# - Using different device sets for different projects
# - Sharing configurations with team members- System PATH resolution (
circup) - default
# Wait 30 seconds for output
circremote -t 30 /dev/ttyUSB0 BME280
# Wait indefinitely
circremote -t 0 /dev/ttyUSB0 BME280Wifi-enabled Raspberry Pi Pico W boards do not support the Web Workflow. Only ESP32 CPUs support it.
Create a config file at ~/.circremote/config.json:
{
"devices": [
{
"name": "pico1",
"device": "/dev/ttyACM0",
"friendly_name": "Raspberry Pi Pico"
},
{
"name": "feather1",
"device": "192.168.1.100:8080",
"password": "mypassword",
"friendly_name": "Adafruit Feather ESP32"
}
]
}Then use the shortcut name:
circremote pico1 BME280
circremote feather1 BME280Add command aliases to your config file:
{
"command_aliases": [
{
"name": "temp",
"command": "BME280"
},
{
"name": "light",
"command": "TSL2591"
}
]
}Then use the alias:
circremote /dev/ttyUSB0 tempYou can create commands in several ways:
Create a Python file and run it directly:
circremote /dev/ttyUSB0 ./my_sensor.pyCreate a directory with the required files:
my_command/
├── code.py # Required: Your Python code
├── info.json # Optional: Command metadata and variables
└── requirements.txt # Optional: Dependencies
Then run it:
circremote /dev/ttyUSB0 ./my_commandAdd custom command directories to your config:
{
"search_paths": [
"/path/to/my/commands",
"~/custom_sensors",
"/opt/circremote/commands"
]
}Commands in these directories will be available by name:
circremote /dev/ttyUSB0 my_custom_sensor{
"name": "My Custom Sensor",
"description": "A custom temperature and humidity sensor",
"tested": true,
"variables": [
{
"name": "sda",
"description": "I2C SDA pin",
"default": "board.SDA"
},
{
"name": "scl",
"description": "I2C SCL pin",
"default": "board.SCL"
}
],
"default_commandline": "sda scl"
}In your code.py, use {{variable_name}} syntax:
import board
import busio
from adafruit_bme280 import basic as adafruit_bme280
# These will be replaced with actual values
i2c = busio.I2C({{scl}}, {{sda}})
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
print(f"Temperature: {bme280.temperature}°C")circremote is able to download code from a web server and send it to a CircuitPython device. It currently only downloads the code; it cannot handle library dependencies or get information about the code from an info.json file.
Please be careful downloading random code from a web site. Even on a CircuitPython device it could contain malicious code which could share your WiFi credentials or other information stored on the device with bad actors.
circremote automatically rewrites Github URLs to properly access the file managed at that URL. These two examples are identical:
# Direct GitHub URL
circremote /dev/ttyUSB0 https://github.com/adafruit/Adafruit_CircuitPython_BME680/blob/main/examples/bme680_simpletest.py`
# GitHub raw URL
circremote /dev/ttyUSB0 https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_BME680/main/examples/bme680_simpletest.pyBe aware that circremote will not automatically install the BME680 library if you run this code this way. It will if you run the local BME680 command.
# Any HTTP/HTTPS URL
circremote /dev/ttyUSB0 https://romkey.com/circup/hello.pyYes, circremote will automatically try to load code.py, info.json and requirements.txt files from URLs that aren't Python files.
This usually means:
- The command name is misspelled
- The command doesn't exist in the built-in library
- The command isn't in your search paths
Check available commands:
circremote -lFor serial connections:
- Check if the device is connected
- Verify the port name (e.g.,
/dev/ttyUSB0,/dev/ttyACM0) - Make sure no other program is using the port
For WebSocket connections:
- Verify the IP address and port
- Check if CircuitPython Web Workflow is enabled on the device
- Ensure the device is on the same network
For WebSocket connections:
- Check your password in the config or
-poption - Verify the password matches
CIRCUITPY_WEB_API_PASSWORDinsettings.tomlon the device - Restart the device after changing the password
For serial connections:
- Add your user to the
dialoutgroup (Linux):sudo usermod -a -G dialout $USER - On macOS, you might need to install drivers for your device
This means a required library isn't installed on the device:
- Check if the command has a
requirements.txtfile - Make sure circup is installed:
pip install circup - Try running without
-Cflag to install dependencies automatically
Your code uses template variables (like {{sda}}) but no values were provided:
# Provide values on command line
circremote /dev/ttyUSB0 BME280 sda=board.IO1 scl=board.IO2
# Or define defaults in info.jsonYou provided a variable that isn't defined in the command's info.json:
- Check the command's documentation for valid variables
- Look at the
info.jsonfile to see what variables are expected - Use
-vflag to see debug information
Install circup to manage CircuitPython dependencies:
pip install circupIf circup is installed but not found, specify the path:
# Command line
circremote -c /usr/local/bin/circup /dev/ttyUSB0 BME280
# Config fileAdd to ~/.circremote/config.json:
{
"circup": "/usr/local/bin/circup"
}Common circup locations:
/usr/local/bin/circup(pip install)/opt/homebrew/bin/circup(Homebrew on macOS)~/venv/bin/circup(virtual environment)circup(system PATH)
- Check the connection (serial port or IP address)
- Try pressing the reset button on the device
- Verify the device is running CircuitPython
- Check if the device is in bootloader mode (some boards have a boot button)
- Check if the device is running CircuitPython Web Workflow
- Verify the IP address and port
- Check your network connection
- Try restarting the device
# Wait 5 seconds for output
circremote -t 5 /dev/ttyUSB0 BME280
# Wait indefinitely (until you press Ctrl+C)
circremote -t 0 /dev/ttyUSB0 BME280Use "double exit mode" to send an extra ^D to the device in order to re-start the program in code.py:
circremote -d /dev/ttyUSB0 BME280circremote -y /dev/ttyUSB0 untested_commandIf something goes wrong and you want detailed debugging info, use the -v or --verbose flag.
circremote -v /dev/ttyUSB0 BME280If your config file has world-readable permissions, you'll see a warning. Fix it:
chmod 600 ~/.circremote/config.json{
"devices": [
{
"name": "my-device",
"device": "/dev/ttyUSB0",
"friendly_name": "My CircuitPython Device"
}
],
"command_aliases": [
{
"name": "temp",
"command": "BME280"
}
],
"search_paths": [
"~/my_commands",
"/opt/custom_sensors"
],
"circup": "/usr/local/bin/circup"
}macOS/Linux:
/dev/ttyUSB0- USB-to-serial adapter/dev/ttyACM0- Arduino-style device/dev/ttyS0- Built-in serial port
Windows:
COM1,COM2,COM3, etc. - Standard Windows COM ports- Check Device Manager to find the correct COM port number
Finding your port:
# macOS/Linux
ls /dev/tty*
# Windows
# Check Device Manager > Ports (COM & LPT)macOS/Linux:
# Check if user is in dialout group
groups $USER
# Add user to dialout group
sudo usermod -a -G dialout $USER
# Log out and back in, or run:
newgrp dialoutWindows:
- Run as Administrator if permission denied
- Check Device Manager for port conflicts
- Ensure device drivers are installed
If you get an error like 'circremote' is not recognized as an internal or external command, Python or the pip installation directory is not in your Windows PATH.
Solution: Add Python to Windows PATH
-
Find your Python installation:
python --version
If this works, note the Python version. If not, you need to install Python first.
-
Find your pip installation directory:
python -m site --user-base
This will show something like
C:\Users\YourUsername\AppData\Local\Programs\Python\Python3x\Lib\site-packages -
Add Python Scripts to PATH:
- Press
Win + R, typesysdm.cpl, press Enter - Click "Environment Variables"
- Under "User variables", find "Path" and click "Edit"
- Click "New" and add:
C:\Users\YourUsername\AppData\Local\Programs\Python\Python3x\Scripts - Replace
YourUsernamewith your actual username - Replace
Python3xwith your Python version (e.g.,Python39,Python310) - Click "OK" on all dialogs
- Press
-
Alternative: Use Python module directly:
python -m circremote COM3 BME280
-
Restart your command prompt and try again:
circremote --version
Note: If you installed Python with the Microsoft Store, the path might be different. Use where python to find the correct location.
The -t or --timeout option sets a maximum time (in seconds) for circremote to wait for output from the device.
Behavior:
- Serial connections: Timeout applies to waiting for device output
- WebSocket connections: Timeout applies to connection establishment
- Default: 10 seconds if not specified
- Disable: Use
-t 0to disable timeout
Cross-platform compatibility:
- Windows: Uses
threading.Timerfor timeout handling - macOS/Linux: Uses
signal.SIGALRMwiththreading.Timerfallback - All platforms: Timeout properly exits circremote when reached
Example:
# Wait up to 30 seconds for output
circremote -t 30 /dev/ttyUSB0 BME280
# Wait up to 5 seconds for output
circremote -t 5 COM3 scan-i2c
# No timeout (wait indefinitely)
circremote -t 0 /dev/ttyUSB0 long_running_commandCircuitPython Web Workflow allows you to connect to devices over the network instead of USB.
Prerequisites:
- ESP32-based CircuitPython device (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2)
- Device connected to WiFi network
- Device has network connectivity
- CIRCUITPY drive unmounted (the command will check this automatically)
Setup Steps:
-
Unmount CIRCUITPY drive (if mounted):
# macOS sudo umount /Volumes/CIRCUITPY # Linux sudo umount /media/username/CIRCUITPY # Windows # Right-click on CIRCUITPY drive → Eject
-
Configure Web Workflow:
# Configure with your WiFi network, port, and Web Workflow password circremote /dev/ttyUSB0 enable-webworkflow "MyWiFiNetwork" "mypassword" 8080 "webpass"
-
Restart the device to apply the configuration
-
Test the connection:
# Connect using Web Workflow (use device's actual IP address) circremote 192.168.1.100:8080 -p webpass system-info
Configuration Details:
- WiFi SSID: Your WiFi network name
- WiFi Password: Your WiFi network password
- Port: Web server port (default: 8080, range: 1-65535)
- Web Workflow Password: Password for Web Workflow authentication
Troubleshooting:
- Ensure device is connected to WiFi
- Check that the IP address is correct and reachable
- Verify the port isn't blocked by firewall
- Make sure the password matches what you configured
The enable-webworkflow command configures both WiFi and Web Workflow settings on ESP32 devices in one step.
Command Format:
circremote <device> enable-webworkflow <wifi_ssid> <wifi_password> <port> <web_workflow_password>Example:
circremote /dev/ttyUSB0 enable-webworkflow "MyWiFi" "wifipass123" 8080 "webpass456"What it does:
- Checks filesystem mount status - Ensures CIRCUITPY is unmounted
- Validates ESP32 compatibility - Confirms the device supports Web Workflow
- Writes configuration to settings.toml:
CIRCUITPY_WIFI_SSID- WiFi network nameCIRCUITPY_WIFI_PASSWORD- WiFi passwordCIRCUITPY_WEB_API_PASSWORD- Web Workflow authentication passwordCIRCUITPY_WEB_API_PORT- Web server port
- Provides next steps - Instructions for restarting and connecting
Safety Features:
- Warns about offline risk - Command may make device unreachable
- Prevents overwriting - Won't overwrite existing configuration
- Mount detection - Automatically detects if CIRCUITPY is mounted
- Port validation - Ensures port number is valid (1-65535)
After running the command:
- Restart your CircuitPython device
- Device connects to WiFi automatically
- Find device IP address (check serial output)
- Use circremote with Web Workflow:
circremote <ip>:<port> -p <web_password> <command>
Resources:
# Quiet mode - suppresses all circremote output except device output
circremote -q /dev/ttyUSB0 BME280
# Quiet mode with auto-confirm (skips all prompts)
circremote -q -y /dev/ttyUSB0 BME280
# Quiet mode with skip-circup (if dependencies need to be installed)
circremote -q -c /dev/ttyUSB0 BME280Quiet mode behavior:
- Suppresses: All circremote messages, warnings, progress info, module descriptions
- Shows: Only output from the CircuitPython device
- Exits with error: If any confirmation is needed (untested commands, offline warnings, dependencies)
- Use with
-y: Combine with-yto auto-confirm all prompts in quiet mode - Use with
-c: Combine with-cto skip dependency installation in quiet mode
Note: Cannot be used with -v (verbose) option - they are mutually exclusive.