Commit 7138789eea6e2bb74e45d3ad0766df6b754509e4

Authored by Etienne Pallier
1 parent 3d9bc073
Exists in dev

new Agent with 3 states and better exceptions...

src/core/pyros_django/agent/Agent.py
1 1 #!/usr/bin/env python3
2 2  
3   -VERSION = "0.5"
  3 +VERSION = "1.0"
4 4  
5 5 #DEBUG=True
6 6 #DEBUG=False
... ... @@ -227,12 +227,46 @@ class StoppableThreadEvenWhenSleeping(threading.Thread):
227 227 '''
228 228  
229 229  
  230 +###
  231 +# =================================================================
  232 +# EXCEPTIONS classes
  233 +# =================================================================
  234 +###
230 235  
231   -"""
232   -=================================================================
233   - class Agent
234   -=================================================================
235   -"""
  236 +class UnknownCmdException(Exception):
  237 + ''' Raised when an Agent (specific) cmd is NOT known by the agent '''
  238 + # pass
  239 + def __init__(self, cmd_name):
  240 + self.cmd_name = cmd_name
  241 + def __str__(self):
  242 + return f"The Agent command '{self.cmd_name}' is unknown by the agent"
  243 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  244 +
  245 +class AgentCmdUnimplementedException(Exception):
  246 + ''' Raised when an Agent Specific cmd is known by the agent but not implemented '''
  247 + # pass
  248 + def __init__(self, cmd_name):
  249 + self.cmd_name = cmd_name
  250 + def __str__(self):
  251 + return f"The Agent (General or Specific) command '{self.cmd_name}' is known by the agent but not implemented"
  252 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  253 +
  254 +class AgentCmdBadArgsException(Exception):
  255 + ''' Raised when an Agent cmd has bad or missing argument(s) '''
  256 + # pass
  257 + def __init__(self, cmd_name):
  258 + self.cmd_name = cmd_name
  259 + def __str__(self):
  260 + return f"The Agent command '{self.cmd_name}' has bad or missing argument(s)"
  261 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  262 +
  263 +
  264 +
  265 +###
  266 +# =================================================================
  267 +# class Agent
  268 +# =================================================================
  269 +###
236 270  
237 271 class Agent:
238 272 """
... ... @@ -258,7 +292,7 @@ class Agent:
258 292 DEBUG_MODE = False
259 293 WITH_SIMULATOR = False
260 294 TEST_MODE = False
261   -
  295 +
262 296 # Default LOG level is INFO
263 297 #PYROS_DEFAULT_GLOBAL_LOG_LEVEL = LogPyros.LOG_LEVEL_INFO # INFO
264 298  
... ... @@ -285,9 +319,11 @@ class Agent:
285 319 # Default scenario to be executed
286 320 #TEST_COMMANDS = iter([
287 321 TEST_COMMANDS_LIST = [
288   - "set_state:active",
289   - "set_state:idle",
  322 + "Agent set_state ATTENTIVE",
  323 + "Agent set_state ROUTINE",
  324 + "Agent set_state IDLE",
290 325  
  326 + '''
291 327 # specific0 not_executed_because_idle
292 328 "specific0",
293 329  
... ... @@ -333,6 +369,7 @@ class Agent:
333 369 # and not even be added to the database command table
334 370 "set_state:active",
335 371 "specific10"
  372 + '''
336 373 ]
337 374 #TEST_COMMANDS = iter(TEST_COMMANDS_LIST)
338 375  
... ... @@ -367,9 +404,22 @@ class Agent:
367 404 ###STATUS_SPECIFIC_PROCESS = "IN_SPECIFIC_PROCESS"
368 405 STATUS_EXIT = "EXITING"
369 406  
370   - # Modes
371   - MODE_ACTIVE = "ACTIVE"
  407 +
  408 + '''
  409 + MODES
  410 +
  411 + In all modes, the Agent listens to commands sent to him and executes Agent level GENERAL ones.
  412 +
  413 + - MODE_IDLE : "idle" mode, does nothing, only executes Agent level GENERAL commands (DO_RESTART, DO_EXIT, DO_ABORT, DO_FLUSH...)
  414 + - MODE_ROUTINE : idem IDLE + executes routine process (before & after)
  415 + - MODE_ATTENTIVE : idem ROUTINE + executes Agent level SPECIFIC commands (commands specific to this agent, that only this agent understands and can execute)
  416 +
  417 + Default mode is MODE_ATTENTIVE (most active mode)
  418 +
  419 + '''
372 420 MODE_IDLE = "IDLE"
  421 + MODE_ROUTINE = "ROUTINE"
  422 + MODE_ATTENTIVE = "ATTENTIVE"
373 423  
374 424 ''' Moved to more central file : config.config_base
375 425 PYROS_DJANGO_BASE_DIR = Path("src/core/pyros_django") # pathlib
... ... @@ -414,6 +464,8 @@ class Agent:
414 464 # new obsconfig init for agent:
415 465 ##def __init__(self, RUN_IN_THREAD=True):
416 466 def __init__(self):
  467 + # Agent is by default in mode ATTENTIVE (most active mode)
  468 + self.mode = self.MODE_ATTENTIVE
417 469 log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__))
418 470 log.debug("start Agent init")
419 471 obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"]
... ... @@ -440,7 +492,7 @@ class Agent:
440 492 self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST)
441 493 ##self.RUN_IN_THREAD = RUN_IN_THREAD
442 494 self._set_status(self.STATUS_LAUNCH)
443   - self._set_idle()
  495 + ####self._set_idle()
