Commit acb2cfd5d882b91c9a9886f48b1eb7e55eb7b85f

Authored by Alexis Koralewski
1 parent ff72df76
Exists in dev

fixing issue when launching agent with new-start command with test argument

Showing 2 changed files with 2646 additions and 2 deletions   Show diff stats
privatedev/plugin/agent/Agent.py 0 → 100755
... ... @@ -0,0 +1,2646 @@
  1 +#!/usr/bin/env python3
  2 +
  3 +#DEBUG=True
  4 +#DEBUG=False
  5 +
  6 +"""
  7 +=================================================================
  8 +Agent class
  9 +=================================================================
  10 +
  11 +This class is the base class for all AgentXXX classes (AgentSST, AgentScheduler, AgentImagesProcessor...)
  12 +
  13 +It runs an infinite loop (called main_loop) doing at each iteration
  14 +some routine tasks (process_routine before and after)
  15 +and executing commands sent by other agents.
  16 +
  17 +To start it up, from the project root :
  18 +
  19 +- In normal mode :
  20 +- ./PYROS start -fg agent -o tnc
  21 +(add -d after the PYROS command for debug mode)
  22 +
  23 +- In test mode (the agent will execute a scenario
  24 +as a suite of commands (in TEST_COMMANDS_LIST)
  25 +to send to himself (or other agents)
  26 +and execute them on reception at each iteration :
  27 +- ./PYROS -t start -fg agent -o tnc
  28 +
  29 +"""
  30 +
  31 +
  32 +###
  33 +# =================================================================
  34 +# SETUP FOR DJANGO
  35 +#
  36 +# (see https://docs.djangoproject.com/en/dev/topics/settings)
  37 +# (see also https://docs.djangoproject.com/en/dev/ref/settings)
  38 +# =================================================================
  39 +###
  40 +
  41 +
  42 +# For cmd parsing
  43 +from typing import List, Tuple
  44 +import ast
  45 +
  46 +import os
  47 +from pathlib import Path
  48 +import sys
  49 +
  50 +import logging
  51 +
  52 +from django.conf import settings as djangosettings
  53 +
  54 +# Conseil sur le net:
  55 +#https://stackoverflow.com/questions/16853649/how-to-execute-a-python-script-from-the-django-shell
  56 +#""
  57 +#import sys, os
  58 +#sys.path.append('/path/to/your/django/app')
  59 +#os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
  60 +#from django.conf import settings
  61 +#""
  62 +
  63 +# To avoid a "ModuleNotFoundError: No module named 'dashboard'"... (not even 1 app found) :
  64 +##sys.path.insert(0, os.path.abspath(".."))
  65 +##sys.path.insert(0, os.path.abspath("src"))
  66 +##sys.path.insert(0, "../src")
  67 +##sys.path.insert(0, "src")
  68 +# To avoid a "ModuleNotFoundError: No module named 'dashboard'"
  69 +## sys.path.append("..")
  70 +py_pwd = os.path.normpath(os.getcwd() + "/..")
  71 +if (py_pwd not in os.sys.path):
  72 + (os.sys.path).append(py_pwd)
  73 +# To avoid a "ModuleNotFoundError: No module named 'src'"
  74 +## sys.path.append("../../../..")
  75 +py_pwd = os.path.normpath(os.getcwd() + "/../../../..")
  76 +if (py_pwd not in os.sys.path):
  77 + (os.sys.path).append(py_pwd)
  78 +##sys.path.append("src")
  79 +
  80 +from src.pyros_logger import log, handler_filebyagent
  81 +#from src.pyros_logger import logger as logg, handler_filebyagent,
  82 +##from src import pyros_logger
  83 +
  84 +'''
  85 +def printd(*args, **kwargs):
  86 + if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs)
  87 +'''
  88 +
  89 +#printd("Starting with this sys.path", sys.path)
  90 +log.debug("Starting with this sys.path" + str(sys.path))
  91 +
  92 +# DJANGO setup
  93 +# self.printd("file is", __file__)
  94 +# mypath = os.getcwd()
  95 +# Go into src/
  96 +##os.chdir("..")
  97 +##os.chdir("src")
  98 +#printd("Current directory : " + str(os.getcwd()))
  99 +log.debug("Current directory : " + str(os.getcwd()))
  100 +
  101 +#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings")
  102 +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "src.core.pyros_django.pyros.settings")
  103 +# os.environ['SECRET_KEY'] = 'abc'
  104 +# os.environ['ENVIRONMENT'] = 'production'
  105 +import django
  106 +
  107 +django.setup()
  108 +
  109 +#printd("DB2 used is:", djangosettings.DATABASES["default"]["NAME"])
  110 +log.debug("DB2 used is:" + djangosettings.DATABASES["default"]["NAME"])
  111 +
  112 +
  113 +###
  114 +# =================================================================
  115 +# IMPORT PYTHON PACKAGES
  116 +#=================================================================
  117 +###
  118 +
  119 +# --- GENERAL PURPOSE IMPORT ---
  120 +#from __future__ import absolute_import
  121 +##import utils.Logger as L
  122 +import platform
  123 +import random
  124 +import threading
  125 +#import multiprocessing
  126 +import time
  127 +'''
  128 +from threading import Thread
  129 +import socket
  130 +'''
  131 +#import ctypes
  132 +#import copy
  133 +
  134 +# --- DJANGO IMPORT ---
  135 +from django.db import transaction
  136 +from django import db
  137 +# from django.core.exceptions import ObjectDoesNotExist
  138 +# from django.db.models import Q
  139 +#from django.shortcuts import get_object_or_404
  140 +#from django.conf import settings as djangosettings
  141 +
  142 +# --- SPECIFIC IMPORT ---
  143 +# Many ways to import configuration settings:
  144 +##import config
  145 +import config.old_config as config_old
  146 +#from config import PYROS_ENV, ROOT_DIR, DOC_DIR
  147 +#from config import *
  148 +
  149 +from common.models import AgentSurvey, AgentCmd, AgentLogs
  150 +
  151 +#from config.configpyros import ConfigPyros
  152 +from config.old_config.configpyros import ConfigPyros as ConfigPyrosOld
  153 +from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig
  154 +
  155 +#from dashboard.views import get_sunelev
  156 +#from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault
  157 +#from utils.JDManipulator import *
  158 +
  159 +##from agent.logpyros import LogPyros
  160 +##from src.logpyros import LogPyros
  161 +
  162 +'''
  163 +from device_controller.abstract_component.device_controller import (
  164 + DCCNotFoundException, UnknownGenericCmdException,
  165 + UnimplementedGenericCmdException, UnknownNativeCmdException
  166 +)
  167 +'''
  168 +
  169 +
  170 +
  171 +###
  172 +# =================================================================
  173 +# GENERAL MODULE CONSTANT & FUNCTIONS DEFINITIONS
  174 +# =================================================================
  175 +###
  176 +
  177 +#DEBUG_FILE = False
  178 +
  179 +##log = L.setupLogger("AgentLogger", "Agent")
  180 +
  181 +IS_WINDOWS = platform.system() == "Windows"
  182 +
  183 +
  184 +class Colors:
  185 + HEADER = "\033[95m"
  186 + BLUE = "\033[94m"
  187 + GREEN = "\033[92m"
  188 + WARNING = "\033[93m"
  189 + FAIL = "\033[91m"
  190 + ENDC = "\033[0m"
  191 + BOLD = "\033[1m"
  192 + UNDERLINE = "\033[4m"
  193 +
  194 +def printColor(color: Colors, message, file=sys.stdout, eol=os.linesep, forced=False):
  195 + #system = platform.system()
  196 + """
  197 + if (self.disp == False and forced == False):
  198 + return 0
  199 + """
  200 + #if system == "Windows":
  201 + if IS_WINDOWS:
  202 + print(message, file=file, end=eol)
  203 + else:
  204 + print(color + message + Colors.ENDC, file=file, end=eol)
  205 + return 0
  206 +
  207 +def printFullTerm(color: Colors, string: str):
  208 + #system = platform.system()
  209 + columns = 100
  210 + row = 1000
  211 + disp = True
  212 + value = int(columns / 2 - len(string) / 2)
  213 + printColor(color, "-" * value, eol="")
  214 + printColor(color, string, eol="")
  215 + value += len(string)
  216 + printColor(color, "-" * (columns - value))
  217 + return 0
  218 +
  219 +
  220 +"""
  221 +=================================================================
  222 + class StoppableThread
  223 +=================================================================
  224 +"""
  225 +'''
  226 +class StoppableThreadEvenWhenSleeping(threading.Thread):
  227 + # Thread class with a stop() method. The thread itself has to check
  228 + # regularly for the stopped() condition.
  229 + # It stops even if sleeping
  230 + # See https://python.developpez.com/faq/?page=Thread#ThreadKill
  231 + # See also https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s03.html
  232 +
  233 + def __init__(self, *args, **kwargs):
  234 + #super(StoppableThreadSimple, self).__init__(*args, **kwargs)
  235 + super().__init__(*args, **kwargs)
  236 + self._stop_event = threading.Event()
  237 +
  238 + #def stop(self):
  239 + def terminate(self):
  240 + self._stop_event.set()
  241 +
  242 + def stopped(self):
  243 + return self._stop_event.is_set()
  244 +
  245 + def wait(self, nbsec:float=2.0):
  246 + self._stop_event.wait(nbsec)
  247 +'''
  248 +
  249 +
  250 +###
  251 +# =================================================================
  252 +# EXCEPTIONS classes
  253 +# =================================================================
  254 +###
  255 +
  256 +class AgentCmdException(Exception):
  257 + ''' Base class for all Agent command exceptions '''
  258 + # pass
  259 + def __init__(self, cmd:AgentCmd):
  260 + self.cmd = cmd
  261 + '''
  262 + def __str__(self):
  263 + return f"The Agent command '{self.cmd.name}' is unknown to the agent"
  264 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  265 + '''
  266 +
  267 +class UnknownCmdException(AgentCmdException):
  268 + ''' Raised when an Agent (specific) cmd is NOT known by the agent '''
  269 + def __str__(self):
  270 + return f"The Agent command '{self.cmd.name}' is unknown to the agent"
  271 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  272 +
  273 +class AgentCmdUnimplementedException(AgentCmdException):
  274 + ''' Raised when an Agent Specific cmd is known by the agent but not implemented '''
  275 + def __str__(self):
  276 + return f"The Agent command '{self.cmd.name}' is known by the agent but not implemented"
  277 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  278 +
  279 +class AgentCmdBadArgsException(AgentCmdException):
  280 + ''' Raised when an Agent cmd has bad, missing, or too many argument(s) '''
  281 + def __str__(self):
  282 + return f"The Agent command '{self.cmd.name}' has bad, missing, or too many argument(s)"
  283 + #return f"({type(self).__name__}): Device Generic command has no implementation in the controller"
  284 +
  285 +
  286 +
  287 +###
  288 +# =================================================================
  289 +# class Agent
  290 +# =================================================================
  291 +###
  292 +
  293 +class Agent:
  294 + """
  295 + See Agent_activity_diag.pu for PlantUML activity diagram
  296 +
  297 + Behavior of an agent:
  298 + - If idle :
  299 + - still does routine_process() and general_process()
  300 + - does not do specific_process()
  301 + - Once a command has been sent to another agent :
  302 + - It waits (non blocking) for the end of execution of the command and get its result
  303 + - If command is timed out or has been skipped or killed, then it is NOT re-executed at next iteration (except if needed explicitely)
  304 + """
  305 +
  306 + # ---
  307 + # --- CLASS (STATIC) attributes (CONSTANTS)
  308 + # --- If agent is instance of Agent:
  309 + # --- - CLASS attributes are accessible via agent.__class__.__dict__
  310 + # --- - INSTANCE attributes are accessible via agent.__dict__
  311 + # ---
  312 +
  313 + # Default modes
  314 + DEBUG_MODE = False
  315 + WITH_SIMULATOR = False
  316 + #TEST_MODE = False
  317 +
  318 + # By default, a command is valid during 5s (and then perempted)
  319 + DEFAULT_CMD_VALIDITY_DURATION = 5
  320 +
  321 + # Wait a fixed number of seconds before each loop ?
  322 + #WITH_RANDOM_WAIT = False
  323 + # 1 sec by default
  324 + _DELAY_NB_SEC = 1
  325 + # - YES if TEST mode (in init())
  326 +
  327 + # Default LOG level is INFO
  328 + #PYROS_DEFAULT_GLOBAL_LOG_LEVEL = LogPyros.LOG_LEVEL_INFO # INFO
  329 +
  330 + # This Agent Specific commands list, that he is able to execute
  331 + # To be overriden by subclasses (here are just a few examples of commands)
  332 + # Format : ("cmd",timeout) with :
  333 + # - cmd : the command name
  334 + # - timeout : the command timeout (in sec)
  335 + AGENT_SPECIFIC_COMMANDS = [
  336 + #"do_specific1",
  337 + #"set_specific2",
  338 + #"do_specific3",
  339 + ("do_specific1", 10),
  340 + ("set_specific2", 5),
  341 + ("do_specific3", 3),
  342 + ]
  343 +
  344 + #
  345 + # --- FOR TEST ONLY ---
  346 + #
  347 +
  348 + # By default, NOT in test mode
  349 + #TEST_MODE = False
  350 +
  351 + # Maximum duration of this agent (only for SIMULATION mode)
  352 + # If set to None, it will never exit except if asked (or CTRL-C)
  353 + # If set to 20, it will exit after 20s
  354 + TEST_MAX_DURATION_SEC = None
  355 + #TEST_MAX_DURATION_SEC = 30
  356 + # Run this agent in simulator mode
  357 + #TEST_MODE = True
  358 + WITH_SIMULATOR = False
  359 + # Run the assertion tests at the end
  360 + TEST_WITH_FINAL_TEST = False
  361 +
  362 + CMD_STATUS = AgentCmd.CMD_STATUS_CODES
  363 +
  364 + # (EP 2022/07) Default SCENARIO to be executed (only in TEST mode), and then STOP
  365 + #
  366 + # It is a list of commands to be sent by this agent to other agents ("self" means himself)
  367 + #
  368 + # Format : List of tuples (command, validity, expected_res, expected_status), with :
  369 + #
  370 + # - command : the format is "recipient cmd args", with :
  371 + # - recipient : name of the Agent that the command is to be sent to (use "self" to mean himself)
  372 + # - cmd : the command name
  373 + # - args : (optional) the list of command arguments, separated by blanks : arg1 arg2 arg3 ...
  374 + #
  375 + # - validity : the command is valid for this duration, afterwards you can forget it
  376 + #
  377 + # - expected_res : the expected result
  378 + #
  379 + # - expected_status : the status of the command expected after execution (expired, killed, skipped, executed...)
  380 + #
  381 + # Ex :
  382 + # - "AgentScheduler set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to the agent AgentScheduler
  383 + # - "AgentX set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to the agent AgentX
  384 + # - "self set_state ATTENTIVE" => means to send the command "set_state ATTENTIVE" to MYSELF
  385 + # - "self do_restart_loop" => means to send the command "do_restart_loop" to MYSELF (no args)
  386 + #
  387 + TEST_COMMANDS_LIST = [
  388 +
  389 + # 1) First, 3 EXCEPTION CASES (uncomment to activate exception)
  390 + # Each of these lines will stop execution with an exception
  391 + # ------------------------------------------------------------
  392 +
  393 + # - Agent command, unknown => ko, UnknownCmdException
  394 + #("self do_unknown", 10, None,None),
  395 +
  396 + # - Agent general command malformed (missing arg) => ko, AgentCmdBadArgsException
  397 + #("Agent set_mode", 10, None,None),
  398 +
  399 + # - Agent specific command, known but not implemented => ko, AgentCmdUnimplementedException
  400 + #("self set_specific2", 10, None,None),
  401 +
  402 + # - Agent specific command, implemented but missing args => ko, AgentCmdBadArgsException
  403 + #(" self do_specific1 1 ", 10, None,None),
  404 +
  405 +
  406 + # 2) NORMAL CASES (test scenario)
  407 + # All these commands should be executed without error, from the 1st to the last one
  408 + # -------------------------------
  409 +
  410 + # This command has a validity of 0s and thus should be tagged as "expired"
  411 + ("self set_mode ATTENTIVE", 0, "MODE = ATTENTIVE", CMD_STATUS.CMD_OUTOFDATE),
  412 +
  413 + # Agent general command
  414 + ("self set_mode ATTENTIVE", 200, "MODE = ATTENTIVE", CMD_STATUS.CMD_EXECUTED),
  415 + # => should get "ATTENTIVE"
  416 + ("self get_mode", 100, "MODE = ATTENTIVE", None),
  417 +
  418 + # => should get "7"
  419 + ("self do_eval 3+5-1", 200, 7, None),
  420 +
  421 + # END, will not go further
  422 + #("self do_exit", 2, "STOPPING"),
  423 +
  424 + # Agent specific commands => should be executed
  425 + ("self do_specific3", 200, None, None),
  426 + ("self do_exit", 500, "STOPPING", None),
  427 + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, 7, None),
  428 +
  429 + ("self set_mode ROUTINE", 200, "MODE = ROUTINE", None),
  430 + # => should get "ROUTINE"
  431 + ("self get_mode", 200, "MODE = ROUTINE", None),
  432 + # Agent specific command => should be skipped (because not ATTENTIVE)
  433 + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, "SKIPPED", None),
  434 +
  435 + # From now on, should not run anymore process_before/after
  436 + # => and should skip next specific commands
  437 + ("self set_mode IDLE", 200, "MODE = IDLE", None),
  438 + # => should get "IDLE"
  439 + ("self get_mode", 200, "MODE = IDLE", None),
  440 + # Agent specific command => should be skipped (because not ATTENTIVE)
  441 + ("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]", 200, 'SKIPPED', None),
  442 +
  443 + # TODO: test priority commands : do_abort, do_flush_cmds, ...
  444 + # - Stop executing new commands (just let them accumulate)
  445 + ##("self do_stop_exec",),
  446 + ##("self set_mode ATTENTIVE",),
  447 + ##("self get_mode",),
  448 + ##("self do_specific1 1 2 3.5 titi (3,'titi',5) [1,3,5,7,9]",),
  449 + ##("self set_mode ROUTINE",),
  450 + ##("self get_mode",),
  451 + ##("self do_abort",),
  452 + # - Now, resume execution of commands :
  453 + # we should have the previous list pending,
  454 + # but as "do_abort" is priority, it should be executed first even if last !
  455 + ##("self do_resume_exec",),
  456 +
  457 + # Restart the restart loop (from init())
  458 + ("self do_restart_loop", 200, "RESTARTING", None),
  459 +
  460 + # Now stop
  461 + ("self do_exit", 200, "STOPPING", None),
  462 +
  463 + '''
  464 + # specific0 not_executed_because_idle
  465 + "specific0",
  466 +
  467 + "set_state:active",
  468 +
  469 + # specific1 executed_because_not_idle, should complete ok
  470 + "specific1",
  471 +
  472 + # specific2 will be executed only when specific1 is finished,
  473 + # and should be aborted before end of execution,
  474 + # because of the 1st coming "do_abort" command below
  475 + "specific2",
  476 +
  477 + # specific3 should be executed only when specific2 is finished (in fact, aborted),
  478 + # and should be aborted before end of execution,
  479 + # because of the 2nd coming "do_abort" command below
  480 + "specific3",
  481 +
  482 + # These commands should not have the time to be processed
  483 + # because the "do_exit" command below should be executed before
  484 + "specific4",
  485 + "specific5",
  486 + "specific6",
  487 + "specific7",
  488 + "specific8",
  489 +
  490 + # Should abort the current running command (which should normally be specific2)
  491 + # even if commands above (specific3, ..., specific8) are already pending
  492 + "do_abort",
  493 +
  494 + # These commands (except abort) won't be executed
  495 + # because too many commands are already pending (above)
  496 + "specific9",
  497 + "do_abort",
  498 + "set_state:active",
  499 + "set_state:idle",
  500 +
  501 + # Should stop the agent even before the previous pending commands are executed
  502 + "do_exit",
  503 +
  504 + # Because of the previous "do_exit" command,
  505 + # these following commands should not be executed,
  506 + # and not even be added to the database command table
  507 + "set_state:active",
  508 + "specific10"
  509 + '''
  510 + ]
  511 + #TEST_COMMANDS = iter(TEST_COMMANDS_LIST)
  512 +
  513 +
  514 +
  515 + '''
  516 + # with thread
  517 + RUN_IN_THREAD = True
  518 + # with process
  519 + #RUN_IN_THREAD = False
  520 + _thread_total_steps_number = 1
  521 + '''
  522 +
  523 +
  524 + #COMMANDS_PEREMPTION_HOURS = 48
  525 + #COMMANDS_PEREMPTION_HOURS = 60/60
  526 +
  527 + name = "Generic Agent"
  528 + status = None
  529 + mode = None
  530 + config = None
  531 +
  532 + # STATUS
  533 + STATUS_LAUNCH = "LAUNCHED"
  534 + STATUS_INIT = "INITIALIZING"
  535 + STATUS_MAIN_LOOP = "IN_MAIN_LOOP"
  536 + STATUS_GET_NEXT_COMMAND = "IN_GET_NEXT_COMMAND"
  537 + STATUS_GENERAL_PROCESS = "IN_GENERAL_PROCESS"
  538 + STATUS_ROUTINE_PROCESS = "IN_ROUTINE_PROCESS"
  539 + ###STATUS_SPECIFIC_PROCESS = "IN_SPECIFIC_PROCESS"
  540 + STATUS_EXIT = "EXITING"
  541 +
  542 +
  543 + # MODE
  544 + #
  545 + # In all modes, the Agent listens to commands sent to him and executes Agent level GENERAL ones.
  546 + # - MODE_IDLE : "idle" mode, does nothing, only executes Agent level GENERAL commands (DO_RESTART, DO_EXIT, DO_ABORT, DO_FLUSH, SET_ACTIVE, ...)
  547 + # - MODE_ROUTINE : idem IDLE + executes routine process (before & after)
  548 + # - MODE_ATTENTIVE : idem ROUTINE + executes Agent level SPECIFIC commands (commands specific to this agent, that only this agent understands and can execute)
  549 + #
  550 + # Default mode is MODE_ATTENTIVE (most active mode)
  551 + MODE_IDLE = "IDLE"
  552 + MODE_ROUTINE = "ROUTINE"
  553 + MODE_ATTENTIVE = "ATTENTIVE"
  554 +
  555 + ''' Moved to more central file : config.config_base
  556 + PYROS_DJANGO_BASE_DIR = Path("src/core/pyros_django") # pathlib
  557 + DEFAULT_CONFIG_FILE_NAME = "config_unit_simulunit1.xml"
  558 + CONFIG_DIR_NAME = "config"
  559 + '''
  560 +
  561 + # My own ConfigPyros instance (Observatory Configuration)
  562 + _oc = None
  563 +
  564 + # Parameters from config file
  565 + # for /src/
  566 + #_path_data = '../../config'
  567 + # for /src/core/pyros_django/
  568 + #_path_data = '../../../../config'
  569 + # Path to config
  570 + _path_data = ''
  571 + _computer_alias = ''
  572 + _computer_description = ''
  573 +
  574 + # Current and next command to send
  575 + _cmdts: AgentCmd = None
  576 + _next_cmdts = None
  577 +
  578 + _agent_survey = None
  579 + _pending_commands = []
  580 +
  581 + '''
  582 + _current_device_cmd = None
  583 + _current_device_cmd_thread = None
  584 + '''
  585 +
  586 + # List of agents I will send commands to
  587 + _my_client_agents_aliases = []
  588 + _my_client_agents = {}
  589 +
  590 + _iter_num = None
  591 +
  592 + # Log object
  593 + _log = None
  594 +
  595 + # new obsconfig init for agent:
  596 + ##def __init__(self, RUN_IN_THREAD=True):
  597 + def __init__(self):
  598 + # Agent is by default in mode ATTENTIVE (most active mode)
  599 + self.mode = self.MODE_ATTENTIVE
  600 + log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__))
  601 + log.debug("start Agent init")
  602 + obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"]
  603 + path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"]
  604 + unit = os.environ["unit_name"]
  605 + oc = OBSConfig(obs_config_file_path,unit)
  606 + self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit)
  607 +
  608 + self.name = self.__class__.__name__
  609 + # set real unit name (the current unit used)
  610 + if unit == "":
  611 + unit = oc.unit_name
  612 + agent_name_from_config = self.get_config().get_agent_name_from_config(self.__class__.__name__)
  613 + if agent_name_from_config:
  614 + self.name = agent_name_from_config
  615 + self.unit = unit
  616 + print(f"Agent name : {self.name}")
  617 + print(f"Unit name : {self.unit}")
  618 +
  619 + # (EP) moved to AgentDevice
  620 + ###self._set_agent_device_aliases_from_config(self.name)
  621 +
  622 + self._set_mode_from_config(self.name)
  623 + log.debug("Step __init__")
  624 +
  625 + self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST)
  626 + ##self.RUN_IN_THREAD = RUN_IN_THREAD
  627 + self._set_status(self.STATUS_LAUNCH)
  628 + ####self._set_idle()
  629 +
  630 + # Create 1st survey if none
  631 + #tmp = AgentSurvey.objects.filter(name=self.name)
  632 + #if len(tmp) == 0:
  633 + #nb_agents = AgentSurvey.objects.filter(name=self.name).count()
  634 + #if nb_agents == 0:
  635 + if AgentSurvey.objects.filter(name=self.name).exists():
  636 + self._agent_survey = AgentSurvey.objects.get(name=self.name)
  637 + else:
  638 + self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration=60, mode=self.mode, status=self.status, iteration=-1)
  639 + log.debug("Agent survey is" + str(self._agent_survey))
  640 + #self.printd("Agent survey is", self._agent_survey)
  641 +
  642 + ("end Agent __init__")
  643 +
  644 +
  645 + #def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True):
  646 + #def __init__(self, config_filename:str=None, RUN_IN_THREAD=True, DEBUG_MODE=False):
  647 + # def __init__(self, config_filename:str=None, RUN_IN_THREAD=True):
  648 +
  649 + # '''
  650 + # print('PYROS_ENV', PYROS_ENV)
  651 + # print('ROOT_DIR', ROOT_DIR)
  652 + # print('DOC_DIR', DOC_DIR)
  653 + # '''
  654 +
  655 + # # Set logger
  656 + # ##pyros_logger.set_logger_config()
  657 + # ##logg = logging.getLogger('pyroslogger')
  658 + # log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__))
  659 +
  660 + # log.debug("start Agent init")
  661 +
  662 + # # SET CONFIG INSTANCE
  663 +
  664 + # # - OLD CONFIG
  665 + # log.debug(f'config file is {config_filename}')
  666 + # ##log.info('PYROS_ENV', config.PYROS_ENV)
  667 + # log.debug('PYROS_ENV' + config_old.PYROS_ENV)
  668 + # log.debug('ROOT_DIR' + config_old.ROOT_DIR)
  669 + # log.debug('DOC_DIR' + config_old.DOC_DIR)
  670 + # ##if config.is_dev_env(): print("DEV ENV")
  671 + # if config_old.is_dev_env(): log.debug("DEV ENV")
  672 + # if config_old.is_prod_env(): log.debug("PROD ENV")
  673 + # if config_old.is_debug(): log.debug("IN DEBUG MODE")
  674 +
  675 + # # - NEW CONFIG
  676 + # obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"]
  677 + # path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"]
  678 + # unit = os.environ["unit_name"]
  679 + # oc = OBSConfig(obs_config_file_path)
  680 + # self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit)
  681 + # log.debug("Step __init__")
  682 +
  683 + # self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST)
  684 + # self.RUN_IN_THREAD = RUN_IN_THREAD
  685 + # self._set_status(self.STATUS_LAUNCH)
  686 + # self._set_idle()
  687 +
  688 + # self._set_agent_device_aliases_from_config(self.name)
  689 + # self._set_mode_from_config(self.name)
  690 + # #self.name = name
  691 + # self.name = self.__class__.__name__
  692 +
  693 +
  694 + # '''
  695 + # printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***')
  696 + # ##self.DEBUG_MODE = DEBUG_MODE
  697 + # self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0')=='1'
  698 + # '''
  699 + # #self.DEBUG_MODE = config.PYROS_ENV
  700 + # ##self.log = LogPyros(self.name, AgentLogs)
  701 + # ##self.DEBUG_MODE = config.is_debug()
  702 + # self.DEBUG_MODE = config_old.is_debug()
  703 + # ##self.log.debug_level = DEBUG_MODE
  704 + # '''
  705 + # # Default LOG level is INFO
  706 + # log_level = LogPyros.LOG_LEVEL_INFO # INFO
  707 + # self.log.set_global_log_level(LogPyros.LOG_LEVEL_DEBUG) if self.DEBUG_MODE else self.log.set_global_log_level(log_level)
  708 + # '''
  709 + # #global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else self.PYROS_DEFAULT_GLOBAL_LOG_LEVEL
  710 + # ##global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config.PYROS_DEFAULT_GLOBAL_LOG_LEVEL
  711 + # ##global_log_level = LogPyros.LOG_LEVEL_DEBUG if self.DEBUG_MODE else config_old.PYROS_DEFAULT_GLOBAL_LOG_LEVEL
  712 + # ##self.log.set_global_log_level(global_log_level)
  713 + # ##self.printd("LOG LEVEL IS:", self.log.debug_level)
  714 + # ##self.print("LOG LEVEL IS:", self.log.get_global_log_level())
  715 +
  716 +
  717 + # # ------------- OLD CONFIG -------------------
  718 + # # Est-ce bien utile ???
  719 + # # New way with PathLib
  720 + # # my_parent_abs_dir = Path(__file__).resolve().parent
  721 + # #TODO: on doit pouvoir faire mieux avec pathlib (sans utiliser str())
  722 + # ##self._path_data = str( Path( str(my_parent_abs_dir).split(str(self.PYROS_DJANGO_BASE_DIR))[0] ) / self.CONFIG_DIR_NAME )
  723 + # ##self._path_data = config.CONFIG_DIR
  724 + # # self._path_data = config_old.CONFIG_DIR
  725 +
  726 + # #self._set_mode(self.MODE_IDLE)
  727 + # # config_filename = self.get_config_filename(config_filename)
  728 + # #self.printd(f"*** Config file used is={config_filename}")
  729 + # # log.debug(f"*** Config file used is={config_filename}")
  730 + # # self.config = ConfigPyrosOld(config_filename)
  731 + # # if self.config.get_last_errno() != self.config.NO_ERROR:
  732 + # # raise Exception(f"Bad config file name '{config_filename}', error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
  733 +
  734 + # #TODO: : à mettre dans la config
  735 + # '''
  736 + # 'AgentDeviceTelescope1': 'AgentDeviceTelescopeGemini',
  737 + # 'AgentDeviceFilterSelector1': 'AgentDeviceSBIG',
  738 + # 'AgentDeviceShutter1': 'AgentDeviceSBIG',
  739 + # 'AgentDeviceSensor1': 'AgentDeviceSBIG',
  740 + # '''
  741 + # #self._my_client_agents = {}
  742 +
  743 + # ### self._agent_logs = AgentLogs.objects.create(name=self.name, message="Step __init__")
  744 + # #self.printdb("Step __init__")
  745 + # log.debug("Step __init__")
  746 +
  747 + # self.TEST_COMMANDS = iter(self.TEST_COMMANDS_LIST)
  748 + # self.RUN_IN_THREAD = RUN_IN_THREAD
  749 + # self._set_status(self.STATUS_LAUNCH)
  750 + # self._set_idle()
  751 +
  752 + # self._set_agent_device_aliases_from_config(self.name)
  753 + # self._set_mode_from_config(self.name)
  754 + # # TODO: remove
  755 + # #self._set_idle()
  756 + # self._set_active()
  757 +
  758 + # # Create 1st survey if none
  759 + # #tmp = AgentSurvey.objects.filter(name=self.name)
  760 + # #if len(tmp) == 0:
  761 + # #nb_agents = AgentSurvey.objects.filter(name=self.name).count()
  762 + # #if nb_agents == 0:
  763 + # if AgentSurvey.objects.filter(name=self.name).exists():
  764 + # self._agent_survey = AgentSurvey.objects.get(name=self.name)
  765 + # else:
  766 + # self._agent_survey = AgentSurvey.objects.create(name=self.name, validity_duration=60, mode=self.mode, status=self.status, iteration=-1)
  767 + # log.debug("Agent survey is" + str(self._agent_survey))
  768 + # #self.printd("Agent survey is", self._agent_survey)
  769 +
  770 + # ("end Agent init")
  771 +
  772 +
  773 +
  774 + def set_config(self, oc: OBSConfig, obs_config_file_path: str, path_to_obs_config_folder: str, unit: str):
  775 + self._oc = {
  776 + 'config' : oc,
  777 + 'env' : [
  778 + obs_config_file_path,
  779 + path_to_obs_config_folder,
  780 + unit
  781 + ]
  782 + }
  783 + '''
  784 + config_env = {
  785 + obs_config_file_path: obs_config_file_path,
  786 + path_to_obs_config_folder: path_to_obs_config_folder,
  787 + unit: unit
  788 + }
  789 + '''
  790 +
  791 + def get_config(self): return self._oc['config']
  792 +
  793 + def get_config_env(self): return self._oc['env']
  794 +
  795 + def show_config(self):
  796 + VERBOSE = False
  797 +
  798 + oc = self.get_config()
  799 + obs_config_file_path, path_to_obs_config_folder, unit = self.get_config_env()
  800 + #self.printd(obs_config_file_path)
  801 + log.debug(obs_config_file_path)
  802 + log.debug(path_to_obs_config_folder)
  803 + log.debug(unit)
  804 + #log.warning("petit warning bidon")
  805 + print("\n")
  806 + print("- Observatory:" + oc.get_obs_name())
  807 + my_unit_name = oc.get_units_name()[0]
  808 + my_unit = (oc.get_units()[my_unit_name])
  809 +
  810 + #print("- Unit description:", my_unit)
  811 +
  812 + if VERBOSE:
  813 + print("\n")
  814 + print("- Computers:", oc.get_computers())
  815 +
  816 + print("\n")
  817 + print("- Active Computers:", oc.get_active_computers())
  818 +
  819 + print("\n")
  820 + print("- Active Devices:", oc.get_active_devices())
  821 +
  822 + print("\n")
  823 + print("- Unit:", my_unit_name)
  824 + VERBOSE and print(oc.get_unit_by_name(my_unit_name))
  825 +
  826 + if VERBOSE:
  827 + print("\n")
  828 + print("- Unit topology:", oc.get_topology(my_unit_name))
  829 +
  830 + print("\n")
  831 + print("- Unit active Agents:", oc.get_active_agents(my_unit_name))
  832 + VERBOSE and print(oc.get_agents(my_unit_name))
  833 +
  834 + print("\n")
  835 + print("- Unit Agents per computer:", oc.get_agents_per_computer(my_unit_name))
  836 +
  837 + print("\n")
  838 + print("- Unit Agents per device:", oc.get_agents_per_device(my_unit_name))
  839 +
  840 + if VERBOSE:
  841 + print("\n")
  842 + print("- Unit Channel groups:", oc.get_channel_groups(my_unit_name))
  843 +
  844 + print("\n")
  845 + print("- Unit Channels:", oc.get_channels(my_unit_name))
  846 +
  847 + #print("\n")
  848 + #print("- Unit/Channel info:", oc.get_channel_information(my_unit_name, 'OpticalChannel_up'))
  849 +
  850 + if VERBOSE:
  851 + print("\n")
  852 + print("- Unit Components agents:", oc.get_components_agents(my_unit_name))
  853 +
  854 + if VERBOSE:
  855 + print("\n")
  856 + print("- Unit database:", oc.get_database_for_unit(my_unit_name))
  857 +
  858 + print("\n")
  859 + print("- Devices names:", oc.get_devices_names())
  860 + if VERBOSE:
  861 + print("\n")
  862 + print("- Devices names & files:", oc.get_devices_names_and_file())
  863 + print("\n")
  864 + print("- Devices:", oc.get_devices())
  865 +
  866 + print("\n")
  867 +
  868 +
  869 +
  870 +
  871 +
  872 +
  873 + def get_config_filename(self, config_filename: str):
  874 + if not config_filename:
  875 + #config_filename = self.DEFAULT_CONFIG_FILE_NAME
  876 + ##config_filename = config.DEFAULT_CONFIG_FILE_NAME
  877 + config_filename = config_old.DEFAULT_CONFIG_FILE_NAME
  878 + # If config file name is RELATIVE (i.e. without path, just the file name)
  879 + # => give it an absolute path (and remove "src/agent/" from it)
  880 + if config_filename == os.path.basename(config_filename):
  881 + ##config_filename = os.path.join(config.CONFIG_DIR, config_filename)
  882 + config_filename = os.path.join(config_old.CONFIG_DIR, config_filename)
  883 + '''
  884 + # Build abs path including current agent dir
  885 + config_filename = os.path.abspath(self.CONFIG_DIR_NAME + os.sep + config_filename)
  886 + # Remove "src/agent_name/" from abs dir :
  887 + # (1) Remove "src/core/pyros_django/"
  888 + config_filename = config_filename.replace(str(self.PYROS_DJANGO_BASE_DIR)+os.sep, os.sep)
  889 + # (2) Remove "agent_name/"
  890 + #TODO: bidouille, faire plus propre
  891 + config_filename = config_filename.replace(os.sep+"agent"+os.sep, os.sep)
  892 + config_filename = config_filename.replace(os.sep+"monitoring"+os.sep, os.sep)
  893 + '''
  894 + return os.path.normpath(config_filename)
  895 +
  896 + def __repr__(self):
  897 + return "I am agent " + self.name
  898 +
  899 + def __str__(self):
  900 + return self.__repr__()
  901 + #return "I am agent " + self.name
  902 +
  903 + # Normal print
  904 + ##def print(self, *args, **kwargs): self.log.print(*args, **kwargs)
  905 + """
  906 + if args:
  907 + self.printd(f"({self.name}): ", *args, **kwargs)
  908 + else:
  909 + self.printd()
  910 + """
  911 + """
  912 + # DEBUG print shortcut
  913 + ##def printd(self, *args, **kwargs): self.log.printd(*args, **kwargs)
  914 + #if DEBUG: self.printd(d(*args, **kwargs)
  915 + def log_d(self, *args, **kwargs): self.log.log_d(*args, **kwargs)
  916 + def log_i(self, *args, **kwargs): self.log.log_i(*args, **kwargs)
  917 + def log_w(self, *args, **kwargs): self.log.log_w(*args, **kwargs)
  918 + def log_e(self, *args, **kwargs): self.log.log_e(*args, **kwargs)
  919 + def log_c(self, *args, **kwargs): self.log.log_c(*args, **kwargs)
  920 + def printdb(self, *args, **kwargs): self.log.db( *args, **kwargs)
  921 + """
  922 +
  923 +
  924 +
  925 + def _get_real_agent_name(self, agent_alias_name:str)->str:
  926 + #self.printd("key is", agent_alias_name)
  927 + '''
  928 + if not self._my_client_agents: return agent_alias_name
  929 + return self._my_client_agents[agent_alias_name]
  930 + '''
  931 + return self._my_client_agents.get(agent_alias_name)
  932 +
  933 +
  934 +
  935 + def run(self, nb_iter:int=None, FOR_REAL:bool=True):
  936 + """
  937 + FOR_REAL: set to False if you don't want Agent to send commands to devices but just print messages without really doing anything
  938 + """
  939 +
  940 + ("in run()")
  941 + #return
  942 +
  943 + # TEST MODE ONLY
  944 + # IF in test mode but with REAL devices (no SIMULATOR), delete all dangerous commands from the test commands list scenario:
  945 + self._TEST_prepare()
  946 +
  947 + #self._DO_EXIT = False
  948 +
  949 + # No agent specific cmd currently running (thread)
  950 + self.AGENT_SPECIFIC_CMD_RUNNING = False
  951 +
  952 + # No Routine process (before or after) currently running (thread)
  953 + self.ROUTINE_PROCESS_RUNNING = False
  954 +
  955 + ################
  956 + # RESTART loop #
  957 + ###############@
  958 + # REPEAT UNTIL not DO_RESTART_LOOP
  959 + self.DO_RESTART_LOOP = True
  960 + while self.DO_RESTART_LOOP:
  961 + # By default, no restart after exit from main loop
  962 + self.DO_RESTART_LOOP = False
  963 +
  964 + self.start_time = time.time()
  965 + #log.debug("on est ici: " + os.getcwd())
  966 +
  967 + self._load_config()
  968 +
  969 + self.print_TEST_MODE()
  970 +
  971 + self.init()
  972 + ''' testing log:
  973 + self.log_e("ERROR")
  974 + self.log_c("FATAL critical ERROR")
  975 + '''
  976 + #self.log_w("WARNING", "watch your step !")
  977 + #log.warning("WARNING"+ "watch your step !")
  978 +
  979 + # Avoid blocking on false "running" commands
  980 + # (old commands that stayed with "running" status when agent was killed)
  981 + AgentCmd.delete_commands_with_running_status_for_agent(self.name)
  982 +
  983 + self._iter_num = 1
  984 +
  985 + #############
  986 + # MAIN loop #
  987 + ############@
  988 + self.DO_MAIN_LOOP = True
  989 + while self.DO_MAIN_LOOP:
  990 + try:
  991 + self._main_loop(nb_iter,FOR_REAL)
  992 + #if not self.DO_MAIN_LOOP: break
  993 + except KeyboardInterrupt: # CTRL-C
  994 + # In case of CTRL-C, kill the current thread (process) before dying (in error)
  995 + #log.info("CTRL-C Interrupted, I kill the current thread (process) before exiting (if exists)")
  996 + log.info("CTRL-C Interrupted, trying to stop cleanly")
  997 + #self._kill_running_device_cmd_if_exists("USER_CTRLC")
  998 + #self.do_things_before_exit("USER_CTRLC")
  999 + self._cleanup_before_exit("USER_CTRLC")
  1000 + exit(1)
  1001 +
  1002 + # TEST mode only
  1003 + self._TEST_test_results()
  1004 + #if self._DO_EXIT: exit(0)
  1005 +
  1006 +
  1007 +
  1008 + #def _kill_running_device_cmd_if_exists(self, abort_cmd_sender):
  1009 + # to be overriden by subclass
  1010 + def do_things_before_exit(self, stopper_agent_name=None):
  1011 + pass
  1012 +
  1013 +
  1014 + def _main_loop(self, nb_iter:int=None, FOR_REAL:bool=True):
  1015 +
  1016 + self._main_loop_start(nb_iter)
  1017 + #if not self.DO_MAIN_LOOP: return
  1018 +
  1019 + self._reload_config_if_changed() # only if changed
  1020 +
  1021 + # better to do this in a subclass
  1022 + #self.show_config()
  1023 +
  1024 + # Log this agent status (update my current mode and status in DB)
  1025 + self._log_agent_status()
  1026 +
  1027 + #self.printd("====== START COMMMANDS PROCESSING ======")
  1028 +
  1029 + # SIMU
  1030 + #self.send_cmd_to("AgentScheduler", "do_replan")
  1031 +
  1032 + # ROUTINE process BEFORE
  1033 + # To be specified by subclass
  1034 + if not self.IS_IDLE(): self._routine_process_before()
  1035 + #self.printd("I am IDLE, so I bypass the routine_process (do not send any new command)")
  1036 +
  1037 + # Processing the next pending command if exists
  1038 + # If Agent general level command like (DO_RESTART, DO_EXIT, DO_ABORT)
  1039 + # => will set DO_MAIN_LOOP=False and/or DO_RESTART_LOOP=True
  1040 + print()
  1041 + print()
  1042 + log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
  1043 + try :
  1044 + cmd = self._process_next_command_if_exists()
  1045 + except (AgentCmdUnimplementedException, AgentCmdBadArgsException, UnknownCmdException) as e :
  1046 + print(e)
  1047 + log.error(f"EXCEPTION on Agent command '{e.cmd.name}'")
  1048 + if type(e) is UnknownCmdException:
  1049 + e.cmd.set_as_skipped("EXCEPTION: unknown command")
  1050 + else :
  1051 + e.cmd.set_as_processed("EXCEPTION: command known but unimplemented or bad args")
  1052 + self._cleanup_before_exit()
  1053 + raise
  1054 + log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
  1055 +
  1056 + # only if in TEST mode
  1057 + self._TEST_check_cmd_res_and_status(cmd)
  1058 +
  1059 + if not self.DO_MAIN_LOOP: return
  1060 +
  1061 + '''
  1062 + # if restart, exit this loop to restart from beginning
  1063 + if self.DO_RESTART or self.DO_EXIT or self.DO_ABORT:
  1064 + self.DO_MAIN_LOOP = False
  1065 + return
  1066 + '''
  1067 +
  1068 + if not self.IS_IDLE(): self._routine_process_after()
  1069 +
  1070 + # TEST MODE only : execute test routine_process always (even if IDLE) in order to (always) send next command from test scenario
  1071 + self._TEST_test_routine_process()
  1072 +
  1073 + #self.printd("====== END COMMMANDS PROCESSING ======")
  1074 +
  1075 + #self.waitfor(self.mainloop_waittime)
  1076 +
  1077 + self._main_loop_end()
  1078 +
  1079 + self._iter_num += 1
  1080 +
  1081 +
  1082 +
  1083 + def _main_loop_start(self, nb_iter:int=None):
  1084 +
  1085 + for i in range(3): print()
  1086 + #self.printd("-"*80)
  1087 + log.info("*"*73)
  1088 + log.info("*"*20 + f" MAIN LOOP ITERATION {self._iter_num} (START) " + "*"*20)
  1089 + log.info("*"*73 + '\n')
  1090 + #self.print(f"Iteration {self._iter_num}")
  1091 +
  1092 + # EXIT because of nb of iterations ?
  1093 + if nb_iter is not None:
  1094 + # Bad number of iterations or nb iterations reached => exit
  1095 + if nb_iter <= 0 or nb_iter < self._iter_num:
  1096 + log.info(f"Exit because number of iterations asked ({nb_iter}) has been reached")
  1097 + self.DO_MAIN_LOOP = False
  1098 + return
  1099 +
  1100 + # Temporizing (delay) : Wait a random number of sec before starting iteration
  1101 + # (to avoid to busy to much the processor)
  1102 + # (also to let another agent having the chance to send a me command)
  1103 + if self._DELAY_NB_SEC :
  1104 + #random_waiting_sec = random.randint(0,5)
  1105 + log.info(f"Waiting {self._DELAY_NB_SEC} sec (random) before starting new iteration...")
  1106 + #time.sleep(random_waiting_sec)
  1107 + self.waitfor(self._DELAY_NB_SEC)
  1108 +
  1109 + # (Test only)
  1110 + # EXIT because max duration reached ?
  1111 + if self.TEST_MODE and self.TEST_MAX_DURATION_SEC and (time.time()-self.start_time > self.TEST_MAX_DURATION_SEC):
  1112 + log.info("Exit because of max duration set to ", self.TEST_MAX_DURATION_SEC, "s")
  1113 + #self._kill_running_device_cmd_if_exists(self.name)
  1114 + self._cleanup_before_exit(self.name)
  1115 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST: self._TEST_test_results()
  1116 + self.DO_MAIN_LOOP = False
  1117 + return
  1118 +
  1119 + self._set_status(self.STATUS_MAIN_LOOP)
  1120 + self.show_mode_and_status()
  1121 +
  1122 + self.main_loop_start()
  1123 +
  1124 + # To be overriden by subclass
  1125 + def main_loop_start(self):
  1126 + pass
  1127 +
  1128 + def _main_loop_end(self):
  1129 + self.main_loop_end()
  1130 + log.info("*"*20 + " MAIN LOOP ITERATION (END) " + "*"*20)
  1131 + #self.do_log(LOG_DEBUG, "Ending main loop iteration")
  1132 +
  1133 + # To be overriden by subclass
  1134 + def main_loop_end(self):
  1135 + pass
  1136 +
  1137 +
  1138 + # To be overriden by subclass (AgentDevice)
  1139 + def process_device_level_cmd(self):
  1140 + pass
  1141 + '''
  1142 + log.info("(DEVICE LEVEL CMD)")
  1143 + try:
  1144 + self.exec_device_cmd_if_possible(cmd)
  1145 + except (UnimplementedGenericCmdException) as e:
  1146 + #except (UnknownGenericCmdException, UnimplementedGenericCmdException, UnknownNativeCmdException) as e:
  1147 + log.e(f"EXCEPTION caught by {type(self).__name__} (from Agent mainloop) for command '{cmd.name}'", e)
  1148 + log.e("Thus ==> ignore this command")
  1149 + cmd.set_result(e)
  1150 + #cmd.set_as_killed_by(type(self).__name__)
  1151 + cmd.set_as_skipped()
  1152 + #raise
  1153 + '''
  1154 +
  1155 +
  1156 +
  1157 + def _process_next_command_if_exists(self)->AgentCmd:
  1158 + ''' Processing the next pending command if exists '''
  1159 +
  1160 + #print()
  1161 + #print()
  1162 + #log.info("*"*10 + " NEXT COMMAND PROCESSING (START) " + "*"*10 + '\n')
  1163 +
  1164 +
  1165 + # Purge commands (every N iterations, starting from 1st, delete old commands)
  1166 + N=5
  1167 + if ((self._iter_num-1) % N) == 0:
  1168 + log.info("Purging expired commands if exists")
  1169 + #AgentCmd.purge_old_commands_for_agent(self.name)
  1170 + self._purge_old_commands_sent_to_me()
  1171 +
  1172 + # Get next command and process it (if exists)
  1173 + cmd = self._get_next_valid_and_not_running_command()
  1174 + self.CURRENT_CMD = cmd
  1175 + #self._set_status(self.STATUS_GENERAL_PROCESS)
  1176 + #if cmd: self.command_process(cmd)
  1177 +
  1178 + # SIMU
  1179 + #cmd = "do_replan"
  1180 + #self.send_cmd_to("AgentScheduler", "do_replan")
  1181 +
  1182 + # No new command => nothing to do
  1183 + if not cmd: return cmd
  1184 +
  1185 + log.info('-'*6)
  1186 + log.info('-'*6 + " RECEIVED NEW COMMAND TO PROCESS: ")
  1187 + log.info('-'*6 + str(cmd))
  1188 + if self.is_in_test_mode() and hasattr(self._cmdts,"expected_res"):
  1189 + log.info(f"*** (with expected result : " + str(self._cmdts.expected_res) + ')')
  1190 + log.info('-'*6)
  1191 +
  1192 + cmd.set_read_time()
  1193 +
  1194 + # CASE 1 - AGENT GENERAL command
  1195 + # (DO_RESTART, DO_EXIST, DO_ABORT, DO_ABORT_COMMAND, DO_FLUSH_COMMANDS, ...)
  1196 + # This processing can set DO_MAIN_LOOP and DO_RESTART_LOOP to False
  1197 + if self._is_agent_general_cmd(cmd):
  1198 + try:
  1199 + self._process_agent_general_cmd(cmd)
  1200 + except AgentCmdUnimplementedException as e:
  1201 + #print(e)
  1202 + #cmd.set_as_skipped("ERROR: Unimplemented Agent General command")
  1203 + #self._cleanup_before_exist()
  1204 + # This exception is managed at higher level :
  1205 + raise
  1206 + #except ValueError as e:
  1207 + except AgentCmdBadArgsException as e:
  1208 + #print(e)
  1209 + #cmd.set_as_skipped("ERROR: Bad Argument(s)")
  1210 + #self._cleanup_before_exit()
  1211 + # This exception is managed at higher level :
  1212 + raise
  1213 + #raise AgentCmdBadArgsException(cmd.name)
  1214 + # Must I stop or restart ?
  1215 + if cmd.name in ('do_stop','do_restart_loop','do_exit','do_abort') :
  1216 + self.DO_MAIN_LOOP = False
  1217 + if cmd.name == 'do_abort':
  1218 + self._abort_current_running_cmd_if_exists()
  1219 + self._cleanup_before_exit()
  1220 + if cmd.name != 'do_restart_loop':
  1221 + self.DO_RESTART_LOOP = False
  1222 + return cmd
  1223 +
  1224 + # CASE 2 - AGENT SPECIFIC command
  1225 + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel)
  1226 + if self._is_agent_specific_cmd(cmd):
  1227 + #log.info("(AGENT LEVEL CMD)")
  1228 + if not self.IS_ATTENTIVE():
  1229 + cmd.set_as_skipped("Skipped because I am not ATTENTIVE")
  1230 + return cmd
  1231 + try:
  1232 + self._process_agent_specific_cmd(cmd)
  1233 + #self._exec_agent_cmd(cmd)
  1234 + #except AttributeError as e:
  1235 + except (AgentCmdUnimplementedException, AgentCmdBadArgsException) as e:
  1236 + ##print(e)
  1237 + #self.log_e(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1238 + #self.log_e("Thus => I ignore this command...")
  1239 + ##log.error(f"EXCEPTION: Agent specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1240 + #log.e("Thus => I ignore this command...")
  1241 + #cmd.set_result("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND", False)
  1242 + #cmd.set_as_pending()
  1243 + ##cmd.set_as_skipped("ERROR: UNIMPLEMENTED AGENT SPECIFIC COMMAND")
  1244 + ##self._cleanup_before_exit()
  1245 + ##raise AgentCmdUnimplementedException(cmd.name)
  1246 + # These exceptions are managed at higher level :
  1247 + raise
  1248 + return cmd
  1249 +
  1250 + # CASE 3 - OTHER level command (DEVsICE level, only for AgentDevice)
  1251 + if self.is_device_level_cmd(cmd):
  1252 + self.process_device_level_cmd(cmd)
  1253 + return cmd
  1254 +
  1255 + # CASE 4 - INVALID COMMAND
  1256 + #raise Exception("INVALID COMMAND: " + cmd.name)
  1257 + log.warning("******************************************************")
  1258 + log.warning("*************** ERROR: INVALID COMMAND (UNKNOWN) ***************")
  1259 + log.warning("******************************************************")
  1260 + #log.warning("Thus => I ignore this command...")
  1261 + #cmd.set_result("ERROR: INVALID COMMAND")
  1262 + ##cmd.set_as_skipped("ERROR: INVALID AGENT COMMAND")
  1263 + ##self._cleanup_before_exit()
  1264 + ##raise UnknownCmdException(cmd.name)
  1265 + raise UnknownCmdException(cmd)
  1266 +
  1267 + #print()
  1268 + #log.info("*"*10 + " NEXT COMMAND PROCESSING (END) " + "*"*10 + "\n")
  1269 +
  1270 +
  1271 +
  1272 +
  1273 + def _abort_current_running_cmd_if_exists(self):
  1274 + pass
  1275 +
  1276 + def _cleanup_before_exit(self, stopper_agent_name:str=None):
  1277 + if not stopper_agent_name: stopper_agent_name = self.name
  1278 + self._set_status(self.STATUS_EXIT)
  1279 + self._log_agent_status()
  1280 +
  1281 + log.info("Trying to stop cleanly")
  1282 + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :")
  1283 + #commands = AgentCmd.get_commands_sent_to_agent(self.name)
  1284 + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  1285 + AgentCmd.show_commands(commands, True)
  1286 + self.do_flush_commands()
  1287 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results()
  1288 + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST:
  1289 + self._TEST_test_results()
  1290 + #self._DO_EXIT=True
  1291 + #exit(0)
  1292 +
  1293 + self.do_things_before_exit(stopper_agent_name)
  1294 +
  1295 +
  1296 + def print_TEST_MODE(self):
  1297 + if self.TEST_MODE:
  1298 + log.debug("[IN TEST MODE]")
  1299 + log.info("Flush previous commands to be sure to start in clean state")
  1300 + AgentCmd.delete_pending_commands_for_agent(self.name)
  1301 + else:
  1302 + log.debug("[IN NORMAL MODE]")
  1303 + self.TEST_MAX_DURATION_SEC=None
  1304 +
  1305 +
  1306 + def _purge_old_commands_sent_to_me(self):
  1307 + AgentCmd.purge_old_commands_sent_to_agent(self.name)
  1308 +
  1309 +
  1310 + def _routine_process_before(self):
  1311 + """
  1312 + Routine processing BEFORE processing received commands.
  1313 +
  1314 + IMPORTANT :
  1315 + this processing must be as SHORT as possible,
  1316 + so that the agent can quickly read its received commands and process them
  1317 +
  1318 + This is a command or set of processings that this agent does or commands that it sends regularly,
  1319 + at each iteration
  1320 + """
  1321 + print()
  1322 + print()
  1323 + log.info("*"*10+ " ROUTINE PROCESSING BEFORE (START) "+ "*"*10+ '\n')
  1324 +
  1325 + self._set_status(self.STATUS_ROUTINE_PROCESS)
  1326 + self.routine_process_before_body()
  1327 + print()
  1328 + log.info("*"*10 + " ROUTINE PROCESSING BEFORE (END) "+ "*"*10)
  1329 +
  1330 +
  1331 + def _routine_process_after(self):
  1332 + """
  1333 + Routine processing AFTER processing received commands.
  1334 +
  1335 + This processing can be longer than the "before" one above,
  1336 + as the agent has already processed its received commands,
  1337 + but not too long anyway, otherwise it will take too long before the next iteration starts...
  1338 +
  1339 + This is a command or set of processings that this agent does or commands that it sends regularly,
  1340 + at each iteration
  1341 + """
  1342 + print()
  1343 + print()
  1344 + log.info("*"*10+ " ROUTINE PROCESSING AFTER (START) "+ "*"*10+ '\n')
  1345 +
  1346 + self._set_status(self.STATUS_ROUTINE_PROCESS)
  1347 + self.routine_process_after_body()
  1348 + print()
  1349 + log.info("*"*10 + " ROUTINE PROCESSING AFTER (END) "+ "*"*10)
  1350 +
  1351 +
  1352 + # To be overridden by subclasses
  1353 + def routine_process_before_body(self):
  1354 + pass
  1355 +
  1356 + # To be overridden by subclasses
  1357 + def routine_process_after_body(self):
  1358 + #if self.TEST_MODE: self._TEST_test_routine_process()
  1359 + pass
  1360 +
  1361 + """
  1362 + def purge_commands(self):
  1363 + ###
  1364 + Delete commands (which I am recipient of) older than COMMANDS_PEREMPTION_HOURS (like 48h)
  1365 + ATTENTION !!! EXCEPT the RUNNING command !!!
  1366 +
  1367 + NB: datetime.utcnow() is equivalent to datetime.now(timezone.utc)
  1368 + ###
  1369 +
  1370 + self.printd("Looking for old commands to purge...")
  1371 + ###
  1372 + COMMAND_PEREMPTION_DATE_FROM_NOW = datetime.utcnow() - timedelta(hours = self.COMMANDS_PEREMPTION_HOURS)
  1373 + #self.printd("peremption date", COMMAND_PEREMPTION_DATE_FROM_NOW)
  1374 + old_commands = AgentCmd.objects.filter(
  1375 + # only commands for me
  1376 + recipient = self.name,
  1377 + # only pending commands
  1378 + sender_deposit_time__lt = COMMAND_PEREMPTION_DATE_FROM_NOW,
  1379 + )
  1380 + ###
  1381 + old_commands = AgentCmd.get_old_commands_for_agent(self.name)
  1382 + if old_commands.exists():
  1383 + self.printd("Found old commands to delete:")
  1384 + for cmd in old_commands: self.printd(cmd)
  1385 + old_commands.delete()
  1386 + """
  1387 +
  1388 + def waitfor(self, nbsec:float=2.0):
  1389 + '''
  1390 + # thread
  1391 + if self._current_device_cmd_thread and self.RUN_IN_THREAD:
  1392 + self._current_device_cmd_thread.wait(nbsec)
  1393 + # process (or main thread)
  1394 + else:
  1395 + time.sleep(nbsec)
  1396 + '''
  1397 + log.info(f"Now, waiting for {nbsec} second(s)...")
  1398 + time.sleep(nbsec)
  1399 +
  1400 + def sleep(self, nbsec):
  1401 + self.waitfor(nbsec)
  1402 +
  1403 + def _set_status(self, status:str):
  1404 + #self.printd(f"[{status}] (switching from status {self.status})")
  1405 + log.debug(f"[{status}]")
  1406 + self.status = status
  1407 + return False
  1408 +
  1409 + def _set_mode(self, mode:str):
  1410 + #self.printd(f"Switching from mode {self.mode} to mode {mode}")
  1411 + log.info(f"[NEW MODE {mode}]")
  1412 + self.mode = mode
  1413 +
  1414 + # Test mode
  1415 + def IS_IDLE(self): return self.mode == self.MODE_IDLE
  1416 + def IS_ROUTINE(self): return self.mode == self.MODE_ROUTINE
  1417 + def IS_ATTENTIVE(self): return self.mode == self.MODE_ATTENTIVE
  1418 +
  1419 + def show_mode_and_status(self):
  1420 + log.info(f"CURRENT MODE is {self.mode} (status is {self.status})")
  1421 +
  1422 + def get_specifics_cmd(self):
  1423 + specific_commands = ""
  1424 + for index, command_tuple in enumerate(self.AGENT_SPECIFIC_COMMANDS):
  1425 + specific_commands += f"{command_tuple[0]}"
  1426 + if index != len(self.AGENT_SPECIFIC_COMMANDS)-1:
  1427 + specific_commands += ";"
  1428 + return specific_commands
  1429 +
  1430 + def get_mode(self):
  1431 + return self.mode
  1432 +
  1433 + def get_state(self):
  1434 + return self.status
  1435 +
  1436 + def set_idle(self):
  1437 + self._set_mode(self.MODE_IDLE)
  1438 + def set_routine(self):
  1439 + self._set_mode(self.MODE_ROUTINE)
  1440 + def set_attentive(self):
  1441 + self._set_mode(self.MODE_ATTENTIVE)
  1442 +
  1443 +
  1444 + def die(self):
  1445 + self._set_status(self.STATUS_EXIT)
  1446 +
  1447 + """
  1448 + suspend/resume
  1449 + """
  1450 + def suspend(self):
  1451 + """
  1452 + TODO:
  1453 + Mode IDLE (doit rester à l'écoute d'un resume,
  1454 + et doit continuer à alimenter les tables pour informer de son état via tables agents_logs,
  1455 + et lire table agents_command pour reprendre via resume,
  1456 + et update la table agents_survey pour donner son status "idle"
  1457 + """
  1458 + self._set_idle()
  1459 + return True
  1460 +
  1461 + def resume(self):
  1462 + """
  1463 + Quit suspend() mode
  1464 + """
  1465 + self._set_active()
  1466 + return True
  1467 +
  1468 +
  1469 + '''
  1470 + (EP) moved to AgentDevice
  1471 + def _set_agent_device_aliases_from_config(self, agent_alias):
  1472 + for a in self._my_client_agents_aliases:
  1473 + # TODO: activer
  1474 + ##self._my_client_agents[a] = self.config.get_paramvalue(a,'general','real_agent_device_name')
  1475 + pass
  1476 + '''
  1477 +
  1478 + # new config (obsconfig)
  1479 + def _set_mode_from_config(self, agent_name):
  1480 + # all agent are active ?
  1481 +
  1482 + #mode = self.MODE_ACTIVE
  1483 + mode = self.MODE_ATTENTIVE
  1484 + self._set_mode(mode)
  1485 + return True
  1486 + # old config
  1487 + # def _set_mode_from_config(self, agent_name):
  1488 + # # --- Get the startmode of the AgentX
  1489 + # modestr = self.config.get_paramvalue(agent_name,'general','startmode')
  1490 + # if self.config.get_last_errno() != self.config.NO_ERROR:
  1491 + # raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
  1492 + # if (modestr == None):
  1493 + # return True
  1494 + # # --- Set the mode according the startmode value
  1495 + # mode = self.MODE_IDLE
  1496 + # if modestr.upper() == 'RUN':
  1497 + # mode = self.MODE_ACTIVE
  1498 + # self._set_mode(mode)
  1499 + # return True
  1500 +
  1501 +
  1502 + def set_delay(self, delay_nb_sec:int):
  1503 + self._DELAY_NB_SEC = delay_nb_sec
  1504 +
  1505 + """
  1506 + =======================================================================================
  1507 + Generic methods that may be specialized (overriden) by subclasses (except if private)
  1508 + =======================================================================================
  1509 + """
  1510 +
  1511 + # To be overridden by subclasses
  1512 + def init(self):
  1513 + #log.debug("*** Initializing... ***")
  1514 + log.info("*** INITIALIZING... ***")
  1515 + self._set_status(self.STATUS_INIT)
  1516 + if self.TEST_MODE: self.set_delay(2)
  1517 +
  1518 +
  1519 + def _reload_config_if_changed(self):
  1520 + self._load_config()
  1521 +
  1522 + def _load_config(self):
  1523 + """
  1524 + TODO:
  1525 + only si date fichier xml changée => en RAM, un objet Config avec méthodes d'accès, appelle le parser de AK (classe Config.py indépendante)
  1526 + """
  1527 + '''
  1528 + # SETUP
  1529 + try:
  1530 + self.config = get_object_or_404(Config, id=1)
  1531 + # By default, set mode to SCHEDULER (False = REMOTE, which should never be the default)
  1532 + self.config.global_mode = True
  1533 + self.config.save()
  1534 + # self.config = Config.objects.get(pk=1)
  1535 + # self.config = Config.objects.get()[0]
  1536 + except Exception as e:
  1537 + # except Config.ObjectDoesNotExist:
  1538 + self.printd("Config read (or write) exception", str(e))
  1539 + return -1
  1540 + '''
  1541 + log.debug("Loading the config file...")
  1542 +
  1543 + # - NEW CONFIG
  1544 + self.get_config().load(self.get_config_env()[0])
  1545 +
  1546 +
  1547 + # - OLD CONFIG
  1548 + # self.config.load()
  1549 + # if self.config.get_last_errno() != self.config.NO_ERROR:
  1550 + # raise Exception(f"error {str(self.config.get_last_errno())}: {str(self.config.get_last_errmsg())}")
  1551 + # if not self.config.is_config_contents_changed():
  1552 + # return
  1553 +
  1554 + # === display informations
  1555 + # --- Get the assembly aliases of the unit
  1556 + # assembled_mount_aliases = []
  1557 + # assembled_channel_aliases = []
  1558 + # assembled_aliases = []
  1559 + # unit_alias = self.config.get_aliases('unit')[0]
  1560 + # params = self.config.get_params(unit_alias)
  1561 + # for param in params:
  1562 + # if param['section']=="assembly" and param['key']=="alias":
  1563 + # assembled_aliases.append(param['value'])
  1564 + #self.printd(f"Unit {unit_alias} is the assembly of {assembled_aliases}")
  1565 +
  1566 + # log.debug("--------- Components of the unit -----------")
  1567 + # log.debug("Configuration file is {}".format(self.config.get_configfile()))
  1568 + # alias = self.config.get_aliases('unit')[0]
  1569 + # namevalue = self.config.get_paramvalue(alias,'unit','description')
  1570 + # log.debug(f"Unit alias is {alias}. Description is {namevalue}:")
  1571 + # unit_subtags = self.config.get_unit_subtags()
  1572 + # for unit_subtag in unit_subtags:
  1573 + # aliases = self.config.get_aliases(unit_subtag)
  1574 + # for alias in aliases:
  1575 + # namevalue = self.config.get_paramvalue(alias,unit_subtag,'description')
  1576 + # log.debug(f"- {unit_subtag} alias is {alias}. Description is {namevalue}")
  1577 + # # --- fill the list of mount and channel assembled
  1578 + # if alias in assembled_aliases:
  1579 + # if unit_subtag=="mount":
  1580 + # assembled_mount_aliases.append(alias)
  1581 + # elif unit_subtag=="channel":
  1582 + # assembled_channel_aliases.append(alias)
  1583 +
  1584 + # log.debug("--------- Assembly of the unit -----------")
  1585 + # log.debug(f"Assembled mount aliases: {assembled_mount_aliases}")
  1586 + # log.debug(f"Assembled channel aliases: {assembled_channel_aliases}")
  1587 +
  1588 + # --- Get the home of the mount[0]
  1589 + # mount_alias = assembled_mount_aliases[0]
  1590 + # home = self.config.get_paramvalue(mount_alias,'MountPointing','home')
  1591 +
  1592 + # log.debug("------------------------------------------")
  1593 + # hostname = socket.gethostname()
  1594 + # self._computer_alias = ''
  1595 + # unit_subtag = 'computer'
  1596 + # aliases = self.config.get_aliases(unit_subtag)
  1597 + # for alias in aliases:
  1598 + # log.debug("alias" + alias)
  1599 + # value = self.config.get_paramvalue(alias,'local','hostname')
  1600 + # log.debug("value" + value)
  1601 + # if value == hostname:
  1602 + # log.debug("value" + value)
  1603 + # self._computer_alias = alias
  1604 + # value = self.config.get_paramvalue(alias,unit_subtag,'description')
  1605 + # self._computer_description = value
  1606 + # value = self.config.get_paramvalue(alias,'path','data')
  1607 + # # Overrides default value
  1608 + # self._path_data = value
  1609 + # break
  1610 + # log.debug(f"hostname = {hostname}")
  1611 + # log.debug(f"path_data = {self._path_data}")
  1612 + # log.debug(f"home = {home}")
  1613 + # log.debug("------------------------------------------")
  1614 +
  1615 + # --- update the log parameters
  1616 + ##self.log.path_data = self._path_data
  1617 + ##print("new self.log.path_data is", self.log.path_data)
  1618 + ##self.log.set_global_path_data(self._path_data)
  1619 + ##self.printd("new self.log.global_path_data is", self.log.get_global_path_data())
  1620 + ##self.log.home = home
  1621 +
  1622 +
  1623 + #def update_survey(self):
  1624 + def _log_agent_status(self):
  1625 + """
  1626 + Save (update) this agent current mode and status in DB
  1627 + """
  1628 + "Updating the agent survey database table..."
  1629 + #self.printd("- fetching table line for agent", self.name)
  1630 + # only necessary when using process (not necessary with threads)
  1631 + #with transaction.atomic():
  1632 + #self._agent_survey = AgentSurvey.objects.get(name=self.name)
  1633 + self._agent_survey.mode = self.mode
  1634 + self._agent_survey.status = self.status
  1635 + self._agent_survey.iteration = self._iter_num
  1636 + self._agent_survey.save()
  1637 + #self._agent_survey.save(update_fields=["mode", "status"])
  1638 +
  1639 +
  1640 + """
  1641 + def send_command(self, cmd_name):
  1642 + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST
  1643 + AgentCmd.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name)
  1644 + """
  1645 + #def send_command(self, to_agent, cmd_type, cmd_name, cmd_args=None):
  1646 + def send_cmd_to(self, to_agent:str, cmd_name:str, cmd_args:str=None, validity:int=None, timeout:int=None):
  1647 + """
  1648 + #ex: send_command(“AgentX”,”GENERIC”,”EVAL”,“3+4”)
  1649 + ex: send_command(“AgentX”,"EVAL”,“3+4”)
  1650 + """
  1651 + #return AgentCmd.send_cmd_from_to(self.name, self._get_real_agent_name_for_alias(to_agent), cmd_name, cmd_args)
  1652 + #cmd = self.create_cmd_for(to_agent, cmd_name, cmd_args, validity, timeout).send()
  1653 + cmd = self.create_cmd_for(to_agent, cmd_name, cmd_args, validity).send()
  1654 + #cmd.send()
  1655 + return cmd
  1656 +
  1657 + def create_cmd_for(self, to_agent:str, cmd_name:str, cmd_args:str=None, validity:int=None) -> AgentCmd:
  1658 +
  1659 + '''
  1660 + real_agent_name = self._get_real_agent_name(to_agent)
  1661 + real_cmd_name = cmd_name
  1662 + if '.' in real_agent_name:
  1663 + real_agent_name, component_name = real_agent_name.split('.')
  1664 + real_cmd_name = component_name+'.'+cmd_name
  1665 + return AgentCmd.create(self.name, real_agent_name, real_cmd_name, cmd_args)
  1666 + try:
  1667 + real_agent_name = self._get_real_agent_name(to_agent)
  1668 + except KeyError as e:
  1669 + '''
  1670 + return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args, validity)
  1671 + '''
  1672 + real_agent_name = self._get_real_agent_name(to_agent)
  1673 + if not real_agent_name:
  1674 + return AgentCmd.create(self.name, to_agent, cmd_name, cmd_args)
  1675 + # log.e("UNKNOWN AgentDevice ALIAS", to_agent)
  1676 + # #self.log_e("Exception raised", e)
  1677 + # log.e(f"=> Thus, I do not send this command '{cmd_name}'")
  1678 + # return None
  1679 + return AgentCmd.create(self.name, real_agent_name, cmd_name, cmd_args)
  1680 + '''
  1681 + '''
  1682 + return AgentCmd(
  1683 + sender=self.name,
  1684 + recipient=self._get_real_agent_name_for_alias(recipient_agent_alias_name),
  1685 + name=cmd_name
  1686 + )
  1687 + '''
  1688 +
  1689 + def _get_next_valid_and_not_running_command(self) -> AgentCmd:
  1690 + """
  1691 + Return next VALID (not expired) command (read from the DB command table)
  1692 + which is relevant to this agent.
  1693 + Commands are read in chronological order
  1694 + """
  1695 + self._set_status(self.STATUS_GET_NEXT_COMMAND)
  1696 + log.info("Looking for a new command to process (sent by another agent):")
  1697 +
  1698 + # 1) Get all pending commands for me (return if None)
  1699 + # Not sure this is necessary to do it in a transaction,
  1700 + # but there might be a risk
  1701 + # that a command status is modified while we are reading...
  1702 + with transaction.atomic():
  1703 + self._pending_commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  1704 + commands = self._pending_commands
  1705 + if not commands.exists():
  1706 + log.info("<None>")
  1707 + return None
  1708 + "Current pending (or running) commands are (time ordered):"
  1709 + AgentCmd.show_commands(commands)
  1710 +
  1711 + # 2) Check if a PRIORITY command is in the list (even at the very end),
  1712 + # and if so return this command
  1713 + # (must be still valid and not yet running)
  1714 + for cmd in commands:
  1715 + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"): break
  1716 + #if cmd.name in ("do_exit", "do_abort"): break
  1717 + if cmd.is_agent_general_priority_cmd():
  1718 + if cmd.is_running():
  1719 + return None
  1720 + if cmd.is_expired():
  1721 + cmd.set_as_outofdate()
  1722 + return None
  1723 + return cmd
  1724 + '''
  1725 + #if cmd.name in ("do_exit", "do_abort", "do_flush_commands"):
  1726 + if cmd.name in ("do_exit", "do_abort"):
  1727 + if cmd.is_running():
  1728 + return None
  1729 + if cmd.is_expired():
  1730 + cmd.set_as_outofdate()
  1731 + return None
  1732 + return cmd
  1733 + '''
  1734 +
  1735 + # 3) If first (oldest) command is currently running
  1736 + # (status CMD_RUNNING), then do nothing and return
  1737 + """
  1738 + cmd_executing = Command.objects.filter(
  1739 + # only commands for me
  1740 + recipient = self.name,
  1741 + # only pending commands
  1742 + recipient_status_code = Command.CMD_STATUS_CODES.CMD_RUNNING,
  1743 + ).first()
  1744 + #if cmd_executing.exists():
  1745 + if cmd_executing:
  1746 + """
  1747 + #cmd = commands[0]
  1748 + cmd = commands.first()
  1749 +
  1750 + if cmd.is_expired():
  1751 + cmd.set_as_outofdate()
  1752 + return None
  1753 +
  1754 + if cmd.is_running():
  1755 + #self.printd(f"There is currently a running command ({cmd_executing.first().name}), so I do nothing (wait for end of execution)")
  1756 + log.info(f"There is currently a running command ({cmd.name})")
  1757 + """
  1758 + # Check that this command is not expired
  1759 + if cmd.is_expired():
  1760 + self.printd("But this command is expired, so set its status to OUTOFDATE, and go on")
  1761 + cmd_executing.set_as_outofdate()
  1762 + else:
  1763 + """
  1764 + log.info(f"Thus, I won't execute any new command until this command execution is finished")
  1765 + # TODO: kill si superieur a MAX_EXEC_TIME
  1766 + return None
  1767 +
  1768 + '''
  1769 + # 4) Tag all expired commands
  1770 + for cmd in commands:
  1771 + if cmd.is_expired(): cmd.set_as_outofdate()
  1772 + # break at 1st "valid" command (not expired)
  1773 + else: break
  1774 +
  1775 + # 5) If no more commands to process, return None
  1776 + if cmd.is_expired(): return None
  1777 + '''
  1778 +
  1779 + # 6) Current cmd must now be a valid (not expired) and PENDING one,
  1780 + # so return it for execution
  1781 + #self.printd(f"Got command {cmd.name} sent by agent {cmd.sender} at {cmd.sender_deposit_time}")
  1782 + #self.printd(f"Starting processing of this command")
  1783 + return cmd
  1784 +
  1785 +
  1786 + '''
  1787 + #def _exec_agent_general_cmd(self, cmd:Command):
  1788 + def _exec_agent_cmd(self, cmd:AgentCmd):
  1789 +
  1790 + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...")
  1791 + log.info(f"Starting execution of an AGENT LEVEL cmd...")
  1792 +
  1793 + # Update read time to say that the command has been READ
  1794 + cmd.set_read_time()
  1795 + cmd.set_as_running()
  1796 +
  1797 + # SPECIFIC command (only related to me, not to any agent)
  1798 + if self._is_agent_specific_cmd(cmd):
  1799 + log.info("(Agent level SPECIFIC cmd)")
  1800 + # Execute method self."cmd.name"()
  1801 + # This can raise an exception (caught by this method caller)
  1802 + self.exec_cmd_from_its_name(cmd)
  1803 + #''
  1804 + try:
  1805 + except AttributeError as e:
  1806 + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1807 + self.printd("Thus => I ignore this command...")
  1808 + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND")
  1809 + cmd.set_as_pending()
  1810 + cmd.set_as_skipped()
  1811 + return
  1812 + #''
  1813 + cmd.set_result("Agent level SPECIFIC cmd done")
  1814 + cmd.set_as_processed()
  1815 + log.info("...Agent level SPECIFIC cmd has been executed")
  1816 + return
  1817 +
  1818 + # GENERAL command (related to any agent)
  1819 + log.info("(Agent level GENERAL CMD)")
  1820 + _,cmd_name,cmd_args = cmd.get_full_name_parts()
  1821 + #cmd_name, cmd_args = cmd.tokenize()
  1822 + #if cmd.name == "set_state:active":
  1823 + #elif cmd.name == "set_state:idle":
  1824 + if cmd_name == "set_state":
  1825 + if not cmd_args: raise ValueError()
  1826 + state = cmd_args[0]
  1827 + if state == "active": self._set_active()
  1828 + if state == "idle": self._set_idle()
  1829 + cmd.set_result("I am now " + state)
  1830 + #time.sleep(1)
  1831 + self.waitfor(1)
  1832 + elif cmd_name in ("do_flush_commands"):
  1833 + "flush_commands received: Delete all pending commands"
  1834 + AgentCmd.delete_pending_commands_for_agent(self.name)
  1835 + cmd.set_result('DONE')
  1836 + elif cmd_name in ("do_abort", "do_exit", "do_restart_init"):
  1837 + #self.printd("Current pending commands are:")
  1838 + #Command.show_commands(self._pending_commands)
  1839 + log.info("Aborting current executing command if exists:")
  1840 + #self._kill_running_device_cmd_if_exists(cmd.sender)
  1841 + self.do_things_before_exit(cmd.sender)
  1842 + if cmd_name == "do_restart_init":
  1843 + log.info("restart_init received: Restarting from init()")
  1844 + self._DO_RESTART=True
  1845 + elif cmd.name == "do_exit":
  1846 + self._DO_EXIT=True
  1847 + cmd.set_result('SHOULD BE DONE NOW')
  1848 + else:
  1849 + #''
  1850 + name = cmd.name
  1851 + args = None
  1852 + if " " in name: name,args = name.split()
  1853 + if name == "do_eval":
  1854 + if args==None: raise(ValueError)
  1855 + cmd.set_result(eval(args))
  1856 + #''
  1857 + if cmd_name == "do_eval":
  1858 + if not cmd_args: raise ValueError()
  1859 + cmd.set_result(eval(cmd_args))
  1860 +
  1861 + cmd.set_as_processed()
  1862 + log.info("...Agent level GENERAL cmd has been executed")
  1863 +
  1864 + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !)
  1865 + # This "do_exit" should normally kill any current thread (to be checked...)
  1866 + if cmd.name == "do_exit":
  1867 + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :")
  1868 + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  1869 + AgentCmd.show_commands(commands, True)
  1870 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results()
  1871 + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST:
  1872 + self._TEST_test_results()
  1873 + #self._DO_EXIT=True
  1874 + #exit(0)
  1875 + '''
  1876 +
  1877 +
  1878 + def _process_agent_general_cmd(self, cmd:AgentCmd):
  1879 + # GENERAL command (related to any agent)
  1880 +
  1881 + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...")
  1882 + log.info(f"Starting execution of an AGENT GENERAL cmd...")
  1883 +
  1884 + # Update read time to say that the command has been READ
  1885 + #cmd.set_read_time()
  1886 + cmd.set_as_running()
  1887 +
  1888 + log.info("(Agent level GENERAL CMD)")
  1889 + cmd_name,cmd_args = cmd.name_and_args
  1890 + #cmd_name, cmd_args = cmd.tokenize()
  1891 + #if cmd.name == "set_state:active":
  1892 + #elif cmd.name == "set_state:idle":
  1893 +
  1894 + # Default result (null)
  1895 + result = None
  1896 +
  1897 + #if cmd_name in ("do_abort", "do_exit", "do_restart_init"):
  1898 + if cmd_name in ("do_abort", "do_exit", "do_restart_loop"):
  1899 + #self.printd("Current pending commands are:")
  1900 + #Command.show_commands(self._pending_commands)
  1901 + log.info("Stopping/Aborting current executing command if exists:")
  1902 + #self._kill_running_device_cmd_if_exists(cmd.sender)
  1903 + '''
  1904 + self.do_things_before_exit(cmd.sender)
  1905 + if cmd_name == "do_restart_init":
  1906 + log.info("restart_init received: Restarting from init()")
  1907 + self._DO_RESTART=True
  1908 + elif cmd.name == "do_exit":
  1909 + self._DO_EXIT=True
  1910 + cmd.set_result('SHOULD BE DONE NOW')
  1911 + '''
  1912 + result = "RESTARTING" if cmd_name == "do_restart_loop" else "STOPPING"
  1913 +
  1914 + elif cmd_name == "get_state":
  1915 + result = "I am now " + self.get_state()
  1916 +
  1917 + elif cmd_name == "get_mode":
  1918 + result = "MODE = " + self.get_mode()
  1919 +
  1920 + elif cmd_name == "set_mode":
  1921 + #if not cmd_args: raise ValueError()
  1922 + if not cmd_args: raise AgentCmdBadArgsException(cmd)
  1923 + mode = cmd_args[0]
  1924 + if mode == "IDLE": self.set_idle()
  1925 + elif mode == "ROUTINE": self.set_routine()
  1926 + elif mode == "ATTENTIVE": self.set_attentive()
  1927 + else: raise AgentCmdBadArgsException(cmd)
  1928 + #cmd.set_result("I am now " + state)
  1929 + result = "MODE = " + mode
  1930 + #time.sleep(1)
  1931 + #self.waitfor(1)
  1932 +
  1933 + #elif cmd_name in ("do_flush_commands"):
  1934 + elif cmd_name == "do_flush_commands":
  1935 + "flush_commands received: Delete all pending commands"
  1936 + self.do_flush_commands()
  1937 + #cmd.set_result('DONE')
  1938 + result = "FLUSH DONE"
  1939 +
  1940 + elif cmd_name == "do_eval":
  1941 + #if not cmd_args: raise ValueError()
  1942 + if not cmd_args: raise AgentCmdBadArgsException(cmd)
  1943 + #cmd.set_result(eval(cmd_args))
  1944 + #result = eval(cmd_args)
  1945 + result = self.do_eval(cmd_args[0])
  1946 + elif cmd_name == "get_specifics_cmd":
  1947 + result = self.get_specifics_cmd()
  1948 +
  1949 + cmd.set_as_processed(result)
  1950 + log.info("...Agent level GENERAL cmd has been executed")
  1951 +
  1952 + '''
  1953 + # If cmd is "do_exit", kill myself (without any question, this is an order soldier !)
  1954 + # This "do_exit" should normally kill any current thread (to be checked...)
  1955 + if cmd.name == "do_exit":
  1956 + log.info("Before exiting, Here are (if exists) the current (still) pending commands (time ordered) :")
  1957 + commands = AgentCmd.get_pending_and_running_commands_for_agent(self.name)
  1958 + AgentCmd.show_commands(commands, True)
  1959 + #if self.TEST_MODE and self.TEST_WITH_FINAL_TEST and self.TEST_COMMANDS_DEST == "myself": self.simulator_test_results()
  1960 + if self.TEST_MODE and self.TEST_WITH_FINAL_TEST:
  1961 + self._TEST_test_results()
  1962 + #self._DO_EXIT=True
  1963 + #exit(0)
  1964 + '''
  1965 +
  1966 +
  1967 +
  1968 +
  1969 + def _process_agent_specific_cmd(self, cmd:AgentCmd):
  1970 +
  1971 + #self.print(f"Starting execution of an AGENT LEVEL cmd {cmd}...")
  1972 + log.info(f"Starting execution of an AGENT level SPECIFIC cmd...")
  1973 +
  1974 + # Update read time to say that the command has been READ
  1975 + #cmd.set_read_time(False)
  1976 + cmd.set_as_running()
  1977 +
  1978 + log.info("(Agent SPECIFIC cmd)")
  1979 + # Execute method self."cmd.name"()
  1980 + # This can raise an exception (caught by this method caller)
  1981 + try:
  1982 + res = self.exec_cmd_from_its_name(cmd)
  1983 + except (AgentCmdUnimplementedException, AgentCmdBadArgsException) as e:
  1984 + # These exceptions are managed at higher level :
  1985 + raise
  1986 + '''
  1987 + except AttributeError as e:
  1988 + self.printd(f"EXCEPTION: Agent level specific command '{cmd.name}' unknown (not implemented as a function) :", e)
  1989 + self.printd("Thus => I ignore this command...")
  1990 + cmd.set_result("ERROR: INVALID AGENT LEVEL SPECIFIC COMMAND")
  1991 + cmd.set_as_pending()
  1992 + cmd.set_as_skipped()
  1993 + return
  1994 + '''
  1995 + #cmd.set_result("Agent level SPECIFIC cmd done")
  1996 + #res = res if res else "Agent SPECIFIC cmd done"
  1997 + cmd.set_as_processed(res)
  1998 + log.info("...Agent SPECIFIC cmd has been executed")
  1999 +
  2000 +
  2001 +
  2002 +
  2003 + '''
  2004 + def do_log(self):
  2005 + #""
  2006 + log à 2 endroits ou 1 seul
  2007 + - in file
  2008 + - in db
  2009 + #""
  2010 + self.printd("Logging data...")
  2011 + '''
  2012 +
  2013 + def exec_cmd_from_its_name(self, cmd:AgentCmd):
  2014 + methods_list = [method for method in dir(self) if callable(getattr(self, method))]
  2015 + #print(methodsList)
  2016 + func = cmd.name
  2017 + if func not in methods_list: raise AgentCmdUnimplementedException(cmd)
  2018 + #for arg in cmd.args: print(arg)
  2019 + #print(cmd.args)
  2020 + #print(*cmd.args)
  2021 + '''
  2022 + try:
  2023 + if not cmd.args:
  2024 + # equivalent to calling self.func()
  2025 + return getattr(self, func)()
  2026 + else:
  2027 + args = []
  2028 + # Convert all args to their real type
  2029 + for arg in cmd.args:
  2030 + #print(arg)
  2031 + #try:
  2032 + # Evaluate arg only if it is not a word (letters)
  2033 + if not arg[0].isalpha():
  2034 + arg = ast.literal_eval(arg)
  2035 + #except ValueError as e: newarg = arg
  2036 + args.append(arg)
  2037 + #print(args)
  2038 + # equivalent to calling self.func(*cmd.args)
  2039 + return getattr(self, func)(*args)
  2040 + '''
  2041 + args = []
  2042 + # Convert all args to their real type
  2043 + for arg in cmd.args:
  2044 + #print(arg)
  2045 + #try:
  2046 + # Evaluate arg only if it is not a word (letters)
  2047 + if not arg[0].isalpha():
  2048 + arg = ast.literal_eval(arg)
  2049 + #except ValueError as e: newarg = arg
  2050 + args.append(arg)
  2051 + try:
  2052 + # equivalent to calling self.func(*cmd.args)
  2053 + return getattr(self, func)(*args)
  2054 + except (TypeError, AttributeError, ValueError) as e:
  2055 + #raise e
  2056 + # "from None" pour ne pas afficher l'exception AttributeError (car interne)
  2057 + raise AgentCmdBadArgsException(cmd) from None
  2058 + #print("I know this specific command but it is not yet implemented : ", func)
  2059 + ##tb = sys.exc_info()[2]
  2060 + ##raise AgentCmdUnimplementedException(cmd).with_traceback(tb)
  2061 + ##raise AgentCmdUnimplementedException(cmd).with_traceback(None)
  2062 +
  2063 + def _is_agent_level_cmd(self, cmd:AgentCmd):
  2064 + return self._is_agent_general_cmd(cmd) or self._is_agent_specific_cmd(cmd)
  2065 +
  2066 + def _is_agent_general_cmd(self, cmd:AgentCmd):
  2067 + return cmd.is_agent_general_cmd()
  2068 +
  2069 +
  2070 +
  2071 + '''
  2072 + def _exec_agent_cmd(self, cmd:Command):
  2073 + # AGENT "GENERAL LEVEL" command
  2074 + # => I process it directly without asking my DC
  2075 + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel)
  2076 + if cmd.is_agent_level_general_cmd():
  2077 + self.printd("********** -- AGENT LEVEL GENERAL CMD *********")
  2078 + self._exec_agent_general_cmd(cmd)
  2079 +
  2080 + # AGENT "SPECIFIC LEVEL" command
  2081 + # => I process it directly without asking my DC
  2082 + # => Simple action, short execution time, so I execute it directly (in current main thread, not in parallel)
  2083 + #elif self._is_agent_level_specific_cmd(cmd):
  2084 + else:
  2085 + self.printd("********** -- AGENT LEVEL SPECIFIC CMD *********")
  2086 + self._exec_agent_specific_cmd(cmd)
  2087 + '''
  2088 +
  2089 + def _is_agent_specific_cmd(self, cmd:AgentCmd):
  2090 + #return cmd.name in self.AGENT_SPECIFIC_COMMANDS
  2091 + #return (cmd.name,) in self.AGENT_SPECIFIC_COMMANDS
  2092 + for (cmd_name,timeout) in self.AGENT_SPECIFIC_COMMANDS:
  2093 + if cmd.name == cmd_name : return True
  2094 +
  2095 + '''
  2096 + def _exec_agent_specific_cmd(self, cmd:Command):
  2097 + # Execute method self."cmd.name"()
  2098 + self.exec_cmd_from_its_name(cmd)
  2099 + '''
  2100 +
  2101 + def is_in_test_mode(self):
  2102 + return self.TEST_MODE
  2103 +
  2104 +
  2105 +
  2106 + ###
  2107 + # ================================================================================================
  2108 + # AGENT GENERAL FUNCTIONS
  2109 + # ================================================================================================
  2110 + ###
  2111 +
  2112 + def do_eval(self, eval_str:str):
  2113 + return eval(eval_str)
  2114 +
  2115 + def do_flush_commands(self):
  2116 + AgentCmd.delete_pending_commands_for_agent(self.name)
  2117 +
  2118 + def do_exec_commands(self, what:str):
  2119 + # - Temporaly stop execution of new commands (let them accumulate)
  2120 + if what == "stop": pass
  2121 + # - Resume execution of commands (accumulated since "do_stop_exec_cmd")
  2122 + if what == "resume": pass
  2123 + # NOT PRIO
  2124 + if what == "noprio": pass
  2125 + # Bad arg
  2126 + raise AgentCmdBadArgsException(self.CURRENT_CMD)
  2127 +
  2128 + # Stop currently running cmd or routine
  2129 + def do_stop_current(self, what:str):
  2130 + if what == "cmd": self.do_stop_current_cmd()
  2131 + if what == "routine": self.do_stop_current_routine()
  2132 + if what == "both":
  2133 + self.do_stop_current_cmd()
  2134 + self.do_stop_current_routine()
  2135 + # Bad arg
  2136 + raise AgentCmdBadArgsException(self.CURRENT_CMD)
  2137 + def do_stop_current_cmd(self):
  2138 + pass
  2139 + def do_stop_current_routine(self):
  2140 + pass
  2141 +
  2142 + def do_exit(self): self.do_stop("asap");
  2143 + def do_abort(self): self.do_stop("now");
  2144 + # Stop agent asap or now
  2145 + def do_stop(self, when:str):
  2146 + # PRIO
  2147 + if when == "asap":
  2148 + pass
  2149 + if when == "now":
  2150 + pass
  2151 + # NOT PRIO
  2152 + if when == "noprio":
  2153 + pass
  2154 + # Bad arg
  2155 + raise AgentCmdBadArgsException(self.CURRENT_CMD)
  2156 +
  2157 +
  2158 + # Stop agent asap or now
  2159 + def do_restart_loop(self, when:str):
  2160 + # PRIO
  2161 + if when == "asap":
  2162 + pass
  2163 + if when == "now":
  2164 + pass
  2165 + # NOT PRIO
  2166 + if when == "noprio":
  2167 + pass
  2168 + # Bad arg
  2169 + raise AgentCmdBadArgsException(self.CURRENT_CMD)
  2170 +
  2171 +
  2172 +
  2173 +
  2174 + ###
  2175 + # ================================================================================================
  2176 + # AGENT SPECIFIC FUNCTIONS
  2177 + # (here just to serve as examples)
  2178 + # ================================================================================================
  2179 + ###
  2180 +
  2181 + #def do_specific1(self, arg1:int, arg2:int=2, arg3:int=3) -> int:
  2182 + #def do_specific1(self, arg1:int, arg2:int=2, arg3:float=3.1, arg4:list=[1,2,3]) -> float:
  2183 + def do_specific1(self,
  2184 + arg1:int,
  2185 + arg2:int,
  2186 + arg3:float=3.1,
  2187 + arg4:str='toto',
  2188 + arg5:Tuple[int,str,int]=(1,'toto',3),
  2189 + #arg5:Tuple[int,int,int]=(1,2,3),
  2190 + arg6:List[int]=[]
  2191 + ) -> float:
  2192 + '''
  2193 + arg1 = int(arg1)
  2194 + arg2 = int(arg2)
  2195 + arg3 = float(arg3)
  2196 + arg5 = ast.literal_eval(arg5)
  2197 + print(arg5[1])
  2198 + arg6 = ast.literal_eval(arg6)
  2199 + '''
  2200 + print(arg4)
  2201 + res = arg1 + arg2 + arg3 + arg5[0] + arg5[2]
  2202 + if arg6: res += arg6[0]
  2203 + return res
  2204 +
  2205 + #def set_specific2(self, arg1:str, arg2:int): pass
  2206 +
  2207 + def do_specific3(self):
  2208 + pass
  2209 +
  2210 +
  2211 + ###
  2212 + # ================================================================================================
  2213 + # DEVICE SPECIFIC FUNCTIONS (abstract for Agent, overriden and implemented by AgentDevice)
  2214 + # ================================================================================================
  2215 + ###
  2216 +
  2217 + # To be overriden by subclass (AgentDevice...)
  2218 + # @abstract
  2219 + def is_device_level_cmd(self, cmd):
  2220 + return False
  2221 +
  2222 + '''
  2223 + # to be overriden by subclass (AgentDevice)
  2224 + # @abstract
  2225 + def exec_device_cmd_if_possible(self, cmd:AgentCmd):
  2226 + pass
  2227 +
  2228 + # TO BE OVERRIDEN by subclass (AgentDevice)
  2229 + # @abstract
  2230 + def exec_device_cmd(self, cmd:AgentCmd):
  2231 + #self.exec_cmd_from_its_name(cmd)
  2232 + pass
  2233 + '''
  2234 +
  2235 +
  2236 +
  2237 +
  2238 +
  2239 + """
  2240 + =================================================================
  2241 + TEST DEDICATED FUNCTIONS
  2242 + =================================================================
  2243 + """
  2244 +
  2245 + def _set_debug_mode(self, mode:bool):
  2246 + self.DEBUG_MODE=mode
  2247 +
  2248 + def _set_with_simulator(self, mode:bool):
  2249 + self.WITH_SIMULATOR=mode
  2250 +
  2251 + def _set_test_mode(self, mode:bool):
  2252 + self.TEST_MODE = mode
  2253 + if self.TEST_MODE: log.info("in TEST MODE")
  2254 +
  2255 + def _TEST_get_next_command_to_send(self)->AgentCmd:
  2256 + #cmd_full_name, validity, res_expected, after_status = next(self.TEST_COMMANDS, (None,None,None,None))
  2257 + cmd_full_name, validity, expected_res, expected_status = next(self.TEST_COMMANDS)
  2258 + #print(expected_final_status)
  2259 + #print(cmd_full_name, res_expected)
  2260 + #return cmd_name
  2261 + if cmd_full_name is None: return None
  2262 + # Remove excessive spaces
  2263 + import re
  2264 + cmd_full_name = re.sub(r"\s+", " ", cmd_full_name).strip()
  2265 + if ' ' not in cmd_full_name: raise Exception('Command is malformed:', cmd_full_name)
  2266 + agent_recipient, cmd_name_and_args = cmd_full_name.split(' ', 1)
  2267 + if agent_recipient == 'self': agent_recipient = self.name
  2268 + cmd_name, cmd_args = cmd_name_and_args, None
  2269 + if ' ' in cmd_name_and_args: cmd_name,cmd_args = cmd_name_and_args.split(' ', 1)
  2270 + cmd = self.create_cmd_for(agent_recipient, cmd_name, cmd_args, validity)
  2271 + cmd.expected_res = expected_res
  2272 + cmd.expected_status = expected_status
  2273 + # If no cmd created (because of error, bad AgentDevice name), call again this method for next cmd
  2274 + #if cmd is None: return self._TEST_get_next_command_to_send()
  2275 + return cmd
  2276 +
  2277 + """
  2278 + def simulator_send_next_command(self):
  2279 + #self._current_test_cmd = "set_state:idle" if self._current_test_cmd=="set_state:active" else "set_state:active"
  2280 + #if self._nb_test_cmds == 4: self._current_test_cmd = "do_exit"
  2281 + cmd_name = next(self.TEST_COMMANDS, None)
  2282 + #self.printd("next cmd is ", cmd_name)
  2283 + if cmd_name is None: return
  2284 + #Command.objects.create(sender=self.name, recipient=self.name, name=cmd_name)
  2285 + recipient_agent = self.name if self.TEST_COMMANDS_DEST=="myself" else self.TEST_COMMANDS_DEST
  2286 + Command.objects.create(sender=self.name, recipient=recipient_agent, name=cmd_name)
  2287 + #time.sleep(1)
  2288 + #self._TEST_current_cmd_idx += 1
  2289 + #self._nb_test_cmds += 1
  2290 + """
  2291 +
  2292 + def _TEST_check_cmd_res_and_status(self, cmd:AgentCmd):
  2293 +
  2294 + if not self.is_in_test_mode(): return
  2295 + if not cmd: return
  2296 +
  2297 + log.debug("*** CHECK ***")
  2298 + #if hasattr(self._cmdts,'expected_res'):
  2299 +
  2300 + log.debug(cmd.result)
  2301 + log.debug(self._cmdts.expected_res)
  2302 + if cmd.is_executed() and self._cmdts.expected_res:
  2303 + assert(cmd.result == self._cmdts.expected_res)
  2304 +
  2305 + log.debug(cmd.state)
  2306 + log.debug(self._cmdts.expected_status)
  2307 + #if hasattr(self._cmdts,'expected_status'):
  2308 + if self._cmdts.expected_status:
  2309 + assert(cmd.state == self._cmdts.expected_status)
  2310 +
  2311 +
  2312 + def _TEST_test_routine_process(self):
  2313 + """
  2314 + TEST MODE ONLY
  2315 +
  2316 + Send next command from scenario defined in TEST_COMMANDS_LIST
  2317 + (only if previous command finished)
  2318 + """
  2319 +
  2320 + if not self.is_in_test_mode(): return
  2321 +
  2322 + log.info("(TEST mode) Trying to send a new command if possible...")
  2323 +
  2324 + # There is a current command being processed
  2325 + # => check if next command is "do_abort"
  2326 + # => if so, instantly send a "do_abort" to abort previous command
  2327 + if self._cmdts is not None:
  2328 + log.info(f"Waiting for end execution of cmd '{self._cmdts.name}' (sent to {self._cmdts.recipient}) ...")
  2329 + # Update cmdts fields from DB
  2330 + self._cmdts.refresh_from_db()
  2331 +
  2332 + # Current cmd is pending or running
  2333 + if self._cmdts.is_pending() or self._cmdts.is_running():
  2334 + if self._next_cmdts is None:
  2335 + # If next command is "do_abort" then abort becomes the new current command (to be sent)
  2336 + self._next_cmdts = self._TEST_get_next_command_to_send()
  2337 + if self._next_cmdts and self._next_cmdts.name == "do_abort":
  2338 + # Wait a little to give a chance to agentB to start execution of current command,
  2339 + # so that we can abort it then (otherwise it won't be aborted!!)
  2340 + #time.sleep(4)
  2341 + self.waitfor(4)
  2342 + self._cmdts = self._next_cmdts
  2343 + self._next_cmdts = None
  2344 + log.info("***")
  2345 + #log.info(f"*** SEND ", self._cmdts)
  2346 + log.info(f"***" + str(self._cmdts))
  2347 + log.info("***")
  2348 + self._cmdts.send()
  2349 +
  2350 + # Current cmd is no more running
  2351 + else:
  2352 +
  2353 + # Execution was not completed
  2354 + #if self._cmdts.is_expired() or self._cmdts.is_skipped() or self._cmdts.is_killed():
  2355 + if self._cmdts.is_skipped() or self._cmdts.is_killed():
  2356 + log.info("Command was not completed")
  2357 + # 2 possible scenarios:
  2358 + # - (1) Send the SAME command again
  2359 + '''
  2360 + self.printd("Command was not completed, so I send it again")
  2361 + # The command was not completed, so, make a copy of it and send it again
  2362 + # For this, it is enough to set primary key to None,
  2363 + # then the send() command below will save a NEW command
  2364 + #self._cmdts = copy.copy(self._cmdts)
  2365 + self._cmdts.id = None
  2366 + SEND_A_NEW_COMMAND = True
  2367 + '''
  2368 + # - (2) Send next command
  2369 + #self._cmdts = None
  2370 +
  2371 + # Execution was completeted => get result
  2372 + elif self._cmdts.is_executed():
  2373 + cmdts_res = self._cmdts.get_result()
  2374 + print("toto")
  2375 + log.info(f"Cmd executed. Result is '{cmdts_res}'")
  2376 + #cmdts_is_processed = True
  2377 + ''' Optimisation possible pour gagner une iteration:
  2378 + self._cmdts = self.simulator_get_next_command_to_send()
  2379 + # No more command to send (from simulator) => return
  2380 + if self._cmdts is None: return
  2381 + SEND_A_NEW_COMMAND = True
  2382 + '''
  2383 + # Set cmdts to None so that a new command will be sent at next iteration
  2384 + self._cmdts = None
  2385 +
  2386 + # No currently running command => get a new command and SEND it
  2387 + if self._cmdts is None:
  2388 + if self._next_cmdts is not None:
  2389 + self._cmdts = self._next_cmdts
  2390 + self._next_cmdts = None
  2391 + else:
  2392 + self._cmdts = self._TEST_get_next_command_to_send()
  2393 + # No more command to send (from simulator) => return and EXIT
  2394 + if self._cmdts is None:
  2395 + self.DO_MAIN_LOOP = False
  2396 + return
  2397 + # Send cmd (= set as pending and save)
  2398 + log.info("***")
  2399 + #self.printd(f"*** SEND ", self._cmdts)
  2400 + log.info(f"*** NEW COMMAND TO SEND is: " + str(self._cmdts))
  2401 + if hasattr(self._cmdts,"expected_res") :
  2402 + log.info(f"*** (with expected result & final status : " + "'"+str(self._cmdts.expected_res)+"' & '"+str(self._cmdts.expected_status) + "')")
  2403 + log.info("***")
  2404 + #self._cmdts.set_as_pending()
  2405 + # SEND
  2406 + self._cmdts.send()
  2407 + #cmdts_is_processed = False
  2408 + #cmdts_res = None
  2409 +
  2410 +
  2411 + def _TEST_test_results(self):
  2412 + if not (self.TEST_MODE and self.TEST_WITH_FINAL_TEST): return
  2413 + if self.TEST_COMMANDS_LIST == []: return
  2414 +
  2415 + nb_commands_to_send = len(self.TEST_COMMANDS_LIST)
  2416 + nb_commands_sent, commands = self._TEST_test_results_start()
  2417 + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST)
  2418 +
  2419 + # General (default) test
  2420 + #self.printd(commands[0].name, "compared to", self.TEST_COMMANDS_LIST[0].split()[1])
  2421 + assert commands[0].name == self.TEST_COMMANDS_LIST[0].split()[1]
  2422 + last_cmd = commands[-1]
  2423 + assert last_cmd.name == self.TEST_COMMANDS_LIST[-1].split()[1]
  2424 + assert last_cmd.name == "do_exit"
  2425 + assert last_cmd.is_executed()
  2426 + assert last_cmd.get_result() == "SHOULD BE DONE NOW"
  2427 +
  2428 + nb_asserted = 0
  2429 + nb_agent_general = 0
  2430 + nb_unknown = 0
  2431 + nb_unimplemented = 0
  2432 + for cmd in commands:
  2433 + assert cmd.is_executed() or cmd.is_killed() or cmd.is_skipped()
  2434 + nb_asserted += 1
  2435 + if cmd.is_agent_general_cmd():
  2436 + nb_agent_general += 1
  2437 + if cmd.name == "do_unknown":
  2438 + assert cmd.is_skipped()
  2439 + #assert "UnimplementedGenericCmdException" in cmd.get_result()
  2440 + assert "INVALID COMMAND" in cmd.get_result()
  2441 + nb_unknown += 1
  2442 + #if cmd.name in ["do_unimplemented", "do_unknown"]:
  2443 + if cmd.name == "do_unimplemented":
  2444 + assert cmd.is_skipped()
  2445 + assert "UnimplementedGenericCmdException" in cmd.get_result()
  2446 + nb_unimplemented += 1
  2447 + assert nb_asserted == nb_commands_sent
  2448 + log.info(f"{nb_commands_to_send} cmds I had to send <==> {nb_asserted} cmds executed (or killed), {nb_commands_to_send-nb_commands_sent} cmd ignored")
  2449 + log.info("Among executed commands:")
  2450 + log.info(f"- {nb_agent_general} AGENT general command(s)")
  2451 + log.info(f"- {nb_unimplemented} unimplemented command(s) => UnimplementedGenericCmdException raised then command was skipped")
  2452 + log.info(f"- {nb_unknown} unknown command(s) => skipped")
  2453 +
  2454 +
  2455 + # Can be overriden by subclass (AgentDevice)
  2456 + self.TEST_test_results_other(commands)
  2457 + '''
  2458 + (EP) moved to AgentDevice
  2459 + # Now test that any "AD get_xx" following a "AD set_xx value" command has result = value
  2460 + for i,cmd_set in enumerate(commands):
  2461 + if cmd_set.name.startswith('set_'):
  2462 + commands_after = commands[i+1:]
  2463 + for cmd_get in commands_after:
  2464 + if cmd_get.name.startswith('get_') and cmd_get.name[4:]==cmd_set.name[4:] and cmd_get.device_type==cmd_set.device_type:
  2465 + log.info("cmd_get.result == cmd_set.args ?" + str(cmd_get.result) + ' ' + str(cmd_set.args))
  2466 + assert cmd_get.get_result() == ','.join(cmd_set.args), "A get_xx command did not gave the expected result as set by a previous set_xx command"
  2467 + break
  2468 + '''
  2469 +
  2470 + # Specific (detailed) test (to be overriden by subclass)
  2471 + nb_asserted2 = self.TEST_test_results_main(commands)
  2472 + self._TEST_test_results_end(nb_asserted)
  2473 +
  2474 + def _TEST_prepare(self):
  2475 + if not self.is_in_test_mode(): return
  2476 + log.debug("\n!!! In TEST mode !!! => preparing to run a scenario of test commands")
  2477 + log.debug("- Current test commands list scenario is:\n" + str(self.TEST_COMMANDS_LIST))
  2478 + if not self.WITH_SIMULATOR:
  2479 + log.debug("\n!!! In TEST but no SIMULATOR mode (using REAL device) !!! => removing dangerous commands for real devices... :")
  2480 + TEST_COMMANDS_LIST_copy = self.TEST_COMMANDS_LIST.copy()
  2481 + for cmd in TEST_COMMANDS_LIST_copy:
  2482 + cmd_key = cmd.split()[1]
  2483 + if ("set_" in cmd_key) or ("do_start" in cmd_key) or cmd_key in ["do_init", "do_goto", "do_open", "do_close"]:
  2484 + self.TEST_COMMANDS_LIST.remove(cmd)
  2485 + log.debug("- NEW test commands list scenario is:\n" + self.TEST_COMMANDS_LIST, '\n')
  2486 +
  2487 + # Can be overriden by subclass (AgentDevice)
  2488 + def TEST_test_results_other(self, commands):
  2489 + pass
  2490 +
  2491 + def _TEST_test_results_start(self):
  2492 + print()
  2493 + log.info("--- Testing if the commands I SENT had the awaited result")
  2494 + log.info("Here are the last commands I sent:")
  2495 + #commands = list(Command.get_last_N_commands_for_agent(self.name, 16))
  2496 + #commands = Command.get_last_N_commands_sent_to_agent(self.name, 16)
  2497 + nb_commands = len(self.TEST_COMMANDS_LIST)
  2498 + if "ad_unknown get_dec" in self.TEST_COMMANDS_LIST: nb_commands -= 1
  2499 + commands = AgentCmd.get_last_N_commands_sent_by_agent(self.name, nb_commands)
  2500 + AgentCmd.show_commands(commands)
  2501 + return nb_commands, commands
  2502 + """ OLD SCENARIO
  2503 + nb_asserted = 0
  2504 + for cmd in commands:
  2505 + if cmd.name == "specific0":
  2506 + assert cmd.is_skipped()
  2507 + nb_asserted+=1
  2508 + if cmd.name == "specific1":
  2509 + assert cmd.result == "in step #5/5"
  2510 + assert cmd.is_executed()
  2511 + nb_asserted+=1
  2512 + if cmd.name in ("specific2","specific3"):
  2513 + assert cmd.is_killed()
  2514 + nb_asserted+=1
  2515 + if cmd.name in ("specific4", "specific5", "specific6", "specific7", "specific8"):
  2516 + assert cmd.is_pending()
  2517 + nb_asserted+=1
  2518 + # 2 cmds abort
  2519 + if cmd.name in ("do_abort"):
  2520 + assert cmd.is_executed()
  2521 + nb_asserted+=1
  2522 + if cmd.name in ("do_exit"):
  2523 + assert cmd.is_executed()
  2524 + nb_asserted+=1
  2525 + assert nb_asserted == 12
  2526 + self.printd("--- Finished testing => result is ok")
  2527 + """
  2528 +
  2529 + # To be overriden by subclass
  2530 + def TEST_test_results_main(self, commands):
  2531 + return 0
  2532 + '''
  2533 + nb_asserted = 0
  2534 + self.printd("from simulator_test_results_main", commands)
  2535 + for cmd in commands:
  2536 + assert cmd.is_executed()
  2537 + nb_asserted+=1
  2538 + return nb_asserted
  2539 + '''
  2540 +
  2541 + def _TEST_test_results_end(self, nb_asserted):
  2542 + #nb_commands_to_send = len(self.TEST_COMMANDS_LIST)
  2543 + #self.printd(nb_asserted, "vs", nb_commands_to_send)
  2544 + #assert nb_asserted == nb_commands_to_send
  2545 + #self.printd(f"************** Finished testing => result is ok ({nb_asserted} assertions) **************")
  2546 + printFullTerm(Colors.GREEN, f"************** Finished testing => result is ok ({nb_asserted} assertions) **************")
  2547 +
  2548 +
  2549 +
  2550 +
  2551 +
  2552 +
  2553 +"""
  2554 +=================================================================
  2555 + MAIN
  2556 +=================================================================
  2557 +"""
  2558 +
  2559 +def extract_parameters():
  2560 + """ Usage: Agent.py [-t] [configfile] """
  2561 + # arg 1 : -t
  2562 + # arg 2 : configfile
  2563 + DEBUG_MODE = False
  2564 + TEST_MODE = False
  2565 + WITH_SIM = False
  2566 + VERBOSE_MODE = False
  2567 + configfile = None
  2568 + log.debug("args:" + str(sys.argv))
  2569 + for arg in sys.argv[1:] :
  2570 + if arg == "-t": TEST_MODE = True
  2571 + elif arg == "-s": WITH_SIM = True
  2572 + elif arg == "-d": DEBUG_MODE = True
  2573 + elif arg == "-v": VERBOSE_MODE = True
  2574 + #else: configfile = arg
  2575 + '''
  2576 + if len(sys.argv) > 1:
  2577 + if sys.argv[1] == "-t":
  2578 + TEST_MODE = True
  2579 + if len(sys.argv) == 3:
  2580 + configfile = sys.argv[2]
  2581 + else:
  2582 + configfile = sys.argv[1]
  2583 + '''
  2584 + #return DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile
  2585 + return DEBUG_MODE, TEST_MODE, VERBOSE_MODE
  2586 +
  2587 +#def build_agent(Agent_type:Agent, name="GenericAgent", RUN_IN_THREAD=True):
  2588 +#def build_agent(Agent_type:Agent, RUN_IN_THREAD=True):
  2589 +def build_agent(Agent_type:Agent) -> Agent :
  2590 + #DEBUG_MODE, WITH_SIM, TEST_MODE, VERBOSE_MODE, configfile = extract_parameters()
  2591 + DEBUG_MODE, TEST_MODE, VERBOSE_MODE = extract_parameters()
  2592 + log.debug("debug mode is" + os.getenv("PYROS_DEBUG"))
  2593 +
  2594 + #print(logger)
  2595 +
  2596 + #agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True)
  2597 + #agent = Agent_type(configfile, RUN_IN_THREAD, DEBUG_MODE=DEBUG_MODE)
  2598 + #agent = Agent_type(RUN_IN_THREAD)
  2599 + agent = Agent_type()
  2600 + # AgentSP isn't in a config, so to avoid that WITH_SIM returns an error it's a special case
  2601 + if agent.name == "AgentSP":
  2602 + agent._set_with_simulator(False)
  2603 + agent._set_test_mode(TEST_MODE)
  2604 + return agent
  2605 + #agent = Agent_type(name, configfile, RUN_IN_THREAD)
  2606 + # Get the information of the agent name (name of class) within obsconfig and get the "is_real" attribute
  2607 + if agent.name in agent.get_config().get_agents(agent.unit).keys():
  2608 + if agent.get_config().get_agent_information(agent.unit,agent.name).get("is_real"):
  2609 + WITH_SIM = not agent.get_config().get_agent_information(agent.unit,agent.name)["is_real"]
  2610 + else:
  2611 + WITH_SIM = True
  2612 + else:
  2613 + WITH_SIM = True
  2614 + agent._set_with_simulator(WITH_SIM)
  2615 + agent._set_test_mode(TEST_MODE)
  2616 + #print(logger)
  2617 + #logg.info("agent built, return it")
  2618 +
  2619 + #agent._set_debug_mode(DEBUG_MODE)
  2620 + return agent
  2621 +
  2622 +
  2623 +if __name__ == "__main__":
  2624 +
  2625 + '''
  2626 + # with thread
  2627 + RUN_IN_THREAD=True
  2628 + # with process
  2629 + #RUN_IN_THREAD=False
  2630 + '''
  2631 +
  2632 + #agent = build_agent(Agent, RUN_IN_THREAD=RUN_IN_THREAD)
  2633 + agent = build_agent(Agent)
  2634 +
  2635 + agent.show_config()
  2636 +
  2637 + #agent = build_agent(Agent, name="GenericAgent", RUN_IN_THREAD=RUN_IN_THREAD)
  2638 + '''
  2639 + TEST_MODE, WITH_SIM, configfile = extract_parameters()
  2640 + agent = Agent("GenericAgent", configfile, RUN_IN_THREAD=True)
  2641 + #agent.setSimulatorMode(TEST_MODE)
  2642 + agent.setTestMode(TEST_MODE)
  2643 + agent.setWithSimulator(WITH_SIM)
  2644 + self.printd(agent)
  2645 + '''
  2646 + agent.run()
... ...
pyros.py
... ... @@ -1115,8 +1115,6 @@ def new_start(configfile: str, observatory: str, unit: str, computer_hostname: s
1115 1115 cmd += " -t"
1116 1116 if verbose_mode():
1117 1117 cmd += " -v"
1118   - if configfile:
1119   - cmd += f" {configfile}"
1120 1118 if computer_hostname:
1121 1119 cmd += f" --computer {computer_hostname}"
1122 1120  
... ...