Created
November 30, 2025 03:38
-
-
Save devjourney/d3f25b2a735478e45d258ff2bb25642a to your computer and use it in GitHub Desktop.
Configure many Shelly IoT devices at once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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