444 496  
445 497 # Create 1st survey if none
446 498 #tmp = AgentSurvey.objects.filter(name=self.name)
... ... @@ -756,26 +808,16 @@ class Agent:
756 808  
757 809 # TEST MODE ONLY
758 810 # IF in test mode but with REAL devices (no SIMULATOR), delete all dangerous commands from the test commands list scenario:
759   - if self.TEST_MODE:
760   - log.debug("\n!!! In TEST mode !!! => preparing to run a scenario of test commands")
761   - log.debug("- Current test commands list scenario is:\n" + str(self.TEST_COMMANDS_LIST))
762   - if not self.WITH_SIMULATOR:
763   - log.debug("\n!!! In TEST but no SIMULATOR mode (using REAL device) !!! => removing dangerous commands for real devices... :")
764   - TEST_COMMANDS_LIST_copy = self.TEST_COMMANDS_LIST.copy()
765   - for cmd in TEST_COMMANDS_LIST_copy:
766   - cmd_key = cmd.split()[1]
767   - if ("set_" in cmd_key) or ("do_start" in cmd_key) or cmd_key in ["do_init", "do_goto", "do_open", "do_close"]:
768   - self.TEST_COMMANDS_LIST.remove(cmd)
769   - log.debug("- NEW test commands list scenario is:\n" + self.TEST_COMMANDS_LIST, '\n')
770   -
771   - self._DO_EXIT = False
772   - self._DO_RESTART = True
  811 + if self.TEST_MODE: self.TEST_prepare()
  812 +
  813 + #self._DO_EXIT = False
  814 + self.DO_RESTART_LOOP = True
773 815  
774 816 # Will loop again only if _DO_RESTART is True
775   - while self._DO_RESTART:
776   - self._DO_RESTART = False
  817 + while self.DO_RESTART_LOOP:
  818 + #self._DO_RESTART = False
777 819 self.start_time = time.time()
778   - log.debug("on est ici: " + os.getcwd())
  820 + #log.debug("on est ici: " + os.getcwd())
779 821  
780 822 self._load_config()
781 823  
... ... @@ -794,12 +836,12 @@ class Agent:
794 836 AgentCmd.delete_commands_with_running_status_for_agent(self.name)
795 837  
796 838 self._iter_num = 1
797   - self._DO_MAIN_LOOP = True
  839 + self.DO_MAIN_LOOP = True
798 840 # Main loop
799   - while self._DO_MAIN_LOOP:
  841 + while self.DO_MAIN_LOOP:
800 842 try:
801 843 self._main_loop(nb_iter,FOR_REAL)
802   - if not self._DO_MAIN_LOOP: break
  844 + #if not self.DO_MAIN_LOOP: break
803 845 except KeyboardInterrupt: # CTRL-C
804 846 # In case of CTRL-C, kill the current thread (process) before dying (in error)
805 847 log.info("CTRL-C Interrupted, I kill the current thread (process) before exiting (if exists)")
... ... @@ -814,15 +856,25 @@ class Agent:
814 856  
815 857 #def _kill_running_device_cmd_if_exists(self, abort_cmd_sender):
816 858 def do_things_before_exit(self, abort_cmd_sender):
817   - pass
  859 + #pass
  860 + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :")
  861 + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  862 + AgentCmd.show_commands(commands, True)
  863 + print("TODO: flush commands !!!")
  864 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results()
  865 + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST:
  866 + self._TEST_test_results()
  867 + #self._DO_EXIT=True
  868 + #exit(0)
  869 +
818 870  
819 871  
820 872 def _main_loop(self, nb_iter:int=None, FOR_REAL:bool=True):
821 873  
822 874 self._main_loop_start(nb_iter)
823   - if not self._DO_MAIN_LOOP: return
  875 + #if not self.DO_MAIN_LOOP: return
824 876  
825   - self._load_config() # only if changed
  877 + self._reload_config_if_changed() # only if changed
826 878  
827 879 # better to do this in a subclass
828 880 #self.show_config()
... ... @@ -833,20 +885,34 @@ class Agent:
833 885  
834 886 #self.printd("====== START COMMMANDS PROCESSING ======")
835 887  
  888 + # SIMU
  889 + #self.send_cmd_to("AgentScheduler", "do_replan")
  890 +
836 891 # ROUTINE process
837 892 # To be overriden to be specified by subclass
838   - self._routine_process_before()
  893 + if not self.IS_IDLE(): self._routine_process_before()
839 894 #self.printd("I am IDLE, so I bypass the routine_process (do not send any new command)")
840 895  
841 896 # Processing the next pending command if exists
842   - self._command_process_if_exists()
  897 + # If Agent general level command like (DO_RESTART, DO_EXIT, DO_ABORT)
  898 + # => will set DO_MAIN_LOOP=False and/or DO_RESTART_LOOP
  899 +
  900 + print()
  901 + print()
  902 + log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
  903 + self._process_next_command_if_exists()
  904 + log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
  905 +
  906 + if not self.DO_MAIN_LOOP: return
843 907  
  908 + '''
844 909 # if restart, exit this loop to restart from beginning
845   - if self._DO_RESTART or self._DO_EXIT:
846   - self._DO_MAIN_LOOP = False
  910 + if self.DO_RESTART or self.DO_EXIT or self.DO_ABORT:
  911 + self.DO_MAIN_LOOP = False
847 912 return
  913 + '''
848 914  
849   - self._routine_process_after()
  915 + if not self.IS_IDLE(): self._routine_process_after()
