diff --git a/db.sqlite3 b/db.sqlite3 index e33654f60a42bd1bdaf59e30dc8587fc89bd53be..8db1a619382a48f53c27a90a51d809a2091ac1f2 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/ping/admin.py b/ping/admin.py index f8dd658921a7e457d2651b11af8b8e020a498a0b..4cadb755cdf11d8b98d1f1efa56f043e7f3fb45d 100644 --- a/ping/admin.py +++ b/ping/admin.py @@ -1,7 +1,9 @@ from django.contrib import admin -from .models import Client, Agent, Experiment +from .models import * admin.site.register(Client) admin.site.register(Agent) -admin.site.register(Experiment) \ No newline at end of file +admin.site.register(Experiment) +admin.site.register(Metrics) +admin.site.register(CurrentExperiment) \ No newline at end of file diff --git a/ping/migrations/0003_currentexperiment_metrics_alter_experiment_media.py b/ping/migrations/0003_currentexperiment_metrics_alter_experiment_media.py new file mode 100644 index 0000000000000000000000000000000000000000..d014e25f9508d4c05a52c3c3bb4038f585fa4c7a --- /dev/null +++ b/ping/migrations/0003_currentexperiment_metrics_alter_experiment_media.py @@ -0,0 +1,37 @@ +# Generated by Django 4.0.4 on 2022-05-13 12:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ping', '0002_experiment'), + ] + + operations = [ + migrations.CreateModel( + name='CurrentExperiment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=60)), + ('cycle', models.PositiveIntegerField(default=0)), + ('nbr_agent', models.PositiveIntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Metrics', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('experiment_name', models.CharField(max_length=60)), + ('cycle', models.PositiveIntegerField()), + ('agent_id', models.PositiveIntegerField()), + ('_metrics', models.BinaryField()), + ], + ), + migrations.AlterField( + model_name='experiment', + name='media', + field=models.FileField(blank=True, null=True, upload_to='media'), + ), + ] diff --git a/ping/models.py b/ping/models.py index c6a47a184abdd614fc87b20e17fb3f7a3a709b3a..ecc17795aad954629623f58144890dc7fdfbd6b5 100644 --- a/ping/models.py +++ b/ping/models.py @@ -1,3 +1,5 @@ +import pickle + from django.db import models from django.forms import ModelForm @@ -30,3 +32,24 @@ class ExperimentForm(ModelForm): class Meta: model = Experiment fields = ["name", "description", "media"] + +class Metrics(models.Model): + experiment_name = models.CharField(max_length=60) + cycle = models.PositiveIntegerField() + agent_id = models.PositiveIntegerField() + _metrics = models.BinaryField() + + def set_data(self, data): + self._metrics = pickle.dumps(data) + + def get_data(self): + return pickle.loads(self._metrics) + + metrics = property(get_data, set_data) + + +class CurrentExperiment(models.Model): + + name = models.CharField(max_length=60) + cycle = models.PositiveIntegerField(default=0) + nbr_agent = models.PositiveIntegerField(default=0) \ No newline at end of file diff --git a/ping/templates/ping/entry.html b/ping/templates/ping/entry.html index b718dd690422ca999abb822edda5e42a6a8dd29b..a46a985dac76700a26dcc23084cd7ac1c21b24f9 100644 --- a/ping/templates/ping/entry.html +++ b/ping/templates/ping/entry.html @@ -16,6 +16,7 @@ <div class="topnav"> <a href="/network">Network</a> <a class="active" href="/experiment">Experiment</a> + <a href="/play">Play</a> </div> <form action="/experiment/new/" method="POST" enctype="multipart/form-data"> {% csrf_token %} diff --git a/ping/templates/ping/experiment.html b/ping/templates/ping/experiment.html index 86454db688d053b9a18b89d3b7fb044bf907711c..e725a128fed66979a861d03286772ffa7fa3cf80 100644 --- a/ping/templates/ping/experiment.html +++ b/ping/templates/ping/experiment.html @@ -16,6 +16,7 @@ <div class="topnav"> <a href="/network">Network</a> <a class="active" href="/experiment">Experiment</a> + <a href="/play">Play</a> </div> <form action='new' method='GET'> diff --git a/ping/templates/ping/index.html b/ping/templates/ping/index.html index e39a748b2828a0220baaa7799f4de10fdaef5575..4e63881224eaf9e740978e16f18b2350aef391ab 100644 --- a/ping/templates/ping/index.html +++ b/ping/templates/ping/index.html @@ -15,6 +15,7 @@ <div class="topnav"> <a class="active" href="/network">Network</a> <a href="/experiment">Experiment</a> + <a href="/play">Play</a> </div> @@ -46,6 +47,10 @@ {% endfor %} </table> + <form action='./agent/kill' method='GET'> + <button type='submit'> Kill agents</button> + </form> + {% if agents %} <table> <tr> diff --git a/ping/templates/ping/play.html b/ping/templates/ping/play.html new file mode 100644 index 0000000000000000000000000000000000000000..2b9de00b064390b137da66fe1291e9c902256fd3 --- /dev/null +++ b/ping/templates/ping/play.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + {% load static %} + <link rel="stylesheet" type="text/css" href="{% static 'ping/index.css' %}"> + + <title>Play - IOTAMAK</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + +</head> + +<body> + + <div class="topnav"> + <a href="/network">Network</a> + <a href="/experiment">Experiment</a> + <a class="active" href="/play">Play</a> + </div> + + <p>Scheduler :</p> + + <form action='./scheduler/step' method='GET'> + <button type='submit'> Step</button> + </form> + + <form action='./scheduler/stop' method='GET'> + <button type='submit'> Stop</button> + </form> + + <form action='./scheduler/start' method='GET'> + <button type='submit'> Start</button> + </form> + + <p>Experiment :</p> + + <form action='./global/exit' method='GET'> + <button type='submit'> Exit</button> + </form> + + <form action='./global/share' method='GET'> + <button type='submit'> Share</button> + </form> + + <form action='./global/start' method='GET'> + <button type='submit'> Start</button> + </form> + + <form action='./global/load' method='GET'> + <button type='submit'> Load</button> + </form> + +</body> +</html> \ No newline at end of file diff --git a/ping/urls.py b/ping/urls.py index f7f30028c5c1823c88de037886b4b053f0628569..133b8fbff2a66971b9292a6bb3193c535437f0bf 100644 --- a/ping/urls.py +++ b/ping/urls.py @@ -8,7 +8,16 @@ urlpatterns = [ path('network/pressed/', views.pressed, name='pressed'), path('network/update/', views.update, name='update'), path('network/agents/', views.agents, name='agents'), + path('network/agent/kill', views.kill, name='kill'), path('experiment/new/', views.entry, name='entry'), path('experiment/', views.experiment, name='experiment'), path('experiment/<int:experiment_id>/check/', views.check, name='check'), + path('play/', views.main_play, name='play'), + path('play/scheduler/start', views.scheduler_start, name='start'), + path('play/scheduler/step', views.scheduler_step, name='step'), + path('play/scheduler/stop', views.scheduler_stop, name='stop'), + path('play/global/exit', views.experiment_stop, name='exit'), + path('play/global/share', views.experiment_share, name='share'), + path('play/global/start', views.experiment_start, name='start_exp'), + path('play/global/load', views.experiment_load, name='load_exp'), ] \ No newline at end of file diff --git a/ping/views/__init__.py b/ping/views/__init__.py index 2b38d03c969c294b0845027299537c3b93e7abcf..c041ce84eedd648974daaeda69e9a8946842d31c 100644 --- a/ping/views/__init__.py +++ b/ping/views/__init__.py @@ -1,2 +1,3 @@ from .experiment import * from .network import * +from .play import * \ No newline at end of file diff --git a/ping/views/experiment.py b/ping/views/experiment.py index 4ba32dce132a779c726a09d3aa83e9ab98c4d721..ca83afb55b19a44481fd2e518ec02df151427151 100644 --- a/ping/views/experiment.py +++ b/ping/views/experiment.py @@ -41,12 +41,8 @@ def empty_tmp(): def check(request, experiment_id): # get the path - print(experiment_id) exp = Experiment.objects.get(pk=experiment_id) - print(str(exp.media)) - print(str(settings.MEDIA_ROOT) + str(exp.media)) # check if it's a zip -> wrong format : not zip - print() if not zipfile.is_zipfile(str(settings.MEDIA_ROOT) + str(exp.media)): exp.status = "Wrong format : zip file expected" diff --git a/ping/views/network.py b/ping/views/network.py index 84e9734db5886687e08b8539042aec4bad3b5f9c..5a13bc786bd60451425f50eba978fc0257d2b894 100644 --- a/ping/views/network.py +++ b/ping/views/network.py @@ -41,14 +41,7 @@ def update(request): ), Cmd( cmd="python3 -m pip install --force-reinstall dist/iotAmak-" + version + "-py3-none-any.whl" - ), - Cmd( - cmd="cd ../../../" - ) # , - # Cmd( - # cmd="rm -r Desktop/mqtt_goyon/example/" + self.experiment_name - # ) - + ) ] for i_client in range(len(ssh.clients)): print("Hostname :", ssh.clients[i_client].hostname, " User :", ssh.clients[i_client].user) @@ -76,6 +69,19 @@ def agents(request): return HttpResponseRedirect(reverse('ping:index')) +def kill(request): + ssh = get_ssh_client() + + commands = [ + Cmd( + cmd="for pid in $(ps -ef | grep 'python ' | awk '{print $2}'); do kill $pid; done", + do_print=False + )] + for i_client in range(len(ssh.clients)): + ssh.run_cmd(i_client, commands) + return HttpResponseRedirect(reverse('ping:index')) + + def index(request): template = loader.get_template('ping/index.html') diff --git a/ping/views/play.py b/ping/views/play.py new file mode 100644 index 0000000000000000000000000000000000000000..c7e0a72fbf80d7cb3f485ae85442bb10f1d632bc --- /dev/null +++ b/ping/views/play.py @@ -0,0 +1,149 @@ +import os +import shutil +import sys +import zipfile +from ast import literal_eval +from subprocess import Popen +from time import sleep + +from django.conf import settings +from django.http import HttpResponse, HttpResponseRedirect +from django.template import loader +from django.urls import reverse +from iotAmak.tool.ssh_client import Cmd +from paho.mqtt.client import Client as MQTTClient + +from ..models import Experiment, CurrentExperiment, Metrics + +from .network import get_ssh_client + +client = MQTTClient(client_id="django-ihm") + +def subscribe(mqttclient, topic, fun): + mqttclient.subscribe(topic) + mqttclient.message_callback_add(topic, fun) + + +def agent_log(client, userdata, message) -> None: + """ + Called when the amas receive a log from any agent, print it in stdout + """ + print("[Log] " + str(message.payload.decode("utf-8")) + " on topic " + message.topic) + + +def agent_metric(client, userdata, message) -> None: + result = literal_eval(message.payload.decode("utf-8")) + agent_id = result.get("id") + + exp = CurrentExperiment.objects.all()[0] + + metric = Metrics(experiment_name=exp.name, cycle=exp.cycle, agent_id=agent_id, metrics=result) + metric.save() + + +def update_nbr_agent(client, userdata, message) -> None: + exp = CurrentExperiment.objects.all()[0] + subscribe(client, "agent/"+str(exp.nbr_agent)+"/metric", agent_metric) + exp.nbr_agent += 1 + exp.save() + +def empty_current_experiment(): + folder = str(settings.MEDIA_ROOT) + "current_experiment" + for filename in os.listdir(folder): + file_path = os.path.join(folder, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print('Failed to delete %s. Reason: %s' % (file_path, e)) + +def experiment_load(request): + + empty_current_experiment() + + exp = Experiment.objects.get(status="Selected") + + with zipfile.ZipFile(str(settings.MEDIA_ROOT) + str(exp.media), 'r') as zip_ref: + zip_ref.extractall(str(settings.MEDIA_ROOT) + "current_experiment") + + # drop metrics table + Metrics.objects.all().delete() + + # drop current experiment table and create new instance + CurrentExperiment.objects.all().delete() + cur_exp = CurrentExperiment(name=Experiment.objects.get(status="Selected").name) + cur_exp.save() + + return HttpResponseRedirect(reverse('ping:play')) + +def experiment_start(request): + + broker_ip = "192.168.201.209" + ssh = get_ssh_client() + + experiment_path = str(settings.MEDIA_ROOT) + "current_experiment/" + Experiment.objects.get(status="Selected").name + + # Connect to the broker + client.username_pw_set(username="goyon", password="mosquitto") + client.connect(host=broker_ip) + client.loop_start() + subscribe(client, "amas/agent/new", update_nbr_agent) + + # start subprocess scheduler + p1 = Popen([sys.executable, experiment_path+'/scheduler.py', broker_ip]) + sleep(1) + # start subprocess amas + send_client = [c.to_send() for c in ssh.clients] + p2 = Popen([sys.executable, experiment_path+'/amas.py', broker_ip, str(send_client)]) + # start subprocess env + p3 = Popen([sys.executable, experiment_path+'/env.py', broker_ip]) + return HttpResponseRedirect(reverse('ping:play')) + + +def main_play(request): + template = loader.get_template('ping/play.html') + context = {} + return HttpResponse(template.render(context, request)) + + +def scheduler_step(request): + client.publish("ihm/step") + return HttpResponseRedirect(reverse('ping:play')) + + +def scheduler_stop(request): + client.publish("ihm/pause") + return HttpResponseRedirect(reverse('ping:play')) + + +def scheduler_start(request): + client.publish("ihm/unpause") + return HttpResponseRedirect(reverse('ping:play')) + +def experiment_share(request): + # get selected experiment + exp = Experiment.objects.get(status="Selected") + + # open ssh connection + ssh = get_ssh_client() + + # file transfer + commands = [ + Cmd( + cmd="rm -r Desktop/mqtt_goyon/example/" + exp.name + ) + + ] + for i_client in range(len(ssh.clients)): + ssh.run_cmd(i_client, commands) + + ssh.update(exp.name, str(settings.MEDIA_ROOT) + "current_experiment/" + exp.name) + + return HttpResponseRedirect(reverse('ping:play')) + +def experiment_stop(request): + client.publish("ihm/exit") + client.publish("ihm/step") + return HttpResponseRedirect(reverse('ping:play')) diff --git a/uploads/current_experiment/ant/__init__.py b/uploads/current_experiment/ant/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uploads/current_experiment/ant/agent.py b/uploads/current_experiment/ant/agent.py new file mode 100644 index 0000000000000000000000000000000000000000..5accea62e91b4885d8fb8b4acc49424b2ab0ff02 --- /dev/null +++ b/uploads/current_experiment/ant/agent.py @@ -0,0 +1,28 @@ +import random +import sys + +from iotAmak.agent import Agent + + +class Ant(Agent): + + def __init__(self, identifier: int, broker_ip: str): + self.x = 0 + self.y = 0 + super().__init__(identifier, broker_ip) + + def on_act(self) -> None: + self.x += random.randint(-5, +5) + self.y += random.randint(-5, +5) + self.log("X : "+str(self.x)+" Y : "+str(self.y)) + + def send_metric(self): + metric = super(Ant, self).send_metric() + + metric["x"] = self.x + metric["y"] = self.y + return metric + +if __name__ == '__main__': + a = Ant(int(sys.argv[1]), str(sys.argv[2])) + a.run() \ No newline at end of file diff --git a/uploads/current_experiment/ant/amas.py b/uploads/current_experiment/ant/amas.py new file mode 100644 index 0000000000000000000000000000000000000000..99a0586c10c2185bf7ed813e58b6ee83c7934747 --- /dev/null +++ b/uploads/current_experiment/ant/amas.py @@ -0,0 +1,20 @@ +import sys + + +from iotAmak.amas import Amas + + +class AntAmas(Amas): + + def __init__(self, broker_ip: str, clients, nbr_agent): + self.agent_to_create = nbr_agent + super().__init__(broker_ip, clients) + + def on_initial_agents_creation(self): + for _ in range(self.agent_to_create): + self.add_agent("ant") + + +if __name__ == '__main__': + s = AntAmas(str(sys.argv[1]), sys.argv[2], 5) + s.run() diff --git a/uploads/current_experiment/ant/env.py b/uploads/current_experiment/ant/env.py new file mode 100644 index 0000000000000000000000000000000000000000..a4f1c425069ef147cbc87de425cd47ea57910ab0 --- /dev/null +++ b/uploads/current_experiment/ant/env.py @@ -0,0 +1,15 @@ +import sys + + +from iotAmak.environment import Environment + + +class AntEnv(Environment): + + def __init__(self, broker_ip): + super().__init__(broker_ip) + + +if __name__ == '__main__': + s = AntEnv(str(sys.argv[1])) + s.run() \ No newline at end of file diff --git a/uploads/current_experiment/ant/scheduler.py b/uploads/current_experiment/ant/scheduler.py new file mode 100644 index 0000000000000000000000000000000000000000..9aac6b62858864b24de49e4414435a3e307af319 --- /dev/null +++ b/uploads/current_experiment/ant/scheduler.py @@ -0,0 +1,7 @@ +import sys + +from iotAmak.scheduler import Scheduler + +if __name__ == '__main__': + a = Scheduler(str(sys.argv[1])) + a.run() \ No newline at end of file diff --git a/uploads/media/amas.py b/uploads/media/amas.py new file mode 100644 index 0000000000000000000000000000000000000000..1b8e3f306293dc9151a0de60da43dd8a772f3b20 --- /dev/null +++ b/uploads/media/amas.py @@ -0,0 +1,20 @@ +import sys + + +from iotAmak.amas import Amas + + +class PhiAmas(Amas): + + def __init__(self, broker_ip: str, clients, nbr_agent): + self.agent_to_create = nbr_agent + super().__init__(broker_ip, clients) + + def on_initial_agents_creation(self): + for _ in range(self.agent_to_create): + self.add_agent("philosophers", [str(self.agent_to_create)]) + + +if __name__ == '__main__': + s = PhiAmas(str(sys.argv[1]), sys.argv[2], 5) + s.run() diff --git a/uploads/media/ant.zip b/uploads/media/ant.zip new file mode 100644 index 0000000000000000000000000000000000000000..412da9c2e93f2efd75892236c484c9a8068ba3c3 Binary files /dev/null and b/uploads/media/ant.zip differ diff --git a/uploads/media/philosophers_7r4Esmi.zip b/uploads/media/philosophers_7r4Esmi.zip new file mode 100644 index 0000000000000000000000000000000000000000..36a06f59b5c16b862e0b44f85cc23505f35b0377 Binary files /dev/null and b/uploads/media/philosophers_7r4Esmi.zip differ