diff --git a/iotamak_ihm/asgi.py b/iotamak_ihm/asgi.py index ff61e515729534e407504192315729ef78e4d527..7cc1395280724f39318e45e0edc126763e176a8d 100644 --- a/iotamak_ihm/asgi.py +++ b/iotamak_ihm/asgi.py @@ -1,16 +1,24 @@ -""" -ASGI config for iotamak_ihm project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ -""" - +# mysite/asgi.py import os +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.security.websocket import AllowedHostsOriginValidator from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'iotamak_ihm.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "iotamak_ihm.settings") +# Initialize Django ASGI application early to ensure the AppRegistry +# is populated before importing code that may import ORM models. + +import ping.routing -application = get_asgi_application() +application = ProtocolTypeRouter({ + "http": get_asgi_application(), + "websocket": + AuthMiddlewareStack( + URLRouter( + ping.routing.websocket_urlpatterns + ) + ) + , +}) diff --git a/iotamak_ihm/settings.py b/iotamak_ihm/settings.py index 3f506acfe0eb5c66886aaf1ac7acf80690289250..e16d92c172cf5b31792755c6991f6367c4e56f36 100644 --- a/iotamak_ihm/settings.py +++ b/iotamak_ihm/settings.py @@ -32,13 +32,14 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads/') # Application definition INSTALLED_APPS = [ - 'ping.apps.PingConfig', + 'ping', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'channels', ] MIDDLEWARE = [ @@ -70,6 +71,15 @@ TEMPLATES = [ ] WSGI_APPLICATION = 'iotamak_ihm.wsgi.application' +ASGI_APPLICATION = 'iotamak_ihm.asgi.application' +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': { + "hosts": [('127.0.0.1', 6379)], + }, + }, +} # Database diff --git a/ping/consumers.py b/ping/consumers.py new file mode 100644 index 0000000000000000000000000000000000000000..4b5a81a62c4a50ef9fe8cb427eba4f6c0f39f008 --- /dev/null +++ b/ping/consumers.py @@ -0,0 +1,75 @@ +from channels.generic.websocket import AsyncWebsocketConsumer + + +import json + +class GraphConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.room_name = 'event' + self.room_group_name = self.room_name+"_graph" + 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'] + 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 CanvasConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.room_name = 'event' + self.room_group_name = self.room_name+"_canvas" + 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'] + 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 + })) \ No newline at end of file diff --git a/ping/routing.py b/ping/routing.py new file mode 100644 index 0000000000000000000000000000000000000000..758b3413366d99133dd7c9484cc6f9f5500de9fe --- /dev/null +++ b/ping/routing.py @@ -0,0 +1,9 @@ +# chat/routing.py +from django.urls import re_path + +from . import consumers + +websocket_urlpatterns = [ + re_path("ws/canvas/", consumers.CanvasConsumer.as_asgi()), + re_path("ws/graph/", consumers.GraphConsumer.as_asgi()), +] \ No newline at end of file diff --git a/ping/templates/ping/play.html b/ping/templates/ping/play.html index 48d9d96cd2276bb1d4560fa5151bde2f8fac12db..058ca30906118a256d5aed5617519c160ed3b049 100644 --- a/ping/templates/ping/play.html +++ b/ping/templates/ping/play.html @@ -5,65 +5,15 @@ <meta charset="utf-8"> {% load static %} <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" + integrity="sha512-QSkVNOCYLtj73J4hbmVoOV6KVZuMluZlioC+trLpewV8qMjsWqlIQvkn1KGX2StWvPMdWGBqim1xlC8krl1EKQ==" + crossorigin="anonymous" + referrerpolicy="no-referrer" + ></script> <title>Play - IOTAMAK</title> <meta name="viewport" content="width=device-width, initial-scale=1"> - - <script type="text/javascript"> - window.addEventListener('load', eventWindowLoaded, false); - function eventWindowLoaded() { - canvasApp(); - } - - function canvasApp() { - - var cycle = 1; - - theCanvas = document.getElementById('canvasOne'); - context = theCanvas.getContext('2d'); - - function erraseCanvas() { - context.clearRect(0, 0, theCanvas.width, theCanvas.height); - } - - function drawScreen(metrics) { - erraseCanvas(); - - var count = metrics.length; - for (let id_agent = 0; id_agent < count; id_agent++) { - if ( metrics[id_agent][2] == 0){ - context.fillStyle = "#000000"; - } - else { - context.fillStyle = "#e00000"; - } - context.beginPath(); - context.arc(metrics[id_agent][0], metrics[id_agent][1], 1, 0, Math.PI * 2, true); - context.closePath(); - context.fill(); - } - } - - async function gameLoop() { - let data = await fetch("./metric/".concat("", cycle.toString().concat("", "/"))) - .then((response) => response.json()) - .then(data => {return data.data;}) - while (data.length == 0){ - await new Promise(r => setTimeout(r, 1000)); - data = await fetch("./metric/".concat("", cycle.toString().concat("", "/"))) - .then((response) => response.json()) - .then(data => {return data.data;}) - } - drawScreen(data) - cycle = cycle + 1 - window.setTimeout(gameLoop, 20); - } - - gameLoop(); - } - - </script> - </head> <body> @@ -113,12 +63,86 @@ <form action='./export_csv/' method='GET'> <button type='submit'> Export all metrics</button> </form> + {% if canvas %} - <canvas id="canvasOne" width="{{ canvas.0 }}" height="{{ canvas.1 }}" style="border:1px solid #000000;"> - Your browser does not support the HTML 5 Canvas. - </canvas> + <canvas id="canvasOne" width="{{ canvas.0 }}" height="{{ canvas.1 }}" style="border:1px solid #000000;"> + Your browser does not support the HTML 5 Canvas. + </canvas> + <script type="text/javascript"> + + + const chatSocket = new WebSocket( + 'ws://' + + window.location.host + + '/ws/canvas/' + ); + + theCanvas = document.getElementById('canvasOne'); + context = theCanvas.getContext('2d'); + + function erraseCanvas() { + context.clearRect(0, 0, theCanvas.width, theCanvas.height); + } + + chatSocket.onmessage = function (e) { + const metrics = JSON.parse(e.data).message; + erraseCanvas(); + + var count = metrics.length; + for (let id_agent = 0; id_agent < count; id_agent++) { + context.fillStyle = metrics[id_agent][2]; + context.beginPath(); + context.arc(metrics[id_agent][0], metrics[id_agent][1], 1, 0, Math.PI * 2, true); + context.closePath(); + context.fill(); + }; + }; + + chatSocket.onclose = function (e) { + console.error('Chat socket closed unexpectedly'); + }; + </script> {% endif %} + +<canvas id="myChart" width="500" height="500" style="border:1px solid #000000;"></canvas> +<script> + +var chart_data = { + labels: ["0", "1", "2", "3"], + type: 'line', + data: { + datasets: [{ + data: [0,0,0,0] + }] + } +}; + +const ctx = document.getElementById('myChart').getContext('2d'); +const myChart = new Chart(ctx, chart_data); + + + const graphSocket = new WebSocket( + 'ws://' + + window.location.host + + '/ws/graph/' + ); + + graphSocket.onmessage = function (e) { + const metrics = JSON.parse(e.data).message; + console.log(metrics); + new_graph_data = chart_data.data.datasets[0].data; + new_graph_data.shift(); + new_graph_data.push(metrics[0]); + console.log(new_graph_data); + + chart_data.data.datasets[0].data = new_graph_data; + console.log(new_graph_data); + + myChart.update(); + + }; +</script> </body> </html> \ No newline at end of file diff --git a/ping/views/play.py b/ping/views/play.py index 8cc116978b537395f2c7fe40792c82abf07f54db..f987cbcc5f99ed1cbabf8b5313e6e6422044c270 100644 --- a/ping/views/play.py +++ b/ping/views/play.py @@ -15,7 +15,7 @@ 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 +from .tool import delete_folder, get_ssh_client, canvas_event_triger, graph_event_triger from ..models import Experiment, CurrentExperiment, Metrics client = MQTTClient(client_id="django-ihm") @@ -37,9 +37,23 @@ def agent_metric(client, userdata, message) -> None: results = literal_eval(message.payload.decode("utf-8")) exp = CurrentExperiment.objects.all()[0] - metric = Metrics(cycle=exp.cycle, metrics=results) - metric.save() - + metrics = Metrics(cycle=exp.cycle, metrics=results) + metrics.save() + + # if canvas + cycle_metrics = [ + [metric.get("x"), metric.get("y"), metric.get("color")] + for metric in results if metric != {}] + canvas_event_triger(cycle_metrics) + + # if graph + data = 0 + for metric in results: + if metric == {}: + pass + elif metric.get("color") != "#000000": + data += 1 + graph_event_triger([data, exp.cycle]) def cycle_done(client, userdata, message) -> None: cur_exp = CurrentExperiment.objects.all()[0] @@ -73,7 +87,7 @@ def experiment_load(request): def experiment_start(request): - broker_ip = "192.168.124.209" + broker_ip = "192.168.193.209" ssh = get_ssh_client() experiment_path = str(settings.MEDIA_ROOT) + "current_experiment/" + Experiment.objects.get(status="Selected").name diff --git a/ping/views/tool.py b/ping/views/tool.py index 172d5f4b0ca30f229fb3b4960340f7e9f3fcc15e..fa938597057ba9fe708cd2c20b6a25417fccac87 100644 --- a/ping/views/tool.py +++ b/ping/views/tool.py @@ -1,6 +1,8 @@ import os import shutil +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer from iotAmak.tool.remote_client import RemoteClient from iotAmak.tool.ssh_client import SSHClient @@ -27,4 +29,25 @@ def get_remote_client(): return res def get_ssh_client(): - return SSHClient(get_remote_client()) \ No newline at end of file + return SSHClient(get_remote_client()) + + +def canvas_event_triger(metrics): + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + 'event_canvas', + { + 'type': 'send_message_to_frontend', + 'message': metrics + } + ) + +def graph_event_triger(metrics): + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + 'event_graph', + { + 'type': 'send_message_to_frontend', + 'message': metrics + } + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..290785cc1291fbc4564ea13de31fdfe29ebda52e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Django >= 4.0.4 +channels >= 3.0.4 +channels-redis >= 3.4.0