850 916  
851 917 #self.printd("====== END COMMMANDS PROCESSING ======")
852 918  
... ... @@ -858,6 +924,8 @@ class Agent:
858 924 self._iter_num += 1
859 925  
860 926  
  927 +
  928 +
861 929 def _main_loop_start(self, nb_iter:int):
862 930  
863 931 for i in range(3): print()
... ... @@ -872,7 +940,7 @@ class Agent:
872 940 # Bad number of iterations or nb iterations reached => exit
873 941 if nb_iter <= 0 or nb_iter < self._iter_num:
874 942 log.info(f"Exit because number of iterations asked ({nb_iter}) has been reached")
875   - self._DO_MAIN_LOOP = False
  943 + self.DO_MAIN_LOOP = False
876 944 return
877 945  
878 946 # Wait a random number of sec before starting iteration
... ... @@ -889,14 +957,15 @@ class Agent:
889 957 #self._kill_running_device_cmd_if_exists(self.name)
890 958 self.do_things_before_exit(self.name)
891 959 #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: self._TEST_test_results()
892   - self._DO_MAIN_LOOP = False
  960 + self.DO_MAIN_LOOP = False
893 961 return
894 962  
895 963 self._set_status(self.STATUS_MAIN_LOOP)
896 964 self.show_mode_and_status()
897 965  
898 966  
899   - def process_other_level_cmd(self):
  967 + # To be overriden by subclass (AgentDevice)
  968 + def process_device_level_cmd(self):
