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