# orchestrator_and_agent.py with Dashboard # to run: # # python media_node.py --role agent --name upstairs_pi # python media_node.py --role orchestrator --name orchestrator01 import json import socket import subprocess import threading import time from flask import Flask, request, jsonify, render_template_string import paho.mqtt.client as mqtt import argparse parser = argparse.ArgumentParser() parser.add_argument("--role", choices=["agent", "orchestrator"], default="agent") parser.add_argument("--name", default="unnamed_node") args = parser.parse_args() NODE_NAME = args.name is_orchestrator = args.role == "orchestrator" # Configuration MQTT_BROKER = "192.168.1.70" MQTT_PORT = 1883 config = json.loads( open('config.json','r').read()) # todo config['node_name'] = NODE_NAME player_proc = None nodes = {} app = Flask(__name__) client = mqtt.Client() # ---------------------- AGENT FUNCTIONS ---------------------- def run_player(data): global player_proc try: if player_proc and player_proc.poll() is None: player_proc.terminate() player_proc.wait() cmd = [data.get("player", "mpv"), data.get("resolved_url") or data.get("media")] cmd += data.get("options", []) print("Launching:", " ".join(cmd)) player_proc = subprocess.Popen(cmd) publish_status("playing", data.get("media")) except Exception as e: publish_status("error", str(e)) def stop_player(): global player_proc if player_proc and player_proc.poll() is None: player_proc.terminate() player_proc.wait() publish_status("stopped", None) def publish_status(state, media): payload = json.dumps({"state": state, "media": media, "node": config['node_name']}) client.publish(config['status_topic'], payload) def send_heartbeat(): while True: ip = socket.gethostbyname(socket.gethostname()) msg = { "node": config['node_name'], "ip": ip, "location": config['location'], "capabilities": config['capabilities'], "timestamp": time.time() } client.publish(config['heartbeat_topic'], json.dumps(msg)) time.sleep(10) # ---------------------- ORCHESTRATOR API ---------------------- @app.route("/play", methods=["POST"]) def play(): data = request.json media = data.get("media") target = data.get("target") if not media or not target: return jsonify({"error": "Missing 'media' or 'target'"}), 400 msg = { "command": "PLAYBACK", "media": media, "resolved_url": data.get("url", media), "player": data.get("player", "mpv"), "options": data.get("options", ["--fs"]) } topic = f"media/commands/{target}/playback" client.publish(topic, json.dumps(msg)) return jsonify({"status": "sent", "target": target}) @app.route("/stop", methods=["POST"]) def stop(): data = request.json target = data.get("target") if not target: return jsonify({"error": "Missing 'target'"}), 400 msg = {"command": "STOP"} topic = f"media/commands/{target}/stop" client.publish(topic, json.dumps(msg)) return jsonify({"status": "sent", "target": target}) @app.route("/nodes", methods=["GET"]) def get_nodes(): return jsonify(nodes) @app.route("/") def dashboard(): html = '''
| Node | IP | Location | Capabilities | Last Seen |
|---|---|---|---|---|
| {{ node }} | {{ data.ip }} | {{ data.location }} | {{ ', '.join(data.capabilities) }} | {{ data.timestamp | int }} |