900 969 pass
901 970 '''
902 971 log.info("(DEVICE LEVEL CMD)")
... ... @@ -914,12 +983,12 @@ class Agent:
914 983  
915 984  
916 985  
917   - def _command_process_if_exists(self):
  986 + def _process_next_command_if_exists(self):
918 987 ''' Processing the next pending command if exists '''
919 988  
920   - print()
921   - print()
922   - log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
  989 + #print()
  990 + #print()
  991 + #log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
923 992  
924 993  
925 994 # Purge commands (every N iterations, delete old commands)
... ... @@ -933,49 +1002,101 @@ class Agent:
933 1002 cmd = self._get_next_valid_and_not_running_command()
934 1003 #self._set_status(self.STATUS_GENERAL_PROCESS)
935 1004 #if cmd: self.command_process(cmd)
936   - if cmd:
937   - log.info('-'*6)
938   - log.info('-'*6 + " RECEIVED NEW COMMAND TO PROCESS: ")
939   - log.info('-'*6 + str(cmd))
940   - log.info('-'*6)
941   -
942   - # CASE 1 - AGENT LEVEL command
943   - # => I process it directly without asking my DC
944   - # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel)
945   - if self._is_agent_level_cmd(cmd):
946   - log.info("(AGENT LEVEL CMD)")
947   - try:
948   - self._exec_agent_cmd(cmd)
949   - except AttributeError as e:
950   - #self.log_e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
951   - #self.log_e("Thus => I ignore this command...")
952   - log.e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
953   - log.e("Thus => I ignore this command...")
954   - cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND")
955   - cmd.set_as_pending()
956   - cmd.set_as_skipped()
957   -
958   - # CASE 2 - DEVICE LEVEL command
959   - # => I delegate it to my DC
960   - # => Execute it only if I am active and no currently running another device level cmd
961   - # => Long execution time, so I will execute it in parallel (in a new thread or process)
962   - #elif self.is_device_level_cmd(cmd):
963   - elif self.is_other_level_cmd(cmd):
964   - self.process_other_level_cmd(cmd)
965   -
966   - # CASE 3 - INVALID COMMAND
967   - else:
968   - #raise Exception("INVALID COMMAND: " + cmd.name)
969   - log.warning("******************************************************")
970   - log.warning("*************** ERROR: INVALID COMMAND (UNKNOWN) ***************")
971   - log.warning("******************************************************")
972   - log.warning("Thus => I ignore this command...")
973   - cmd.set_result("ERROR: INVALID COMMAND")
974   - cmd.set_as_skipped()
  1005 +
  1006 + # SIMU
  1007 + #cmd = "do_replan"
  1008 + #self.send_cmd_to("AgentScheduler", "do_replan")
  1009 +
  1010 + # No new command => nothing to do
  1011 + if not cmd: return
975 1012  
976   - print()
977   - log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
  1013 + log.info('-'*6)
  1014 + log.info('-'*6 + " RECEIVED NEW COMMAND TO PROCESS: ")
  1015 + log.info('-'*6 + str(cmd))
  1016 + log.info('-'*6)
978 1017  
  1018 + cmd.set_read_time()
  1019 +
  1020 + # CASE 1 - AGENT GENERAL command
  1021 + # (DO_RESTART, DO_EXIST, DO_ABORT, DO_ABORT_COMMAND, DO_FLUSH_COMMANDS, ...)
  1022 + # This processing can set DO_MAIN_LOOP and DO_RESTART_LOOP to False
  1023 + if self._is_agent_general_cmd(cmd):
  1024 + try:
  1025 + self._process_agent_general_cmd(cmd)
  1026 + except AgentCmdUnimplementedException as e:
  1027 + print(e)
  1028 + cmd.set_as_skipped("ERROR: Unimplemented Agent General command")
  1029 + self._cleanup_before_exist()
  1030 + raise
  1031 + #except ValueError as e:
  1032 + except AgentCmdBadArgsException as e:
  1033 + print(e)
  1034 + cmd.set_as_skipped("ERROR: Bad Argument(s)")
  1035 + self._cleanup_before_exit()
  1036 + raise
  1037 + #raise AgentCmdBadArgsException(cmd.name)
  1038 + # Must I stop or restart ?
  1039 + if cmd.name in ('DO_RESTART','DO_EXIT','OO_ABORT') :
  1040 + self.DO_MAIN_LOOP = False
  1041 + if cmd.name == 'OO_ABORT':
  1042 + self._abort_current_running_cmd_if_exists()
  1043 + else :
  1044 + self._cleanup_before_exit()
  1045 + if cmd.name != 'OO_RESTART':
  1046 + self.DO_RESTART_LOOP = False
  1047 + return
  1048 +
  1049 + # CASE 2 - AGENT SPECIFIC command
  1050 + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel)
  1051 + if self._is_agent_specific_cmd(cmd):
  1052 + #log.info("(AGENT LEVEL CMD)")
  1053 + if not self._IS_ATTENTIVE():
  1054 + cmd.set_as_skipped("Skipped because I am not ATTENTIVE")
  1055 + return
  1056 + try:
  1057 + self._process_agent_specific_cmd(cmd)
  1058 + #self._exec_agent_cmd(cmd)
  1059 + #except AttributeError as e:
  1060 + except AgentCmdUnimplementedException as e:
  1061 + print(e)
  1062 + #self.log_e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1063 + #self.log_e("Thus => I ignore this command...")
  1064 + log.e(f"EXCEPTION: Agent specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1065 + #log.e("Thus => I ignore this command...")
  1066 + #cmd.set_result("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND", False)
  1067 + #cmd.set_as_pending()
  1068 + cmd.set_as_skipped("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND")
  1069 + self._cleanup_before_exit()
  1070 + raise AgentCmdUnimplementedException(cmd.name)
  1071 + return
  1072 +
  1073 + # CASE 3 - OTHER level command (DEVsICE level, only for AgentDevice)
  1074 + if self.is_device_level_cmd(cmd):
  1075 + self.process_device_level_cmd(cmd)
  1076 + return
  1077 +
  1078 + # CASE 4 - INVALID COMMAND
  1079 + #raise Exception("INVALID COMMAND: " + cmd.name)
  1080 + log.warning("******************************************************")
  1081 + log.warning("*************** ERROR: INVALID COMMAND (UNKNOWN) ***************")
  1082 + log.warning("******************************************************")
  1083 + #log.warning("Thus => I ignore this command...")
  1084 + #cmd.set_result("ERROR: INVALID COMMAND")
  1085 + cmd.set_as_skipped("ERROR: INVALID AGENT COMMAND")
  1086 + self._cleanup_before_exit()
  1087 + raise UnknownCmdException(cmd.name)
  1088 +
  1089 + #print()
  1090 + #log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
  1091 +
  1092 +
  1093 +
  1094 +
  1095 + def _abort_current_running_cmd_if_exists(self):
  1096 + pass
  1097 +
  1098 + def _cleanup_before_exit(self):
  1099 + self.do_things_before_exit()
979 1100  
980 1101  
981 1102 def print_TEST_MODE(self):
... ... @@ -1041,7 +1162,7 @@ class Agent:
1041 1162  
1042 1163 # To be overridden by subclasses
1043 1164 def routine_process_after_body(self):
1044   - if self.TEST_MODE: self._test_routine_process()
  1165 + if self.TEST_MODE: self._TEST_test_routine_process()
1045 1166  
1046 1167 """
1047 1168 def purge_commands(self):
... ... @@ -1096,17 +1217,18 @@ class Agent:
1096 1217 log.info(f"[NEW MODE {mode}]")
1097 1218 self.mode = mode
1098 1219  
1099   - def _is_active(self):
1100   - return self.mode == self.MODE_ACTIVE
1101   - def _is_idle(self):
1102   - return not self._is_active()
  1220 + # Test mode
  1221 + def IS_IDLE(self): return self.mode == self.MODE_IDLE
  1222 + def IS_ROUTINE(self): return self.mode == self.MODE_ROUTINE
  1223 + def IS_ATTENTIVE(self): return self.mode == self.MODE_ATTENTIVE
1103 1224  
1104   - def _set_active(self):
1105   - self._set_mode(self.MODE_ACTIVE)
1106   - log.info("set active mode")
1107 1225  
1108   - def _set_idle(self):
  1226 + def set_idle(self):
1109 1227 self._set_mode(self.MODE_IDLE)
  1228 + def set_routine(self):
  1229 + self._set_mode(self.MODE_ROUTINE)
  1230 + def set_attentive(self):
  1231 + self._set_mode(self.MODE_ATTENTIVE)
1110 1232  
1111 1233 def show_mode_and_status(self):
1112 1234 log.info(f"CURRENT MODE is {self.mode} (status is {self.status})")
... ... @@ -1149,7 +1271,8 @@ class Agent:
1149 1271 def _set_mode_from_config(self, agent_name):
1150 1272 # all agent are active ?
1151 1273  
1152   - mode = self.MODE_ACTIVE
  1274 + #mode = self.MODE_ACTIVE
  1275 + mode = self.MODE_ATTENTIVE
1153 1276 self._set_mode(mode)
1154 1277 return True
1155 1278 # old config
... ... @@ -1179,6 +1302,9 @@ class Agent:
1179 1302 log.debug("Initializing...")
1180 1303 self._set_status(self.STATUS_INIT)
1181 1304  
  1305 + def _reload_config_if_changed(self):
  1306 + self._load_config()
  1307 +
1182 1308 def _load_config(self):
1183 1309 """
1184 1310 TODO:
... ... @@ -1325,13 +1451,14 @@ class Agent:
1325 1451 real_agent_name = self._get_real_agent_name(to_agent)
1326 1452 except KeyError as e:
1327 1453 '''
1328   - # real_agent_name = self._get_real_agent_name(to_agent)
1329   - # if not real_agent_name:
  1454 + real_agent_name = self._get_real_agent_name(to_agent)
  1455 + if not real_agent_name:
  1456 + return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args)
1330 1457 # log.e("UNKNOWN AgentDevice ALIAS", to_agent)
1331 1458 # #self.log_e("Exception raised", e)
1332 1459 # log.e(f"=> Thus, I do not send this command '{cmd_name}'")
1333 1460 # return None
1334   - return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args)
  1461 + return AgentCmd.create(self.name, real_agent_name, cmd_name, cmd_args)
1335 1462 '''
1336 1463 return AgentCmd(
1337 1464 sender=self.name,
... ... @@ -1518,6 +1645,128 @@ class Agent:
1518 1645  
1519 1646  
1520 1647  
  1648 + def _process_agent_general_cmd(self, cmd:AgentCmd):
  1649 + # GENERAL command (related to any agent)
  1650 +
  1651 + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...")
  1652 + log.info(f"Starting execution of an AGENT GENERAL cmd...")
  1653 +
  1654 + # Update read time to say that the command has been READ
  1655 + #cmd.set_read_time()
  1656 + cmd.set_as_running()
  1657 +
  1658 + log.info("(Agent level GENERAL CMD)")
  1659 + _,cmd_name,cmd_args = cmd.get_full_name_parts()
  1660 + #cmd_name, cmd_args = cmd.tokenize()
  1661 + #if cmd.name == "set_state:active":
  1662 + #elif cmd.name == "set_state:idle":
  1663 +
  1664 + # Default result (null)
  1665 + result = None
  1666 +
  1667 + #if cmd_name in ("do_abort", "do_exit", "do_restart_init"):
  1668 + if cmd_name in ("do_abort", "do_exit", "do_restart"):
  1669 + #self.printd("Current pending commands are:")
  1670 + #Command.show_commands(self._pending_commands)
  1671 + log.info("Stopping/Aborting current executing command if exists:")
  1672 + #self._kill_running_device_cmd_if_exists(cmd.sender)
  1673 + '''
  1674 + self.do_things_before_exit(cmd.sender)
  1675 + if cmd_name == "do_restart_init":
  1676 + log.info("restart_init received: Restarting from init()")
  1677 + self._DO_RESTART=True
  1678 + elif cmd.name == "do_exit":
  1679 + self._DO_EXIT=True
  1680 + cmd.set_result('SHOULD BE DONE NOW')
  1681 + '''
  1682 + result = "STOP or RESTART"
  1683 +
  1684 + elif cmd_name == "set_state":
  1685 + #if not cmd_args: raise ValueError()
  1686 + if not cmd_args: raise AgentCmdBadArgsException(cmd.name)
  1687 + state = cmd_args[0]
  1688 + if state == "IDLE": self.set_idle()
  1689 + elif state == "ROUTINE": self.set_routine()
  1690 + elif state == "ATTENTIVE": self.set_attentive()
  1691 + else: raise AgentCmdBadArgsException(cmd.name)
  1692 + #cmd.set_result("I am now " + state)
  1693 + result = "I am now " + state
  1694 + #time.sleep(1)
  1695 + #self.waitfor(1)
  1696 +
  1697 + #elif cmd_name in ("do_flush_commands"):
  1698 + elif cmd_name == "do_flush_commands":
  1699 + "flush_commands received: Delete all pending commands"
  1700 + AgentCmd.delete_pending_commands_for_agent(self.name)
  1701 + #cmd.set_result('DONE')
  1702 + result = "FLUSH DONE"
  1703 +
  1704 + elif cmd_name == "do_eval":
  1705 + #if not cmd_args: raise ValueError()
  1706 + if not cmd_args: raise AgentCmdBadArgsException(cmd.name)
  1707 + #cmd.set_result(eval(cmd_args))
  1708 + result = eval(cmd_args)
  1709 +
  1710 + else:
  1711 + raise UnknownCmdException(cmd.name)
  1712 + '''
  1713 + name = cmd.name
  1714 + args = None
  1715 + if " " in name: name,args = name.split()
  1716 + if name == "do_eval":
  1717 + if args==None: raise(ValueError)
  1718 + cmd.set_result(eval(args))
  1719 + '''
  1720 +
  1721 + cmd.set_as_processed(result)
  1722 + log.info("...Agent level GENERAL cmd has been executed")
  1723 +
  1724 + '''
  1725 + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !)
  1726 + # This "do_exit" should normally kill any current thread (to be checked...)
  1727 + if cmd.name == "do_exit":
  1728 + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :")
  1729 + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  1730 + AgentCmd.show_commands(commands, True)
  1731 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results()
  1732 + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST:
  1733 + self._TEST_test_results()
  1734 + #self._DO_EXIT=True
  1735 + #exit(0)
  1736 + '''
  1737 +
  1738 +
  1739 +
  1740 +
  1741 + def _process_agent_specific_cmd(self, cmd:AgentCmd):
  1742 +
  1743 + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...")
  1744 + log.info(f"Starting execution of an AGENT LEVEL cmd...")
  1745 +
  1746 + # Update read time to say that the command has been READ
  1747 + #cmd.set_read_time(False)
  1748 + cmd.set_as_running()
  1749 +
  1750 + log.info("(Agent SPECIFIC cmd)")
  1751 + # Execute method self."cmd.name"()
  1752 + # This can raise an exception (caught by this method caller)
  1753 + self.exec_cmd_from_its_name(cmd)
  1754 + '''
  1755 + try:
  1756 + except AttributeError as e:
  1757 + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1758 + self.printd("Thus => I ignore this command...")
  1759 + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND")
  1760 + cmd.set_as_pending()
  1761 + cmd.set_as_skipped()
  1762 + return
  1763 + '''
  1764 + #cmd.set_result("Agent level SPECIFIC cmd done")
  1765 + cmd.set_as_processed("Agent SPECIFIC cmd done")
  1766 + log.info("...Agent SPECIFIC cmd has been executed")
  1767 +
  1768 +
  1769 +
1521 1770  
1522 1771 '''
1523 1772 def do_log(self):
... ... @@ -1531,13 +1780,23 @@ class Agent:
1531 1780  
1532 1781 def exec_cmd_from_its_name(self, cmd:AgentCmd):
1533 1782 func = cmd.name
1534   - if cmd.args:
1535   - return getattr(self, func)(*cmd.args)
1536   - else:
1537   - return getattr(self, func)()
  1783 + try:
  1784 + if cmd.args:
  1785 + return getattr(self, func)(*cmd.args)
  1786 + else:
  1787 + return getattr(self, func)()
  1788 + except AttributeError as e:
  1789 + # TODO: AgentCmdBadArgsException si la fonction existe dans le code de l'agent
  1790 + print("I know this specific command but it is not yet implemented : ", func)
  1791 + print(e)
  1792 + raise AgentCmdUnimplementedException(cmd.name)
1538 1793  
1539 1794 def _is_agent_level_cmd(self, cmd:AgentCmd):
1540   - return cmd.is_agent_general_cmd() or self._is_agent_specific_cmd(cmd)
  1795 + return self._is_agent_general_cmd(cmd) or self._is_agent_specific_cmd(cmd)
  1796 +
  1797 + def _is_agent_general_cmd(self, cmd:AgentCmd):
  1798 + return cmd.is_agent_general_cmd()
  1799 +
1541 1800  
1542 1801 '''
1543 1802 def _exec_agent_cmd(self, cmd:Command):
... ... @@ -1578,8 +1837,7 @@ class Agent:
1578 1837  
1579 1838 # To be overriden by subclass (AgentDevice...)
1580 1839 # @abstract
1581   - #def is_device_level_cmd(self, cmd):
1582   - def is_other_level_cmd(self, cmd):
  1840 + def is_device_level_cmd(self, cmd):
1583 1841 return False
1584 1842  
1585 1843 '''
... ... @@ -1641,7 +1899,7 @@ class Agent:
1641 1899 #self._nb_test_cmds += 1
1642 1900 """
1643 1901  
1644   - def _test_routine_process(self):
  1902 + def _TEST_test_routine_process(self):
1645 1903 """
1646 1904 TEST MODE ONLY
1647 1905 """
... ... @@ -1713,7 +1971,7 @@ class Agent:
1713 1971 self._cmdts = self._TEST_get_next_command_to_send()
1714 1972 # No more command to send (from simulator) => return and EXIT
1715 1973 if self._cmdts is None:
1716   - self._DO_MAIN_LOOP = False
  1974 + self.DO_MAIN_LOOP = False
1717 1975 return
1718 1976 # Send cmd (= set as pending and save)
1719 1977 log.info("***")
... ... @@ -1787,6 +2045,18 @@ class Agent:
1787 2045 nb_asserted2 = self.TEST_test_results_main(commands)
1788 2046 self._TEST_test_results_end(nb_asserted)
1789 2047  
  2048 + def TEST_prepare(self):
  2049 + log.debug("\n!!! In TEST mode !!! => preparing to run a scenario of test commands")
  2050 + log.debug("- Current test commands list scenario is:\n" + str(self.TEST_COMMANDS_LIST))
  2051 + if not self.WITH_SIMULATOR:
  2052 + log.debug("\n!!! In TEST but no SIMULATOR mode (using REAL device) !!! => removing dangerous commands for real devices... :")
  2053 + TEST_COMMANDS_LIST_copy = self.TEST_COMMANDS_LIST.copy()
  2054 + for cmd in TEST_COMMANDS_LIST_copy:
  2055 + cmd_key = cmd.split()[1]
  2056 + if ("set_" in cmd_key) or ("do_start" in cmd_key) or cmd_key in ["do_init", "do_goto", "do_open", "do_close"]:
  2057 + self.TEST_COMMANDS_LIST.remove(cmd)
  2058 + log.debug("- NEW test commands list scenario is:\n" + self.TEST_COMMANDS_LIST, '\n')
  2059 +
1790 2060 # Can be overriden by subclass (AgentDevice)
1791 2061 def TEST_test_results_other(self, commands):
1792 2062 pass
... ... @@ -1889,7 +2159,7 @@ def extract_parameters():
1889 2159  
1890 2160 #def build_agent(Agent_type:Agent, name="GenericAgent", RUN_IN_THREAD=True):
1891 2161 #def build_agent(Agent_type:Agent, RUN_IN_THREAD=True):
1892   -def build_agent(Agent_type:Agent):
  2162 +def build_agent(Agent_type:Agent) -> Agent :
1893 2163 #DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile = extract_parameters()
1894 2164 DEBUG_MODE, TEST_MODE, VERBOSE_MODE = extract_parameters()
1895 2165 log.debug("debug mode is" + os.getenv("PYROS_DEBUG"))
... ...
src/core/pyros_django/agent/AgentDevice.py
... ... @@ -295,14 +295,19 @@ class AgentDevice(Agent):
295 295 ================================================================================================
296 296 """
297 297  
  298 + '''
298 299 # @override superclass (Agent) method
299 300 def is_other_level_cmd(self, cmd: AgentCmd):
300 301 return self.is_device_level_cmd(cmd)
  302 + '''
301 303  
  304 + # I delegate this command to my DC
  305 + # => Execute it only if I am active and no currently running another device level cmd
  306 + # => Long execution time, so I will execute it in parallel (in a new thread or process)
302 307 def is_device_level_cmd(self, cmd: AgentCmd):
303 308 return self._device_ctrl.is_valid_cmd(DeviceCmd(cmd.full_name))
304 309  
305   - def process_other_level_cmd(self):
  310 + def process_device_level_cmd(self):
306 311 log.info("(DEVICE LEVEL CMD)")
307 312 try:
308 313 self.exec_device_cmd_if_possible(cmd)
... ...
src/core/pyros_django/agent/doc/Agent_activity_diag.pu
1 1  
2 2 @startuml
3 3  
4   -' --- Agent & AgentDevice ACTIVIY DIAGRAM (plantUML) ---
  4 +' --- Agent ACTIVIY DIAGRAM (plantUML) ---
5 5  
6 6 ' NEW syntax => http://plantuml.com/fr/activity-diagram-beta
7 7 ' OLD syntax => http://plantuml.com/fr/activity-diagram-legacy
... ... @@ -25,24 +25,143 @@
25 25  
26 26  
27 27 title
28   -__**Agent & AgentDevice run() function : Activity Diagram**__
29   -<size:10><i>Version 19-11-2019</i></size>
  28 +__**Agent Activity Diagram**__
  29 +<size:10><i>Version 10-06-2022</i></size>
30 30  
31 31 end title
32 32 '(E. Pallier)
33 33  
  34 +
  35 +
34 36 '|Agent|
35 37 start
36 38  
37   -:DO_EXIT = False
38   -DO_RESTART = True;
  39 +:STATUS = ATTENTIVE;
  40 +note right
  41 + Possible statuses : IDLE, ROUTINE, ATTENTIVE
  42 +end note
  43 +
  44 +:DO_RESTART = True;
  45 +
  46 +' *** RESTART loop ***
  47 +' while (DO_RESTART ?) is (yes)
  48 +repeat
39 49  
40   -while (DO_RESTART ?) is (yes)
41 50 :load_config();
42 51 :init();
43 52 :DO_MAIN_LOOP = True;
  53 +
  54 + ' *** MAIN loop ***
  55 + 'while (DO_MAIN_LOOP ?) is (yes)
  56 + repeat
  57 +
  58 + :reload_config_if_changed();
  59 +
  60 + :log_agent_status();
  61 + note right
  62 + Log this agent status in DB
  63 + end note
  64 +
  65 + if (STATUS is 'IDLE' ?) then (yes)
  66 + else (no))
  67 + #green:process_routine_before();
  68 + endif
  69 +
  70 + :CMD = get_next_cmd_if_exists();
  71 + if (CMD ?) then
  72 +
  73 + ' AGENT GENERAL level
  74 + if (is_agent_general_level(CMD) ?) then (yes)
  75 +
  76 + partition AGENT_GENERAL_level {
  77 + #green:process_agent_level_general_cmd(CMD);
  78 +
  79 + ' I must stop or restart
  80 + if (CMD is DO_ABORT || DO_EXIT || DO_RESTART ?) then (yes)
  81 +
  82 + :DO_MAIN_LOOP = False;
  83 +
  84 + if (CMD is DO_ABORT ?) then (yes)
  85 + :abort_current_running_cmd_if_exists();
  86 + else (no)
  87 + :cleanup_before_exit();
  88 + endif
  89 +
  90 + if (CMD is DO_RESTART ?) then (yes)
  91 + else (no)
  92 + :DO_RESTART = False;
  93 + endif
  94 +
  95 + ->// ==> GOTO "DO_MAIN_LOOP ?"//;
  96 +
  97 + break
  98 +
  99 + ' I don't have to stop
  100 + else (no)
  101 + ' end of "I must stop or restart ?"
  102 + ' ->NOK;
  103 + ' :Alert "toto";
  104 + endif
  105 + }
  106 +
  107 +
  108 +
  109 + ' AGENT SPECIFIC level
  110 + else (no)
  111 + partition AGENT_SPECIFIC_level {
  112 + if (STATUS is 'ATTENTIVE' ?) then (yes)
  113 + if (is_agent_specific_level(CMD) ?) then (yes)
  114 + #green:process_agent_level_specific_cmd(CMD);
  115 + if (KO?) then (yes)
  116 + #pink:cleanup_before_exit()
  117 + EXC UnimplementedAgentLevelSpecificCmdExc;
  118 + kill
  119 + else (no)
  120 + endif
  121 + else (no)
  122 + #pink:cleanup_before_exit()
  123 + EXC UnknownCmdException;
  124 + kill
  125 + endif
  126 + else (no)
  127 + endif
  128 + }
  129 +
  130 + ' Agent level Command processing
  131 + endif
  132 +
  133 + ' No command to process
  134 + else (no)
  135 +
  136 + ' Command processing
  137 + endif
  138 +
  139 + if (STATUS is 'IDLE' ?) then (yes)
  140 + else (no)
  141 + #green:process_routine_after();
  142 + endif
  143 +
  144 +
  145 + ' DO_MAIN_LOOP loop
  146 + 'endwhile (no)
  147 + repeat while (DO_MAIN_LOOP ?) is (yes) not (no)
  148 + ' ->//merged step//;
  149 +
  150 +' DO_RESTART loop
  151 +' endwhile (no)
  152 +repeat while (DO_RESTART ?) is (yes) not (no)
  153 +
  154 +stop
  155 +
  156 +
  157 +
  158 +
  159 +
  160 +/'
