The multi-device server provides comprehensive device lifecycle management through REST APIs and persistent storage. Devices are registered dynamically, stored in devices.json, and managed in-memory through the DEVICES dictionary.
Why Dictionary Keys?Device IDs are used as dictionary keys for O(1) lookup performance. This is critical when validating device existence in request handlers.
Devices are loaded from devices.json on server startup:
source/server.py
DEVICES_FILE = os.getenv("DEVICES_FILE", "devices.json")def load_devices(): """Load devices from devices.json. If not exists, create defaults.""" if os.path.exists(DEVICES_FILE): with open(DEVICES_FILE, "r", encoding="utf-8") as f: return json.load(f) # Default device for first-time setup default = { "principal": { "ip": "192.168.1.205", "port": 4370, "password": 0, "timeout": 5, "name": "Entrada Principal", "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } } save_devices(default) return default# Load on startupDEVICES = load_devices()log.info(f"Dispositivos cargados: {list(DEVICES.keys())}")
First-Time SetupIf devices.json doesn’t exist, the server creates it with a default device configuration. This ensures the server is immediately usable after installation.
Server validates required fields and normalizes the device ID:
source/server.py
device_id = str(body.get("id", "")).strip().lower().replace(" ", "_")name = str(body.get("name", "")).strip()ip = str(body.get("ip", "")).strip()if not device_id or not name or not ip: return _json({"success": False, "error": "id, name e ip son requeridos."}, 400)
3
Check for Conflicts
Verify the device ID doesn’t already exist:
source/server.py
with devices_lock: if device_id in DEVICES: return _json({"success": False, "error": f"El id '{device_id}' ya existe."}, 409)
4
Create Device and Lock
Add device to registry and create its dedicated lock:
Devices can be updated via PUT request to /devices/{device_id}:
source/server.py
@app.route("/devices/<device_id>", methods=["PUT"])def update_device(device_id): if device_id not in DEVICES: return device_not_found(device_id) body = request.get_json(silent=True) if not body: return _json({"success": False, "error": "Body JSON requerido."}, 400) with devices_lock: cfg = DEVICES[device_id] # Update only provided fields if "name" in body: cfg["name"] = str(body["name"]).strip() if "ip" in body: cfg["ip"] = str(body["ip"]).strip() if "port" in body: cfg["port"] = int(body["port"]) if "password" in body: cfg["password"] = int(body["password"]) if "timeout" in body: cfg["timeout"] = int(body["timeout"]) # Add update timestamp cfg["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") save_devices(DEVICES) return _json({"success": True, "message": f"Dispositivo '{device_id}' actualizado."})
Partial Updates SupportedYou can update just the fields you need. Omitted fields retain their current values.
Each device gets its own threading lock to allow parallel operations:
source/server.py
# Initialize locks for all loaded devices_locks = {k: threading.Lock() for k in DEVICES}def get_lock(device_id): """Returns (creating if needed) the lock for a device.""" with devices_lock: if device_id not in _locks: _locks[device_id] = threading.Lock() return _locks[device_id]