diff --git a/ping/admin.py b/ping/admin.py index e43d25f202f05c77baf33d788c47ade50750775b..8a9827390482d2b4ce6e8bde7dd5195cf7917802 100644 --- a/ping/admin.py +++ b/ping/admin.py @@ -7,4 +7,5 @@ admin.site.register(Agent) admin.site.register(Experiment) admin.site.register(Metrics) admin.site.register(CurrentExperiment) -admin.site.register(Network) \ No newline at end of file +admin.site.register(Network) +admin.site.register(Link) \ No newline at end of file diff --git a/ping/consumers.py b/ping/consumers.py index 4b5a81a62c4a50ef9fe8cb427eba4f6c0f39f008..0e45079160e57d9b35806f7707f6c84152e3aa57 100644 --- a/ping/consumers.py +++ b/ping/consumers.py @@ -57,6 +57,42 @@ class CanvasConsumer(AsyncWebsocketConsumer): ) print("DISCONNECED CODE: ",code) + async def receive(self, text_data=None, bytes_data=None): + data = json.loads(text_data) + message = data['message'] + await self.channel_layer.group_send( + self.room_group_name,{ + "type": 'send_message_to_frontend', + "message": message + } + ) + async def send_message_to_frontend(self,event): + # Receive message from room group + message = event['message'] + # Send message to WebSocket + await self.send(text_data=json.dumps({ + 'message': message + })) + + +class ComConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.room_name = 'event' + self.room_group_name = self.room_name+"_com" + await self.channel_layer.group_add( + self.room_group_name, + self.channel_name + ) + print(self.room_group_name) + await self.accept() + + async def disconnect(self, code): + await self.channel_layer.group_discard( + self.room_group_name, + self.channel_name + ) + print("DISCONNECED CODE: ",code) + async def receive(self, text_data=None, bytes_data=None): data = json.loads(text_data) message = data['message'] diff --git a/ping/models.py b/ping/models.py index 58bc5f985890c04f22c16cb45ee8755c145d33f6..abec77261bd3d873244e04c60d66d710140c4754 100644 --- a/ping/models.py +++ b/ping/models.py @@ -61,6 +61,8 @@ class CurrentExperiment(models.Model): seed = models.CharField(default="0", max_length=64) scheduling = models.CharField(default="sync", max_length=10) + com_graph = models.BooleanField(default=False) + class Network(models.Model): broker_ip = models.CharField(max_length=60) @@ -70,3 +72,8 @@ class Network(models.Model): iotamak_core_version = models.CharField(max_length=60) path_to_iotamak = models.CharField(max_length=60) + +class Link(models.Model): + id1 = models.PositiveIntegerField(default=0) + id2 = models.PositiveIntegerField(default=0) + value = models.PositiveIntegerField(default=1) \ No newline at end of file diff --git a/ping/routing.py b/ping/routing.py index 758b3413366d99133dd7c9484cc6f9f5500de9fe..cc4f81c7d2864fe46cfef442b10572862aca5d64 100644 --- a/ping/routing.py +++ b/ping/routing.py @@ -6,4 +6,5 @@ from . import consumers websocket_urlpatterns = [ re_path("ws/canvas/", consumers.CanvasConsumer.as_asgi()), re_path("ws/graph/", consumers.GraphConsumer.as_asgi()), + re_path("ws/com/", consumers.ComConsumer.as_asgi()), ] \ No newline at end of file diff --git a/ping/templates/ping/play.html b/ping/templates/ping/play.html index 48d9d4b708c1f726d176d0f0d6d3916b5c09af93..294e493af869af4e1ebdfcd1c18d9d735ede7e79 100644 --- a/ping/templates/ping/play.html +++ b/ping/templates/ping/play.html @@ -4,6 +4,8 @@ <head> <meta charset="utf-8"> {% load static %} +<!-- Load d3.js --> +<script src="https://d3js.org/d3.v7.min.js"></script> <link rel="stylesheet" type="text/css" href="{% static 'ping/index.css' %}"> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js" @@ -14,6 +16,25 @@ <title>Play - IOTAMAK</title> <meta name="viewport" content="width=device-width, initial-scale=1"> + +<style> + +.links line { + stroke: #999; + stroke-opacity: 0.6; +} + +.nodes circle { + stroke: #fff; + stroke-width: 5px; +} + +text { + font-family: sans-serif; + font-size: 15px; +} + +</style> </head> <body> @@ -105,6 +126,7 @@ {% endif %} + {% if canvas %} <canvas id="line-chart" width="800" height="450"></canvas> <script> @@ -144,6 +166,87 @@ const myChart = new Chart(document.getElementById("line-chart"), { }; </script> + {% endif %} + + +<svg width="960" height="600"></svg> +<script> + +var graph = { + "nodes": [ + {"id": "Agent_0", "group": 1}, + {"id": "Agent_1", "group": 2}, + {"id": "Agent_2", "group": 1}, + {"id": "Agent_3", "group": 3} + ], + "links": [] +}; + + + + +var svg = d3.select("svg"), + width = +svg.attr("width"), + height = +svg.attr("height"); + +var color = d3.scaleOrdinal(d3.schemeCategory20); + +var simulation = d3.forceSimulation() + .force("link", d3.forceLink().id(function(d) { return d.id; })) + .force("charge", d3.forceManyBody().strength(-2500)) + .force("center", d3.forceCenter(width / 2, height / 2)); + + var link = svg.append("g").attr("class", "links").selectAll("line") + .data(graph.links).enter().append("line") + .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); + + var node = svg.append("g") + .attr("class", "nodes") + .selectAll("g") + .data(graph.nodes) + .enter().append("g") + + var circles = node.append("circle") + .attr("r", 20) + .attr("fill", function(d) { return color(d.group); }); + + var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13); + + node.append("title").text(function(d) { return d.id; }); + + simulation.nodes(graph.nodes).on("tick", ticked); + simulation.force("link").links(graph.links); + + function ticked() { + link + .attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + node + .attr("transform", function(d) { + return "translate(" + d.x + "," + d.y + ")"; + }) + }; + +const comSocket = new WebSocket( + 'ws://' + + window.location.host + + '/ws/com/' +); + +comSocket.onmessage = function (e) { + graph = JSON.parse(e.data).message; + console.log(graph); + + //simulation.nodes(graph.nodes); + simulation.force("link").links(graph.links); + simulation.alpha(1).restart(); + }; +</script> + + </body> </html> \ No newline at end of file diff --git a/ping/views/play.py b/ping/views/play.py index 046b2aa38bcc763b3f07f26e74f02f3a2bee9aa9..4018f9bdaa5e6c65dcd5f5490860e3803b4628b6 100644 --- a/ping/views/play.py +++ b/ping/views/play.py @@ -16,8 +16,8 @@ from django.urls import reverse from iotAmak.tool.ssh_client import Cmd from paho.mqtt.client import Client as MQTTClient -from .tool import delete_folder, get_ssh_client, canvas_event_triger, graph_event_triger -from ..models import Experiment, CurrentExperiment, Metrics, Network +from .tool import delete_folder, get_ssh_client, canvas_event_triger, graph_event_triger, com_event_triger +from ..models import Experiment, CurrentExperiment, Metrics, Network, Link client = MQTTClient(client_id="django-ihm") @@ -34,6 +34,18 @@ def agent_log(client, userdata, message) -> None: print("[Log] " + str(message.payload.decode("utf-8")) + " on topic " + message.topic) +def agent_message(client, userdata, message) -> None: + id1 = literal_eval(message.payload.decode("utf-8")).get("id") + id2 = int(message.topic.split("/")[1]) + + if id1 > id2: + id1, id2 = id2, id1 + + link = Link.objects.filter(id1=id1, id2=id2)[0] + link.value += 1 + link.save() + + def agent_metric(client, userdata, message) -> None: results = literal_eval(message.payload.decode("utf-8")) @@ -56,15 +68,39 @@ def agent_metric(client, userdata, message) -> None: data += 1 graph_event_triger([data, exp.cycle]) + def cycle_done(client, userdata, message) -> None: cur_exp = CurrentExperiment.objects.all()[0] cur_exp.cycle += 1 cur_exp.save() + data = { + "nodes": [ + {"id": "Agent_0", "group": 1}, + {"id": "Agent_1", "group": 2}, + {"id": "Agent_2", "group": 1}, + {"id": "Agent_3", "group": 3} + ], + "links": [ + { + "source": "Agent_"+str(elem.id1), + "target": "Agent_"+str(elem.id2), + "value": elem.value + } + for elem in Link.objects.all() + ] + } + com_event_triger(str(data)) + def update_nbr_agent(client, userdata, message) -> None: exp = CurrentExperiment.objects.all()[0] - subscribe(client, "agent/"+str(exp.nbr_agent)+"/log", agent_log) + subscribe(client, "agent/" + str(exp.nbr_agent) + "/log", agent_log) + if exp.com_graph: + subscribe(client, "agent/" + str(exp.nbr_agent) + "/mail", agent_message) + for i in range(exp.nbr_agent): + link = Link(id1=i, id2=exp.nbr_agent) + link.save() exp.nbr_agent += 1 exp.save() @@ -84,14 +120,18 @@ def experiment_load(request): CurrentExperiment.objects.all().delete() cur_exp = CurrentExperiment(name=Experiment.objects.get(status="Selected").name) - with open(str(settings.MEDIA_ROOT) + "current_experiment/"+str(exp.name)+"/config.json") as json_file: + with open(str(settings.MEDIA_ROOT) + "current_experiment/" + str(exp.name) + "/config.json") as json_file: config = json.load(json_file) if "seed" in config: cur_exp.seed = str(config.get("seed")) else: random.seed() - cur_exp.seed = str(random.randint(0,9999999)) + cur_exp.seed = str(random.randint(0, 9999999)) + + if "com_graph" in config: + cur_exp.com_graph = True + Link.objects.all().delete() cur_exp.scheduling = config.get("scheduling_type") @@ -99,6 +139,7 @@ def experiment_load(request): return HttpResponseRedirect(reverse('ping:play')) + def delete_nohup(): ssh = get_ssh_client() @@ -112,6 +153,7 @@ def delete_nohup(): print(" Ip : ", ssh.clients[i_client].hostname) ssh.run_cmd(i_client, commands) + def get_nohup(): ssh = get_ssh_client() @@ -125,6 +167,7 @@ def get_nohup(): print(" Ip : ", ssh.clients[i_client].hostname) ssh.run_cmd(i_client, commands) + def experiment_start(request): n = Network.objects.all()[0] ssh = get_ssh_client() @@ -157,9 +200,9 @@ def experiment_start(request): "broker_ip": n.broker_ip, "clients": str([c.to_send() for c in ssh.clients]), "seed": exp.seed, - "iot_path": n.path_to_iotamak+"example/", + "iot_path": n.path_to_iotamak + "example/", "broker_username": n.broker_username, - "broker_password": n.broker_password + "broker_password": n.broker_password } if not exp.scheduling == "sync": amas_dict["wait_delay"] = str(5) @@ -169,7 +212,7 @@ def experiment_start(request): "broker_ip": n.broker_ip, "seed": exp.seed, "broker_username": n.broker_username, - "broker_password": n.broker_password + "broker_password": n.broker_password } if not exp.scheduling == "sync": env_dict["wait_delay"] = str(5) @@ -285,12 +328,13 @@ def experiment_metric(request, metrics_id): for metric in raw_metrics[0].metrics if metric != {}] return JsonResponse({'data': cycle_metrics}) + def export_csv(request): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="users.csv"' writer = csv.writer(response) - writer.writerow(['cycle'] + ["Agent_"+key for key in Metrics.objects.order_by('?').first().metrics[0].keys()]) + writer.writerow(['cycle'] + ["Agent_" + key for key in Metrics.objects.order_by('?').first().metrics[0].keys()]) for m in Metrics.objects.all(): cycle = m.cycle @@ -299,4 +343,4 @@ def export_csv(request): if metric != {}: writer.writerow([cycle] + [value for value in metric.values()]) - return response \ No newline at end of file + return response diff --git a/ping/views/tool.py b/ping/views/tool.py index 5eb763ee76780a098187122a3199e1a88f698685..35036dc00cdf49ab55e1d92b283295f0b46ecbac 100644 --- a/ping/views/tool.py +++ b/ping/views/tool.py @@ -55,3 +55,13 @@ def graph_event_triger(metrics): 'message': metrics } ) + +def com_event_triger(data): + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + 'event_com', + { + 'type': 'send_message_to_frontend', + 'message': data + } + ) \ No newline at end of file