44 161 while (DO_MAIN_LOOP ?) is (yes)
  162 +
45 163 partition main_loop() {
  164 +
46 165 :reload_config();
47 166 note right
48 167 only if changed
... ... @@ -53,7 +172,10 @@ while (DO_RESTART ?) is (yes)
53 172 Log this agent status in DB
54 173 end note
55 174  
56   - :routine_process();
  175 + :routine_process_before();
  176 + note right
  177 + Routine process at "loop start"
  178 + end note
57 179  
58 180 :COMMAND = get_next_valid_command();
59 181 if (COMMAND ?) then
... ... @@ -82,8 +204,8 @@ while (DO_RESTART ?) is (yes)
82 204 :DO_MAIN_LOOP = False;
83 205 endif
84 206  
85   -
86 207 }
  208 +
87 209 endwhile (no)
88 210 endwhile (no)
89 211  
... ... @@ -91,9 +213,7 @@ stop
91 213  
92 214  
93 215 (A)
94   -/'
95   - exec_agent_cmd()
96   -'/
  216 +' exec_agent_cmd()
97 217 if (is_agent_specific_cmd(COMMAND) ?) then (yes)
98 218 :exec_cmd_from_its_name(COMMAND);
99 219 else (no)
... ... @@ -105,9 +225,7 @@ detach
105 225  
106 226  
107 227 (D)
108   -/'
109   - exec_device_cmd_if_possible()
110   -'/
  228 +' exec_device_cmd_if_possible()
111 229 if (I am IDLE ?) then (yes)
112 230 :SKIP command (ignored);
113 231 else if (another device cmd running ?) then (yes)
... ... @@ -130,4 +248,6 @@ else (no)
130 248 }
131 249 endif
132 250  
  251 +'/
  252 +
133 253 @enduml
... ...
src/core/pyros_django/common/models.py
1 1 ##from __future__ import unicode_literals
2 2  
3 3 # Stdlib imports
  4 +from numpy import False_
4 5 from src.device_controller.abstract_component.device_controller import DeviceCmd
5 6 from enum import Enum
6 7 from datetime import datetime, timedelta, date
... ... @@ -488,7 +489,7 @@ class AgentCmd(models.Model):
488 489 # TODO: maybe à mettre au format json (key:value)
489 490 result = models.CharField(max_length=400, blank=True)
490 491  
491   - # on creation: (AUTO) Automatically set at table line creation (line created by the sender)
  492 + # - on creation: (AUTO) Automatically set at table line creation (line created by the sender)
492 493 s_deposit_time = models.DateTimeField(
493 494 blank=True, null=True, auto_now_add=True)
494 495 # - on reading:
... ... @@ -810,18 +811,18 @@ class AgentCmd(models.Model):
810 811 self.refresh_from_db()
811 812 return self.result
812 813  
813   - def set_result(self, result: str):
  814 + def set_result(self, result: str, do_save:bool=True):
814 815 self.result = result
815   - self.save()
  816 + if do_save: self.save()
816 817  
817   - def set_read_time(self):
  818 + def set_read_time(self, do_save:bool=True):
818 819 self.r_read_time = datetime.utcnow().astimezone()
819 820 # Optimization: update only 1 field
820   - self.save(update_fields=["r_read_time"])
  821 + if do_save: self.save(update_fields=["r_read_time"])
821 822  
822   - def set_as_processed(self):
  823 + def set_as_processed(self, result:str=None):
823 824 printd(f"- Set command {self.name} as processed")
824   - self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
  825 + self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED, result=result)
825 826 # print(self)
826 827 """
827 828 self.state = self.CMD_STATUS_CODES.CMD_EXECUTED
... ... @@ -839,8 +840,8 @@ class AgentCmd(models.Model):
839 840 def set_as_pending(self):
840 841 self.set_state_to(self.CMD_STATUS_CODES.CMD_PENDING)
841 842  
842   - def set_as_skipped(self):
843   - self.set_state_to(self.CMD_STATUS_CODES.CMD_SKIPPED)
  843 + def set_as_skipped(self, result:str=None):
  844 + self.set_state_to(self.CMD_STATUS_CODES.CMD_SKIPPED, result=result)
844 845  
845 846 def set_as_killed_by(self, author_agent: str):
846 847 printd(f"- Set command {self.name} as killed")
... ... @@ -855,8 +856,13 @@ class AgentCmd(models.Model):
855 856 self.set_state_to(self.CMD_STATUS_CODES.CMD_EXECUTED)
856 857 '''
857 858  
858   - def set_state_to(self, status: str, author_agent_name: str = None):
  859 + def set_state_to(self, status: str, author_agent_name: str = None, result:str=None):
  860 + '''
  861 + If comment is present it will be set in the "result" field
  862 + '''
859 863 now_time = datetime.utcnow().astimezone()
  864 + if result:
  865 + self.set_result(result, False)
860 866 if status in (self.CMD_STATUS_CODES.CMD_RUNNING, self.CMD_STATUS_CODES.CMD_SKIPPED, self.CMD_STATUS_CODES.CMD_OUTOFDATE):
861 867 assert self.is_pending()
862 868 if status == self.CMD_STATUS_CODES.CMD_RUNNING:
... ...