Commit 1c18151dc73b5a65151edce7773a41099ff580da
1 parent
37d5efa2
Exists in
dev
Multi-agents : AgentA (sender) sends commands to AgentB (receiver)
- AgentA a son propre scenario de commandes à envoyer à AgentB - Mode opératoire pour lancer un agent: - pour démarrer agentA : ./pyros.py start agentA [-c configfile] (ou encore: activer l'environnement virtuel, puis lancer "./AgentA.py configfile") - pour démarrer agentB : ouvrir un autre terminal et taper "./pyros.py start agentB"
Showing
5 changed files
with
115 additions
and
25 deletions
Show diff stats
README.md
... | ... | @@ -71,19 +71,19 @@ Date: 18/03/2019 |
71 | 71 | |
72 | 72 | Author: E. Pallier |
73 | 73 | |
74 | -VERSION: 0.20.20 | |
74 | +VERSION: 0.20.21 | |
75 | 75 | |
76 | 76 | Comment: |
77 | - Multi-agents (3 agents) : AgentA, AgentB, et AgentX s'envoient des commandes (en //) | |
77 | + Multi-agents (2 agents) : AgentA (sender) sends commands to AgentB (receiver) | |
78 | 78 | |
79 | - - AgentA, AgentB, et AgentX ont chacun leur propre scenario | |
80 | - - GROSSE OPTIMISATION : plus besoin du script intermédiaire "start_agent.py" !!! | |
81 | - - pyros.py lance directement "cd src/agent/ ; python AgentX.py" | |
79 | + - AgentA a son propre scenario de commandes à envoyer à AgentB | |
82 | 80 | - Mode opératoire pour lancer un agent: |
83 | - - pour démarrer agentX : ./pyros.py start agentX [-c configfile] | |
84 | - (ou encore: activer l'environnement virtuel, puis lancer "./AgentX.py configfile") | |
85 | - - pour démarrer agentA : ouvrir un autre terminal et taper "./pyros.py start agentA" | |
86 | - - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentX (ou AgentA) à False ou True | |
81 | + - pour démarrer agentA : ./pyros.py start agentA [-c configfile] | |
82 | + (ou encore: activer l'environnement virtuel, puis lancer "./AgentA.py configfile") | |
83 | + - pour démarrer agentB : ouvrir un autre terminal et taper "./pyros.py start agentB" | |
84 | + - pour utiliser thread ou processus : il suffit de mettre la constante RUN_IN_THREAD de AgentA (ou AgentB ou AgentX) à False ou True | |
85 | + - GROSSE OPTIMISATION : plus besoin du script intermédiaire "start_agent.py" !!! | |
86 | + ==> pyros.py lance directement "cd src/agent/ ; python AgentX.py" | |
87 | 87 | |
88 | 88 | -------------------------------------------------------------------------------------------- |
89 | 89 | - TECHNICAL DOC: tinyurl.com/pyros-doc | ... | ... |
src/agent/Agent.py
... | ... | @@ -148,7 +148,7 @@ class Agent: |
148 | 148 | # Run this agent in simulator mode |
149 | 149 | SIMULATOR_MODE = True |
150 | 150 | # Run the assertion tests at the end |
151 | - SIMULATOR_WITH_TEST = True | |
151 | + SIMULATOR_WITH_TEST = False | |
152 | 152 | # Who should I send commands to ? |
153 | 153 | SIMULATOR_COMMANDS_DEST = "myself" |
154 | 154 | # Default scenario to be executed |
... | ... | @@ -271,6 +271,9 @@ class Agent: |
271 | 271 | DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml" |
272 | 272 | CONFIG_DIR = "config" |
273 | 273 | |
274 | + # Command to send | |
275 | + cmdts = None | |
276 | + | |
274 | 277 | _agent_survey = None |
275 | 278 | _pending_commands = [] |
276 | 279 | _current_specific_cmd = None |
... | ... | @@ -373,8 +376,8 @@ class Agent: |
373 | 376 | # Wait a random number of sec before starting new iteration |
374 | 377 | # (to let another agent having the chance to send a command) |
375 | 378 | random_waiting_sec = random.randint(0,5) |
376 | - ##print(f"Waiting {random_waiting_sec} sec before starting new iteration...") | |
377 | - ##time.sleep(random_waiting_sec) | |
379 | + print(f"Waiting {random_waiting_sec} sec (random) before starting new iteration...") | |
380 | + time.sleep(random_waiting_sec) | |
378 | 381 | |
379 | 382 | try: |
380 | 383 | |
... | ... | @@ -389,7 +392,7 @@ class Agent: |
389 | 392 | |
390 | 393 | self.update_survey() |
391 | 394 | |
392 | - if self.SIMULATOR_MODE: self.simulator_send_next_command() | |
395 | + #if self.SIMULATOR_MODE: self.simulator_send_next_command() | |
393 | 396 | |
394 | 397 | # generic cmd in json format |
395 | 398 | print("------START COMMMAND PROCESSING------") |
... | ... | @@ -408,7 +411,7 @@ class Agent: |
408 | 411 | N=3 |
409 | 412 | if ((self._iter_num-1) % N) == 0: Command.purge_old_commands_for_agent(self.name) |
410 | 413 | |
411 | - self.waitfor(self.mainloop_waittime) | |
414 | + #self.waitfor(self.mainloop_waittime) | |
412 | 415 | |
413 | 416 | print("-"*20, "MAIN LOOP ITERATION (END)", "-"*20) |
414 | 417 | #print("-"*80) |
... | ... | @@ -420,8 +423,8 @@ class Agent: |
420 | 423 | # Exit if max duration is timed out |
421 | 424 | if self.MAX_DURATION_SEC: |
422 | 425 | #print ("time", time.time()-start_time) |
423 | - print("Exit because of max duration set to ", self.MAX_DURATION_SEC, "s") | |
424 | 426 | if time.time()-start_time > self.MAX_DURATION_SEC: |
427 | + print("Exit because of max duration set to ", self.MAX_DURATION_SEC, "s") | |
425 | 428 | self.kill_running_specific_cmd_if_exists() |
426 | 429 | break |
427 | 430 | |
... | ... | @@ -443,6 +446,46 @@ class Agent: |
443 | 446 | """ |
444 | 447 | self.set_status(self.STATUS_ROUTINE_PROCESS) |
445 | 448 | |
449 | + if self.cmdts is None: | |
450 | + self.cmdts = self.simulator_get_next_command_to_send() | |
451 | + else: | |
452 | + # Previous cmd was not completed, so, make a copy of it and send it again | |
453 | + # For this, it is enough to set primary key to None, | |
454 | + # then the send() command below will save a NEW command | |
455 | + #self.cmdts = copy.copy(self.cmdts) | |
456 | + self.cmdts.set_as_pending() | |
457 | + self.cmdts.id = None | |
458 | + | |
459 | + # No more command to send (from simulator), return | |
460 | + if self.cmdts is None: return | |
461 | + | |
462 | + # 1) Send cmd | |
463 | + print(f"Send command", self.cmdts) | |
464 | + self.cmdts.send() | |
465 | + cmdts_is_processed = False | |
466 | + cmdts_res = None | |
467 | + | |
468 | + # 2) Wait for end of cmd execution | |
469 | + #self.wait_for_execution_of_cmd(self.cmdts) | |
470 | + while not cmdts_is_processed: | |
471 | + print(f"Waiting for end of cmd {self.cmdts.name} execution...") | |
472 | + self.cmdts.refresh_from_db() | |
473 | + # timeout ? | |
474 | + if self.cmdts.is_expired(): break | |
475 | + # skipped or killed ? | |
476 | + if self.cmdts.is_skipped() or self.cmdts.is_killed(): break | |
477 | + if self.cmdts.is_executed(): | |
478 | + cmdts_res = self.cmdts.get_result() | |
479 | + self.cmdts = None | |
480 | + cmdts_is_processed = True | |
481 | + time.sleep(1) | |
482 | + | |
483 | + # 3) Get cmd result | |
484 | + if cmdts_is_processed: | |
485 | + print(f"Cmd executed. Result is '{cmdts_res}'") | |
486 | + else: | |
487 | + print("Command was not completed") | |
488 | + | |
446 | 489 | |
447 | 490 | """ |
448 | 491 | def purge_commands(self): |
... | ... | @@ -604,11 +647,18 @@ class Agent: |
604 | 647 | self._agent_survey.save() |
605 | 648 | |
606 | 649 | |
650 | + def simulator_get_next_command_to_send(self)->Command: | |
651 | + cmd_name = next(self.SIMULATOR_COMMANDS, None) | |
652 | + if cmd_name is None: return None | |
653 | + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST | |
654 | + return Command(sender=self.name, receiver=receiver_agent, name=cmd_name) | |
655 | + | |
607 | 656 | def simulator_send_next_command(self): |
608 | 657 | #self._current_test_cmd = "go_idle" if self._current_test_cmd=="go_active" else "go_active" |
609 | 658 | #if self._nb_test_cmds == 4: self._current_test_cmd = "exit" |
610 | 659 | cmd_name = next(self.SIMULATOR_COMMANDS, None) |
611 | - if not cmd_name: return | |
660 | + #print("next cmd is ", cmd_name) | |
661 | + if cmd_name is None: return | |
612 | 662 | #Command.objects.create(sender=self.name, receiver=self.name, name=cmd_name) |
613 | 663 | receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST |
614 | 664 | Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name) |
... | ... | @@ -616,6 +666,11 @@ class Agent: |
616 | 666 | #self._simulator_current_cmd_idx += 1 |
617 | 667 | #self._nb_test_cmds += 1 |
618 | 668 | |
669 | + """ | |
670 | + def send_command(self, cmd_name): | |
671 | + receiver_agent = self.name if self.SIMULATOR_COMMANDS_DEST=="myself" else self.SIMULATOR_COMMANDS_DEST | |
672 | + Command.objects.create(sender=self.name, receiver=receiver_agent, name=cmd_name) | |
673 | + """ | |
619 | 674 | |
620 | 675 | def get_next_valid_command(self)->Command: |
621 | 676 | """ |
... | ... | @@ -755,9 +810,11 @@ class Agent: |
755 | 810 | # Executing command |
756 | 811 | if cmd.name == "go_active": |
757 | 812 | self.set_active() |
813 | + cmd.set_result("I am now active") | |
758 | 814 | time.sleep(1) |
759 | 815 | if cmd.name == "go_idle": |
760 | 816 | self.set_idle() |
817 | + cmd.set_result("I am now idle") | |
761 | 818 | time.sleep(1) |
762 | 819 | # If cmd is "abort", kill any currently running thread |
763 | 820 | if cmd.name in ("abort", "exit"): | ... | ... |
src/agent/AgentA.py
... | ... | @@ -14,6 +14,9 @@ from Agent import Agent |
14 | 14 | |
15 | 15 | class AgentA(Agent): |
16 | 16 | |
17 | + #MAX_DURATION_SEC = None | |
18 | + MAX_DURATION_SEC = 85 | |
19 | + | |
17 | 20 | # FOR TEST ONLY |
18 | 21 | # Run this agent in simulator mode |
19 | 22 | SIMULATOR_MODE = True |
... | ... | @@ -21,19 +24,25 @@ class AgentA(Agent): |
21 | 24 | SIMULATOR_WITH_TEST = False |
22 | 25 | # Who should I send commands to ? |
23 | 26 | #SIMULATOR_COMMANDS_DEST = "myself" |
24 | - SIMULATOR_COMMANDS_DEST = "AgentX" | |
27 | + SIMULATOR_COMMANDS_DEST = "AgentB" | |
25 | 28 | # Scenario to be executed |
26 | 29 | SIMULATOR_COMMANDS = [ |
27 | 30 | "go_active", |
31 | + | |
28 | 32 | "go_idle", |
33 | + # Not executed because receiver agent is now "idle" | |
34 | + #"specific0", | |
35 | + | |
36 | + # Executed because receiver agent is now "active" | |
29 | 37 | "go_active", |
30 | - "go_idle", | |
31 | - "go_active", | |
38 | + "specific1", | |
39 | + | |
32 | 40 | "go_idle", |
33 | 41 | "exit", |
34 | 42 | ] |
35 | 43 | |
36 | 44 | |
45 | + | |
37 | 46 | """ |
38 | 47 | ================================================================= |
39 | 48 | FUNCTIONS RUN INSIDE MAIN THREAD | ... | ... |
src/agent/AgentB.py
... | ... | @@ -21,9 +21,10 @@ class AgentB(Agent): |
21 | 21 | SIMULATOR_WITH_TEST = False |
22 | 22 | # Who should I send commands to ? |
23 | 23 | #SIMULATOR_COMMANDS_DEST = "myself" |
24 | - SIMULATOR_COMMANDS_DEST = "AgentX" | |
24 | + SIMULATOR_COMMANDS_DEST = "AgentA" | |
25 | 25 | # Scenario to be executed |
26 | - SIMULATOR_COMMANDS = [ | |
26 | + SIMULATOR_COMMANDS = [] | |
27 | + """ | |
27 | 28 | "go_active", |
28 | 29 | "go_idle", |
29 | 30 | "go_active", |
... | ... | @@ -33,6 +34,7 @@ class AgentB(Agent): |
33 | 34 | "go_idle", |
34 | 35 | "exit", |
35 | 36 | ] |
37 | + """ | |
36 | 38 | |
37 | 39 | |
38 | 40 | """ | ... | ... |
src/common/models.py
... | ... | @@ -278,6 +278,12 @@ class Command(models.Model): |
278 | 278 | |
279 | 279 | # -------------- Command CLASS (static) METHODS -------------- |
280 | 280 | |
281 | + """ | |
282 | + @classmethod | |
283 | + def send(cls, cmd:Command): | |
284 | + cls.objects.create(sender=cmd.name, receiver=cmd.receiver, name=cmd.name) | |
285 | + """ | |
286 | + | |
281 | 287 | @classmethod |
282 | 288 | def get_peremption_date_from_now(cls): |
283 | 289 | return datetime.utcnow().astimezone() - timedelta(hours = cls.COMMANDS_PEREMPTION_HOURS) |
... | ... | @@ -374,6 +380,8 @@ class Command(models.Model): |
374 | 380 | |
375 | 381 | # --- BOOLEAN (test) functions --- |
376 | 382 | |
383 | + def send(self): self.save() | |
384 | + | |
377 | 385 | def is_generic(self): |
378 | 386 | """ |
379 | 387 | Is this a generic command ? |
... | ... | @@ -412,7 +420,14 @@ class Command(models.Model): |
412 | 420 | if elapsed_time > max_time: |
413 | 421 | """ |
414 | 422 | |
415 | - # --- SETTERS functions --- | |
423 | + # --- GETTERS/SETTERS functions --- | |
424 | + | |
425 | + def get_result(self): | |
426 | + return self.result | |
427 | + | |
428 | + def set_result(self, result:str): | |
429 | + self.result = result | |
430 | + self.save() | |
416 | 431 | |
417 | 432 | def set_read_time(self): |
418 | 433 | self.receiver_read_time = datetime.utcnow().astimezone() |
... | ... | @@ -421,15 +436,20 @@ class Command(models.Model): |
421 | 436 | |
422 | 437 | def set_as_processed(self): |
423 | 438 | print(f"- Set command {self.name} as processed") |
439 | + print(self) | |
424 | 440 | self.receiver_status_code = self.CMD_STATUS_CODES.CMD_EXECUTED |
425 | 441 | self.receiver_processed_time = datetime.utcnow().astimezone() |
426 | - # Optimization: update the related fields | |
427 | - self.save(update_fields=["receiver_status_code", "receiver_processed_time"]) | |
442 | + self.save() | |
443 | + # Optimization: update the related fields, but does not work, why ? | |
444 | + ##self.save(update_fields=["receiver_status_code", "receiver_processed_time"]) | |
428 | 445 | |
429 | 446 | def set_as_outofdate(self): |
430 | 447 | print(f"- Set this command as expired (older than its validity duration of {self.validity_duration_sec}s): {self}") |
431 | 448 | self.set_status_to(self.CMD_STATUS_CODES.CMD_OUTOFDATE) |
432 | 449 | |
450 | + def set_as_pending(self): | |
451 | + self.set_status_to(self.CMD_STATUS_CODES.CMD_PENDING) | |
452 | + | |
433 | 453 | def set_as_skipped(self): |
434 | 454 | self.set_status_to(self.CMD_STATUS_CODES.CMD_SKIPPED) |
435 | 455 | |
... | ... | @@ -448,7 +468,9 @@ class Command(models.Model): |
448 | 468 | |
449 | 469 | def set_status_to(self, status:str): |
450 | 470 | self.receiver_status_code = status |
451 | - self.save(update_fields=["receiver_status_code"]) | |
471 | + self.save() | |
472 | + # Optimization, but does not work, why ?... | |
473 | + ##self.save(update_fields=["receiver_status_code"]) | |
452 | 474 | |
453 | 475 | |
454 | 476 | class AgentSurvey(models.Model): | ... | ... |