Skip to content

Instantly share code, notes, and snippets.

@devjourney
Created November 30, 2025 03:38
Show Gist options
  • Select an option

  • Save devjourney/d3f25b2a735478e45d258ff2bb25642a to your computer and use it in GitHub Desktop.

Select an option

Save devjourney/d3f25b2a735478e45d258ff2bb25642a to your computer and use it in GitHub Desktop.
Configure many Shelly IoT devices at once.
# python configure_shelly.py --password <password> --method BLE.SetConfig --device-file .\shelly_ips.txt --config "{ \"rpc\": { \"enable\": true } }"
# python configure_shelly.py --password <password> --method BLE.GetConfig --device-file .\shelly_ips.txt
# python configure_shelly.py --password <password> --method Cloud.SetConfig --device-file .\shelly_ips.txt --config "{ \"enable\": true }"
# python configure_shelly.py --password <password> --method Cloud.GetConfig --device-file .\shelly_ips.txt
import requests
import json
from requests.auth import HTTPDigestAuth
import argparse
def exec_config(ip_address: str, password: str, method: str, config: dict):
url = f"http://{ip_address}/rpc"
username = "admin"
payload = {
"jsonrpc": "2.0",
"id": 1,
"src": "python-script",
"method": method
}
if config is not None:
payload["params"] = { "config": config }
try:
response = requests.post(
url,
json = payload,
auth = HTTPDigestAuth(username, password),
timeout = 5
)
response.raise_for_status()
result = response.json()
if 'error' in result:
print(f"JSON-RPC Error received from device: {result['error']['message']}")
return None
if 'result' in result:
return result['result']
else:
print(f"Error: Unexpected response structure: {result}")
return None
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
print("Authentication failed (401 Unauthorized). Check the password.")
else:
print(f"HTTP Error: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Network or request error: {e}")
return None
except json.JSONDecodeError:
print("Error: Could not decode JSON response from device.")
print(f"Raw Response Text: {response.text}")
return None
def main():
parser = argparse.ArgumentParser(description="My script that needs a password and device IP file")
parser.add_argument("-p", "--password",
required = True,
help="Password (use carefully - it will be visible in process list)")
parser.add_argument("-m", "--method",
required = True,
help="Method to execute on the device")
parser.add_argument("-c", "--config",
required = False,
help="Configuration to send to the device")
parser.add_argument("-d", "--device-file",
required = True,
help="Path to the device IP configuration file")
args = parser.parse_args()
AUTH_PASS = args.password
DEVICE_FILE = args.device_file
METHOD = args.method
CONFIG = json.loads(args.config) if args.config else None
try:
with open(DEVICE_FILE, 'r') as f:
for line_num, line in enumerate(f, 1):
ip = line.strip()
if not ip or ip.startswith('#'):
continue
print(f"Processing device {ip} (line {line_num})...")
result = exec_config(ip, AUTH_PASS, METHOD, CONFIG)
if result is not None:
print(f"Device {ip} responded: {result}")
else:
print(f"Failed to get a valid response from device {ip}")
except FileNotFoundError:
print(f"Error: File {DEVICE_FILE} not found.")
except PermissionError:
print(f"Error: Permission denied reading {DEVICE_FILE}.")
except Exception as e:
print(f"Unexpected error reading {DEVICE_FILE}: {e}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment