Commit a2dbbde10e44f298c4aade36612d01105ea81c1a

Authored by Alexis Koralewski
1 parent d42ec18b
Exists in dev

Adding new configpyros class, renaming previous configpyros class to obsconfig_class

config/pyros/__init__.py 0 → 100644
config/pyros/config_pyros.py 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +import yaml,os,time
  2 +import pykwalify.core
  3 +from pykwalify.errors import SchemaError
  4 +
  5 +class configpyros:
  6 + def check_and_return_config(self,yaml_file:str,schema_file:str)->dict:
  7 + """
  8 + Check if yaml_file is valid for the schema_file and return an dictionary of the config file
  9 +
  10 + Args:
  11 + yaml_file (str): Path to the config_file to be validated
  12 + schema_file (str): Path to the schema file
  13 +
  14 + Returns:
  15 + dict: dictionary of the config file (with values)
  16 + """
  17 + # disable pykwalify error to clean the output
  18 + #####logging.disable(logging.ERROR)
  19 + try:
  20 + can_yaml_file_be_read = False
  21 + while can_yaml_file_be_read != True:
  22 + if os.access(yaml_file, os.R_OK):
  23 + can_yaml_file_be_read = True
  24 + else:
  25 + print(f"{yaml_file} can't be accessed, waiting for availability")
  26 + time.sleep(0.5)
  27 +
  28 + c = pykwalify.core.Core(source_file=yaml_file, schema_files=[self.config_path+"/"+schema_file])
  29 + return c.validate(raise_exception=True)
  30 + except SchemaError:
  31 + for error in c.errors:
  32 + print("Error :",str(error).split(". Path")[0])
  33 + print("Path to error :",error.path)
  34 + return None
  35 + except IOError:
  36 + print("Error when reading the observatory config file")
  37 +
  38 +
  39 + def read_and_check_config_file(self,yaml_file:str)->dict:
  40 + """
  41 + Read the schema key of the config file to retrieve schema name and proceed to the checking of that config file
  42 + Call check_and_return_config function and print its return.
  43 +
  44 + Args:
  45 + yaml_file (str): path to the config file
  46 + Returns:
  47 + dict: Dictionary of the config file (with values)
  48 + """
  49 + try:
  50 + can_config_file_be_read = False
  51 + while can_config_file_be_read != True:
  52 +
  53 + if os.access(yaml_file, os.R_OK):
  54 + can_config_file_be_read = True
  55 + else:
  56 + print(f"{yaml_file} can't be accessed, waiting for availability")
  57 + time.sleep(0.5)
  58 + with open(yaml_file, 'r') as stream:
  59 + print(f"Reading {yaml_file}")
  60 + config_file = yaml.safe_load(stream)
  61 + result = self.check_and_return_config(yaml_file,config_file["schema"])
  62 + if result == None:
  63 + print("Error when reading and validating config file, please check the errors right above")
  64 + exit(1)
  65 + return result
  66 +
  67 + except yaml.YAMLError as exc:
  68 + print(exc)
  69 + except Exception as e:
  70 + print(e)
  71 + return None
  72 +
  73 + def __init__(self,pyros_config_file):
  74 + self.config_path = os.path.abspath(os.path.join(os.environ.get("DJANGO_PATH"),"../../../config/pyros//"))
  75 + self.pyros_config = self.read_and_check_config_file(pyros_config_file)
... ...
config/pyros/config_pyros.yml 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +schema: schema_pyros_config.yml
  2 +general:
  3 + logo: logo_pyros.png
  4 + color_theme: sienna
  5 + nb_element_per_page: 8
  6 +USR:
  7 + nb_element_per_page: 10
  8 +SCP: ~
  9 +OCF: ~
  10 +
  11 +
  12 +
0 13 \ No newline at end of file
... ...
config/pyros/schema_pyros_config.yml 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +type: map
  2 +mapping:
  3 + schema:
  4 + type: str
  5 + required: True
  6 + general:
  7 + type: map
  8 + required: True
  9 + mapping:
  10 + logo:
  11 + type: str
  12 + required: True
  13 + color_theme:
  14 + type: str
  15 + required: True
  16 + nb_element_per_page:
  17 + type: int
  18 + required: True
  19 + USR:
  20 + type: any
  21 + SCP:
  22 + type: any
  23 + OCF:
  24 + type: any
0 25 \ No newline at end of file
... ...
pyros.py
... ... @@ -21,6 +21,7 @@ import sys
21 21 import time
22 22 import re
23 23 import glob
  24 +from config.pyros.config_pyros import configpyros
24 25 from git import Repo
25 26 #import logging
26 27  
... ... @@ -569,6 +570,8 @@ def test(app):
569 570 print("Running tests")
570 571 os.environ["PATH_TO_OBSCONF_FOLDER"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"obsconfig/fixtures/")
571 572 os.environ["unit_name"] = ""
  573 + configfile = 'config_pyros.yml'
  574 + os.environ["pyros_config_file"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"../../../config/pyros/", configfile)
572 575 #start_dir = os.getcwd()
573 576 if app == None:
574 577 #apps = ['obsconfig','scientific_program','common', 'scheduler', 'routine_manager', 'user_manager', 'alert_manager.tests.TestStrategyChange']
... ... @@ -663,7 +666,6 @@ def initdb():
663 666 return True
664 667  
665 668  
666   -
667 669 @pyros_launcher.command(help="Launch an agent")
668 670 #@global_test_options
669 671 @click.argument('agent')
... ... @@ -677,8 +679,15 @@ def start(agent:str, configfile:str, observatory:str, unit:str):
677 679 log.debug("Running start command")
678 680 if configfile:
679 681 log.debug("With config file"+ configfile)
  682 + os.environ["pyros_config_file"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"../../../config/pyros/", configfile)
680 683 else:
681   - configfile = ''
  684 + configfile = 'config_pyros.yml'
  685 + os.environ["pyros_config_file"] = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"../../../config/pyros/", configfile)
  686 +
  687 + logo_name = configpyros(os.environ["pyros_config_file"]).pyros_config["general"]["logo"]
  688 + logo_path = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"../../../config/pyros/", logo_name)
  689 + media_path = os.path.join(os.path.abspath(PYROS_DJANGO_BASE_DIR),"misc/static/media",logo_name)
  690 + shutil.copy(logo_path,media_path)
682 691 #if test_mode(): print("in test mode")
683 692 #if verbose_mode(): print("in verbose mode")
684 693 if observatory == None or len(observatory) == 0:
... ... @@ -709,8 +718,8 @@ def start(agent:str, configfile:str, observatory:str, unit:str):
709 718 '''
710 719  
711 720 # add path to pyros_django folder as the config class is supposed to work within this folder
712   - #cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{os.path.join(PYROS_DJANGO_BASE_DIR,os.environ.get('PATH_TO_OBSCONF_FILE'))}')\""
713   - cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.configpyros import ConfigPyros\nConfigPyros('{obs_config_file_path}')\""
  721 + #cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig\nOBSConfig('{os.path.join(PYROS_DJANGO_BASE_DIR,os.environ.get('PATH_TO_OBSCONF_FILE'))}')\""
  722 + cmd_test_obs_config = f"-c \"from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig\nOBSConfig('{obs_config_file_path}')\""
714 723 if not execProcessFromVenv(cmd_test_obs_config):
715 724 # Observatory configuration has an issue
716 725 exit(1)
... ...
src/core/pyros_django/agent/Agent.py
... ... @@ -128,7 +128,7 @@ from common.models import AgentSurvey, AgentCmd, AgentLogs
128 128  
129 129 #from config.configpyros import ConfigPyros
130 130 from config.old_config.configpyros import ConfigPyros as ConfigPyrosOld
131   -from src.core.pyros_django.obsconfig.configpyros import ConfigPyros
  131 +from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig
132 132  
133 133 #from dashboard.views import get_sunelev
134 134 #from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault
... ... @@ -460,7 +460,7 @@ class Agent:
460 460 obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"]
461 461 path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"]
462 462 unit = os.environ["unit_name"]
463   - oc = ConfigPyros(obs_config_file_path)
  463 + oc = OBSConfig(obs_config_file_path)
464 464 self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit)
465 465  
466 466 #self.name = name
... ... @@ -545,7 +545,7 @@ class Agent:
545 545  
546 546  
547 547  
548   - def set_config(self, oc: ConfigPyros, obs_config_file_path: str, path_to_obs_config_folder: str, unit: str):
  548 + def set_config(self, oc: OBSConfig, obs_config_file_path: str, path_to_obs_config_folder: str, unit: str):
549 549 self._oc = {
550 550 'config' : oc,
551 551 'env' : [
... ...
src/core/pyros_django/dashboard/views.py
... ... @@ -25,10 +25,10 @@ from devices.CameraVISRemoteControlDefault import CameraVISRemoteControlDefault
25 25 from devices.CameraNIRRemoteControlDefault import CameraNIRRemoteControlDefault
26 26 from devices import PLC
27 27 import time,os
28   -from obsconfig.configpyros import ConfigPyros
  28 +from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig
29 29 from django.conf import settings as pyros_settings
30   -
31 30 sys.path.append("../../..")
  31 +from config.pyros.config_pyros import configpyros
32 32 #import utils.celme as celme
33 33 import vendor.guitastro as guitastro
34 34  
... ... @@ -41,11 +41,13 @@ MAX_LOGS_LINES = 100
41 41 log = l.setupLogger("dashboard", "dashboard")
42 42  
43 43 def index(request):
44   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  44 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
45 45 observatory_name = config.get_obs_name()
46 46 unit_name = config.unit_name
47 47 request.session["obsname"] = observatory_name+" "+unit_name
48   -
  48 + request.session["pyros_config"] = pyros_settings.CONFIG_PYROS
  49 + logo = pyros_settings.CONFIG_PYROS.get("general").get("logo")
  50 + request.session["logo"] = "media/"+logo
49 51 message = ""
50 52 if request.user.is_authenticated:
51 53 if SP_Period_Guest.objects.filter(email=request.user.email):
... ...
src/core/pyros_django/misc/static/media/logo_pyros.png 0 → 100644

59.3 KB

src/core/pyros_django/misc/templates/base.html
... ... @@ -40,6 +40,13 @@
40 40  
41 41 <body>
42 42 <style>
  43 +:root{
  44 + --main-bg-color: {{ request.session.pyros_config.general.color_theme }}
  45 +}
  46 +.bg-gradient-primary{
  47 + background-color: var(--main-bg-color);
  48 + background-image: None;
  49 +}
43 50 /*
44 51 #page-wrapper {
45 52 width: 80vw;
... ... @@ -125,6 +132,12 @@ a {
125 132 margin-right:1vw;
126 133 }
127 134  
  135 +#logo_icon{
  136 + height: 4.5vh;
  137 + width: 4.5vw;
  138 + float:left;
  139 + margin-right:1vw;
  140 +}
128 141 .dropdown_user_actions {
129 142 overflow: hidden;
130 143 min-width:8.5vw;
... ... @@ -241,7 +254,11 @@ footer{
241 254 <div class="sidebar-brand d-flex align-items-center justify-content-center">
242 255 <a class="navbar-brand" href={% url "index" %} style="color: black;">
243 256 <img class="sidebar-brand-icon" src="{% static 'media/home.png' %}" id="home_icon"></a>
244   - <div class="sidebar-brand-text mx-3">PyROS</div>
  257 + {% if request.session.logo %}
  258 + <img class="sidebar-brand-icon" src="{% static request.session.logo %}" id="logo_icon"></a>
  259 + {% else %}
  260 + <div class="sidebar-brand-text mx-3">PyROS</div>
  261 + {% endif %}
245 262  
246 263 </div>
247 264 {% comment %} OLD SIDE MENU
... ... @@ -253,7 +270,7 @@ footer{
253 270 <li class="nav-item"><a class="nav-link" href="{% url "current_schedule" %}">Schedule</a></li>
254 271 {# TODO: TBD #}
255 272 {% if USER_LEVEL|ifinlist:"Admin,Operator,Unit-PI,Management board member,Observer,TAC" %}
256   - <li class="nav-item"><a class="nav-link" href="{% url "requests_list" %}">Request</a></li>
  273 + <li class="nav-item"><a class="nav-link" href="{% url "sequences_list" %}">Request</a></li>
257 274 <li class="nav-item"><a class="nav-link" href="#">Images</a></li>
258 275 {% endif %}
259 276  
... ...
src/core/pyros_django/obsconfig/configpyros.py renamed to src/core/pyros_django/obsconfig/obsconfig_class.py
1   -#!/usr/bin/env python3
2   -import pykwalify.core
3   -import sys
4   -import yaml, logging, os, pickle, time
5   -from datetime import datetime
6   -from pykwalify.errors import PyKwalifyException,SchemaError
7   -from pathlib import Path
8   -
9   -class ConfigPyros:
10   - # (AKo) : Config file path is checked on the settings file, if the file isn't valid (i.e not found) the error will be launched by the settings file when starting the application
11   - devices_links = {}
12   - current_file = None
13   - #COMPONENT_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/components/")
14   - #GENERIC_DEVICES_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/devices/")
15   - pickle_file = "obsconfig.p"
16   - obs_config = None
17   - devices = None
18   - computers = None
19   - agents = None
20   - obs_config_file_content = None
21   - #obs_config_path = os.environ.get("PATH_TO_OBSCONF_FOLDER",os.path.join(os.environ["DJANGO_PATH"],"../../../privatedev/config/default/"))
22   - errors = None
23   -
24   - def verify_if_pickle_needs_to_be_updated(self, observatory_config_file)->bool:
25   - """
26   -
27   - Args:
28   - observatory_config_file ([type]): [description]
29   -
30   - Returns:
31   - bool: [description]
32   - """
33   - self.CONFIG_PATH = os.path.dirname(observatory_config_file)+"/"
34   - self.obs_config_path = self.CONFIG_PATH
35   - #self.CONFIG_PATH = self.obs_config_path
36   - if os.path.isfile(self.CONFIG_PATH+self.pickle_file) == False:
37   - return True
38   - else:
39   - pickle_file_mtime = os.path.getmtime(self.CONFIG_PATH+self.pickle_file)
40   - obs_config_mtime = os.path.getmtime(observatory_config_file)
41   -
42   - obs_config = self.read_and_check_config_file(observatory_config_file)
43   - if obs_config_mtime > pickle_file_mtime:
44   - # create obs file (yaml) from pickle["obsconfig"] with date of pickle within history folder-> nom ficher + année + mois + jour + datetime (avec secondes) -> YYYY/MM/DD H:m:s
45   - pickle_datetime = datetime.utcfromtimestamp(pickle_file_mtime).strftime("%Y%m%d_%H%M%S")
46   - # Create history folder if doesn't exist
47   - Path(self.obs_config_path+"/history/").mkdir(parents=True, exist_ok=True)
48   - file_name = f"{self.obs_config_path}/history/observatory_{pickle_datetime}.yml"
49   - config_file = open(observatory_config_file,"r")
50   -
51   - with open(file_name, 'w') as f:
52   - f.write(config_file.read())
53   - return True
54   - if obs_config == None:
55   - print(f"Error when trying to read config file (path of config file : {observatory_config_file}")
56   - return -1
57   - self.obs_config = obs_config
58   - # check last date of modification for devices files
59   - for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
60   - device_file = self.CONFIG_PATH+device["DEVICE"]["file"]
61   - device_file_mtime = os.path.getmtime(device_file)
62   - if device_file_mtime > pickle_file_mtime:
63   - return True
64   -
65   - for computer in self.obs_config["OBSERVATORY"]["COMPUTERS"]:
66   - computer_file = self.CONFIG_PATH+computer["COMPUTER"]["file"]
67   - computer_file_mtime = os.path.getmtime(computer_file)
68   - if computer_file_mtime > pickle_file_mtime:
69   - return True
70   - return False
71   -
72   - def load(self, observatory_config_file):
73   - pickle_needs_to_be_updated = self.verify_if_pickle_needs_to_be_updated(observatory_config_file)
74   - # check if we already read and load devices configuration and if pickle needs to be updated
75   - if pickle_needs_to_be_updated == False and self.devices != None:
76   - return None
77   - else:
78   - if os.path.isfile(self.CONFIG_PATH+self.pickle_file) and pickle_needs_to_be_updated == False:
79   - print("Reading pickle file")
80   - try:
81   - can_pickle_file_be_read = False
82   - while can_pickle_file_be_read != True:
83   - if os.access(self.CONFIG_PATH+self.pickle_file, os.R_OK):
84   - pickle_dict = pickle.load(open(self.CONFIG_PATH+self.pickle_file,"rb"))
85   - can_pickle_file_be_read = True
86   - else:
87   - time.sleep(0.5)
88   - except IOError:
89   - print("Error when reading the pickle file")
90   - try:
91   - self.obs_config = pickle_dict["obs_config"]
92   - self.computers = pickle_dict["computers"]
93   - self.devices = pickle_dict["devices"]
94   - self.devices_links = pickle_dict["devices_links"]
95   - self.obs_config_file_content = pickle_dict["obs_config_file_content"]
96   - self.raw_config = pickle_dict["raw_config"]
97   - except:
98   - # we rewrite the pickle file, the content will be the same otherwise we would be in the else case
99   - print("Rewritting the pickle file (an error occured while reading it, the content will be the same as it was")
100   - pickle_dict = {}
101   -
102   - self.obs_config = self.read_and_check_config_file(observatory_config_file)
103   - obs_file = open(observatory_config_file,"r")
104   - pickle_dict["raw_config"] = obs_file.read()
105   - obs_file.close()
106   - self.raw_config = pickle_dict["raw_config"]
107   - pickle_dict["obs_config"] = self.obs_config
108   - pickle_dict["devices"] = self.get_devices()
109   - pickle_dict["computers"] = self.get_computers()
110   - pickle_dict["devices_links"] = self.devices_links
111   - pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(observatory_config_file)
112   - print("Writing pickle file")
113   - pickle.dump(pickle_dict,open(self.CONFIG_PATH+self.pickle_file,"wb"))
114   - else:
115   - print("Pickle file needs to be created or updated")
116   - pickle_dict = {}
117   -
118   - self.obs_config = self.read_and_check_config_file(observatory_config_file)
119   - pickle_dict["obs_config"] = self.obs_config
120   - pickle_dict["devices"] = self.get_devices()
121   - pickle_dict["computers"] = self.get_computers()
122   - pickle_dict["devices_links"] = self.devices_links
123   - pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(observatory_config_file)
124   - print("Writing pickle file")
125   - pickle.dump(pickle_dict,open(self.CONFIG_PATH+self.pickle_file,"wb"))
126   -
127   - def check_and_return_config(self,yaml_file:str,schema_file:str)->dict:
128   - """
129   - Check if yaml_file is valid for the schema_file and return an dictionary of the config file
130   -
131   - Args:
132   - yaml_file (str): Path to the config_file to be validated
133   - schema_file (str): Path to the schema file
134   -
135   - Returns:
136   - dict: dictionary of the config file (with values)
137   - """
138   - # disable pykwalify error to clean the output
139   - #####logging.disable(logging.ERROR)
140   - try:
141   - can_yaml_file_be_read = False
142   - while can_yaml_file_be_read != True:
143   - if os.access(yaml_file, os.R_OK):
144   - can_yaml_file_be_read = True
145   - else:
146   - print(f"{yaml_file} can't be accessed, waiting for availability")
147   - time.sleep(0.5)
148   -
149   - c = pykwalify.core.Core(source_file=yaml_file, schema_files=[self.SCHEMA_PATH+schema_file])
150   - return c.validate(raise_exception=True)
151   - except SchemaError:
152   - for error in c.errors:
153   - print("Error :",str(error).split(". Path")[0])
154   - print("Path to error :",error.path)
155   - self.errors = c.errors
156   - return None
157   - except IOError:
158   - print("Error when reading the observatory config file")
159   -
160   - @staticmethod
161   - def check_config(yaml_file:str,schema_file:str)->any:
162   - """
163   - Check if yaml_file is valid for the schema_file and return a boolean or list of errors according the schema
164   -
165   - Args:
166   - yaml_file (str): Path to the config_file to be validated
167   - schema_file (str): Path to the schema file
168   -
169   - Returns:
170   - any: boolean (True) if the configuration is valid according the schema or a list of error otherwise
171   - """
172   - # disable pykwalify error to clean the output
173   - ####logging.disable(logging.ERROR)
174   - try:
175   - can_yaml_file_be_read = False
176   - while can_yaml_file_be_read != True:
177   - if os.access(yaml_file, os.R_OK):
178   - can_yaml_file_be_read = True
179   - else:
180   - print(f"{yaml_file} can't be accessed, waiting for availability")
181   - time.sleep(0.5)
182   -
183   - c = pykwalify.core.Core(source_file=yaml_file, schema_files=[schema_file])
184   - c.validate(raise_exception=True)
185   - return True
186   - except SchemaError:
187   - for error in c.errors:
188   - print("Error :",str(error).split(". Path")[0])
189   - print("Path to error :",error.path)
190   -
191   - return c.errors
192   - except IOError:
193   - print("Error when reading the observatory config file")
194   -
195   - def read_and_check_config_file(self,yaml_file:str)->dict:
196   - """
197   - Read the schema key of the config file to retrieve schema name and proceed to the checking of that config file
198   - Call check_and_return_config function and print its return.
199   -
200   - Args:
201   - yaml_file (str): path to the config file
202   - Returns:
203   - dict: Dictionary of the config file (with values)
204   - """
205   - self.current_file = yaml_file
206   - try:
207   - can_config_file_be_read = False
208   - while can_config_file_be_read != True:
209   -
210   - if os.access(yaml_file, os.R_OK):
211   - can_config_file_be_read = True
212   - else:
213   - print(f"{yaml_file} can't be accessed, waiting for availability")
214   - time.sleep(0.5)
215   - with open(yaml_file, 'r') as stream:
216   - print(f"Reading {yaml_file}")
217   - config_file = yaml.safe_load(stream)
218   -
219   - self.DJANGO_PATH = os.environ.get("DJANGO_PATH",os.path.abspath(os.path.dirname(yaml_file)))
220   - self.SCHEMA_PATH = os.path.join(self.DJANGO_PATH,"../../../config/schemas/")
221   - self.CONFIG_PATH = self.obs_config_path
222   - self.COMPONENT_PATH = os.path.join(self.DJANGO_PATH,"../../../config/components/")
223   - self.GENERIC_DEVICES_PATH = os.path.join(self.DJANGO_PATH,"../../../config/devices/")
224   - result = self.check_and_return_config(yaml_file,config_file["schema"])
225   - if result == None:
226   - print("Error when reading and validating config file, please check the errors right above")
227   - exit(1)
228   - return result
229   -
230   - except yaml.YAMLError as exc:
231   - print(exc)
232   - except Exception as e:
233   - print(e)
234   - return None
235   -
236   - def read_generic_component_and_return_attributes(self,component_name:str)->dict:
237   - file_path = self.COMPONENT_PATH + component_name + ".yml"
238   - try:
239   - with open(file_path, 'r') as stream:
240   - config_file = yaml.safe_load(stream)
241   -
242   - attributes = {}
243   - for attribute in config_file:
244   -
245   - attribute = attribute["attribute"]
246   - attributes[attribute.pop("key")] = attribute
247   - return attributes
248   - except yaml.YAMLError as exc:
249   - print(exc)
250   - except Exception as e:
251   - print(e)
252   - return None
253   -
254   - def read_capability_of_device(self,capability:dict)->dict:
255   - """
256   - Read capability of device and inherit attributes from generic component then overwrite attributes defined in device config
257   -
258   - Args:
259   - capability (dict): dictionary containing a capabilitiy (keys : component and attributes)
260   -
261   - Returns:
262   - dict: dictionary of capability inherited by generic component and overwritten his attributes by current attributes of capability
263   - """
264   -
265   - component_attributes = self.read_generic_component_and_return_attributes(capability["component"])
266   -
267   - attributes = {}
268   - # get all attributes of device's capability
269   - for attribute in capability["attributes"]:
270   - attribute = attribute["attribute"]
271   -
272   - attributes[attribute.pop("key")] = attribute
273   -
274   - # for each attributes of generic component attributes
275   - for attribute_name in attributes.keys():
276   - # merge attributes of general component with specified component in device config file
277   - new_attributes = {**component_attributes[attribute_name],**attributes[attribute_name]}
278   - if "is_enum" in component_attributes[attribute_name].keys():
279   - # make an intersection of both list of values
280   - new_attributes["value"] = list(set(attributes[attribute_name]["value"]) & set(component_attributes[attribute_name]["value"]))
281   - if len(new_attributes["value"]) == 0:
282   - print(f"Value of lastly read device's attribute '{attribute_name}' isn't one of the values of component configuration for this device (component configuration value(s): {component_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
283   - exit(1)
284   - component_attributes[attribute_name] = new_attributes
285   -
286   - # return inherited and overwritten attributes of capability
287   - capability["attributes"] = component_attributes
288   - return capability
289   -
290   - def get_devices_names_and_file(self) -> dict:
291   - """
292   - Return a dictionary giving the device file name by the device name
293   - Returns:
294   - dict: key is device name, value is file name
295   - """
296   - devices_names_and_files = {}
297   - for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
298   - device = device["DEVICE"]
299   -
300   - devices_names_and_files[device["name"]] = device["file"]
301   - return devices_names_and_files
302   -
303   - def read_device_config_file(self,config_file_name:str,is_generic=False)->dict:
304   - """
305   - Read the device config file, inherit generic device config if "generic" key is present in "DEVICE".
306   - Associate capabilities of attached_devices if this device has attached_devices.
307   - Inherit capabilities from generic component and overwritte attributes defined in the device config
308   - Args:
309   - config_file_name (str): file name to be read
310   - is_generic (bool, optional): tells if we're reading a generic configuration (is_generic =True) or not (is_generic = False). Defaults to False.
311   -
312   - Returns:
313   - dict: formatted device configuration (attributes, capabilities...)
314   - """
315   - self.current_file = config_file_name
316   - devices_name_and_file = self.get_devices_names_and_file()
317   - if not is_generic:
318   - # check if device config file is listed in observatory configuration
319   - current_device_name = [device for device,file_name in devices_name_and_file.items() if file_name in config_file_name[len(self.CONFIG_PATH):] ]
320   - if len(current_device_name) <= 0:
321   - print(f"Current file '{config_file_name[len(self.CONFIG_PATH):]}' isn't listed in observatory configuration")
322   - print("The devices names and files are: ")
323   - for device_name,file_name in devices_name_and_file.items():
324   - print(f"device name: '{device_name}', device filename: '{file_name}'")
325   - exit(1)
326   - print(f"Reading {config_file_name}")
327   - try:
328   - with open(config_file_name, 'r') as stream:
329   - config_file = yaml.safe_load(stream)
330   - # if we're reading a generic device configuration, the path to get the schema is different than usual
331   - self.SCHEMA_PATH =os.path.join(self.DJANGO_PATH,"../../../config/schemas/")
332   -
333   - # read and verify that the device configuration match the schema
334   - result = self.check_and_return_config(config_file_name,config_file["schema"])
335   - # if the configuration didn't match the schema or had an error when reading the file
336   - if result == None:
337   - print("Error when reading and validating config file, please check the errors right above")
338   - exit(1)
339   - else:
340   - # the configuration is valid
341   - # storing DEVICE key in device (DEVICE can contains : attributes of device, capabilities, attached devices)
342   - device = result["DEVICE"]
343   - generic_device_config = None
344   - # if the device is associated to an generic configuration, we'll read that generic configuration to inherit his attributes
345   - if "generic" in device:
346   - # storing the whole current config
347   - current_config = result
348   - # read and get the generic device config
349   - generic_device_config = self.read_device_config_file(self.GENERIC_DEVICES_PATH+device["generic"],is_generic=True)
350   - # merge whole device config but we need to merge capabilities differently after
351   - new_config = {**generic_device_config["DEVICE"],**current_config["DEVICE"]}
352   - result["DEVICE"] = new_config
353   -
354   - # device has capabilities
355   - if "CAPABILITIES" in device:
356   - capabilities = []
357   - # if the device is associated to a generic device we need to associate his capabilities with the generic capabilities and overwrite them
358   - if generic_device_config != None:
359   - # we're making a copy of generic device config so we can remove items during loop
360   - copy_generic_device_config = generic_device_config.copy()
361   - # We have to extend capabilities of generic device configuration
362   - for capability in current_config["DEVICE"]["CAPABILITIES"]:
363   - is_capability_in_generic_config = False
364   - current_config_capability = capability["CAPABILITY"]
365   - current_config_component = current_config_capability["component"]
366   - # find if this component was defined in generic_device_config
367   - for index,generic_config_capability in enumerate(generic_device_config["DEVICE"]["CAPABILITIES"]):
368   - # if the current capability is the capability of the component we're looking for
369   - if current_config_component == generic_config_capability["component"]:
370   - is_capability_in_generic_config = True
371   - # we're merging their attributes
372   - new_attributes = generic_config_capability["attributes"].copy()
373   - attributes = {}
374   - current_config_attributes = current_config_capability["attributes"]
375   - generic_config_attributes = generic_config_capability["attributes"]
376   - for attribute in current_config_attributes:
377   - attribute = attribute["attribute"]
378   - attributes[attribute.pop("key")] = attribute
379   - # for each attributes of device component attributes
380   - for attribute_name in attributes.keys():
381   - # merge attributes of general component with specified component in device config file
382   - new_attributes[attribute_name] = {**generic_config_attributes[attribute_name],**attributes[attribute_name]}
383   - if "is_enum" in generic_config_attributes[attribute_name].keys():
384   - # make an intersection of both list of values
385   - new_attributes[attribute_name]["value"] = list(set(attributes[attribute_name]["value"]) & set(generic_config_attributes[attribute_name]["value"]))
386   - if len(new_attributes[attribute_name]["value"]) == 0:
387   - print(f"Value of device '{config_file_name}' for attribute '{attribute_name}' isn't one of the values of generic configuration for this device (generic value(s): {generic_config_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
388   - exit(1)
389   - # removing this capability from generic device configuration
390   - generic_device_config["DEVICE"]["CAPABILITIES"].pop(index)
391   - capabilities.append({"component": current_config_component,"attributes":new_attributes})
392   - break
393   - if is_capability_in_generic_config == False:
394   - current_config_capability = self.read_capability_of_device(current_config_capability)
395   - # the component defined in the current_config isn't defined in generic config (should not happen but we'll deal with that case anyway) : we're simply adding this capability
396   - capabilities.append(current_config_capability)
397   - # looping through generic device config's capabilities in order to add them to current device configuration
398   - for generic_config_capability in generic_device_config["DEVICE"]["CAPABILITIES"]:
399   - capabilities.append(generic_config_capability)
400   - else:
401   - # device not associated to a generic device configuration
402   - for capability in device["CAPABILITIES"]:
403   - capability = capability["CAPABILITY"]
404   - capabilities.append(self.read_capability_of_device(capability))
405   - device["CAPABILITIES"] = capabilities
406   - # associate capabilities to final device configuration (stored in result variable)
407   - result["DEVICE"]["CAPABILITIES"] = device["CAPABILITIES"]
408   - if "ATTACHED_DEVICES" in device.keys():
409   - # device has attached devices, we need to read their configuration in order to get their capabilities and add them to the current device
410   - devices_name_and_file = self.get_devices_names_and_file()
411   - active_devices = self.get_active_devices()
412   - for attached_device in device["ATTACHED_DEVICES"]:
413   - is_attached_device_link_to_agent = False
414   - # we're looking for if the attached device is associated to an agent (i.e. the device is considered as 'active'). However an attached_device shoudn't be active
415   - for active_device in active_devices:
416   - if devices_name_and_file[active_device] == attached_device["file"]:
417   - # the attached device is an active device (so it's linked to an agent)
418   - is_attached_device_link_to_agent = True
419   - break
420   - if self.CONFIG_PATH+attached_device["file"] != config_file_name and not is_attached_device_link_to_agent:
421   - # if the attached device isn't the device itself and not active
422   -
423   - # get configuration of attached device
424   - config_of_attached_device = self.read_device_config_file(self.CONFIG_PATH+attached_device["file"])
425   - capabilities_of_attached_device = None
426   - if "CAPABILITIES" in config_of_attached_device["DEVICE"].keys():
427   - capabilities_of_attached_device = config_of_attached_device["DEVICE"]["CAPABILITIES"]
428   - if capabilities_of_attached_device != None:
429   - # get name of device corresponding to the config file name
430   - parent_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == config_file_name[len(self.CONFIG_PATH):]]
431   - attached_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == attached_device["file"]]
432   - if len(parent_device_name) > 0 :
433   - parent_device_name = parent_device_name[0]
434   - else:
435   - print(f"Attached device filename '{config_file_name[len(self.CONFIG_PATH):]}' is not listed in observatory devices files names")
436   - print("The devices names and files are: ")
437   - for device_name,file_name in devices_name_and_file.items():
438   - print(f"device name: '{device_name}', device filename: '{file_name}'")
439   - exit(1)
440   - if len(attached_device_name) > 0 :
441   - attached_device_name = attached_device_name[0]
442   - else:
443   -
444   - print(f"Attached device filename '{attached_device['file']}' is not listed in observatory devices files names")
445   - print("The devices names and files are: ")
446   - for device_name,file_name in devices_name_and_file.items():
447   - print(f"device name: '{device_name}', device filename: '{file_name}'")
448   - exit(1)
449   - # associate attached device to his 'parent' device (parent device is the currently read device)
450   - self.devices_links[attached_device_name] = parent_device_name
451   - for capability in capabilities_of_attached_device:
452   - # add capabilities of attached device to current device
453   - result["DEVICE"]["CAPABILITIES"].append(capability)
454   - return result
455   - except yaml.YAMLError as exc:
456   - print(exc)
457   - except Exception as e:
458   - print(e)
459   - exit(1)
460   - #return None
461   -
462   - def __init__(self, observatory_config_file:str, unit_name:str="") -> None:
463   - """
464   - Initiate class with the config file
465   - set content attribute to a dictionary containing all values from the config file
466   -
467   - Args:
468   - config_file_name (str): path to the config file
469   - """
470   - self.load(observatory_config_file)
471   - if unit_name == "":
472   - # By default we will use the first unit
473   - self.unit_name = self.get_units_name()[0]
474   - else:
475   - self.unit_name = unit_name
476   -
477   - def get_obs_name(self) -> str:
478   - """
479   - Return name of the observatory
480   -
481   - Returns:
482   - str: Name of the observatory
483   - """
484   - return self.obs_config["OBSERVATORY"]["name"]
485   -
486   - def get_channels(self, unit_name: str) -> dict:
487   -
488   - """
489   - return dictionary of channels
490   -
491   - Args:
492   - unit_name (str): Name of the unit
493   - Returns:
494   - dict: [description]
495   - """
496   - unit = self.get_unit_by_name(unit_name)
497   - channels = {}
498   -
499   - for channel_id in range(len(unit["TOPOLOGY"]["CHANNELS"])):
500   - channel = unit["TOPOLOGY"]["CHANNELS"][channel_id]["CHANNEL"]
501   - channels[channel["name"]] = channel
502   - return channels
503   -
504   - def get_computers(self) -> dict:
505   - """
506   - return dictionary of computers
507   -
508   - Returns:
509   - dict: [description]
510   - """
511   - if self.computers != None:
512   - return self.computers
513   - else:
514   - computers = {}
515   - for computer_id in range(len(self.obs_config["OBSERVATORY"]["COMPUTERS"])):
516   - computer = self.obs_config["OBSERVATORY"]["COMPUTERS"][computer_id]["COMPUTER"]
517   - if( "file" in computer.keys() ):
518   - computer["computer_config"]= self.read_and_check_config_file(self.CONFIG_PATH+computer["file"])["COMPUTER"]
519   - computers[computer["name"]] = computer
520   - return computers
521   -
522   - def get_devices(self) -> dict:
523   - """
524   - return dictionary of devices
525   -
526   - Returns:
527   - dict: [description]
528   - """
529   - if self.devices != None:
530   - return self.devices
531   -
532   - else:
533   - devices = {}
534   - for device_id in range(len(self.obs_config["OBSERVATORY"]["DEVICES"])):
535   - device = self.obs_config["OBSERVATORY"]["DEVICES"][device_id]["DEVICE"]
536   - if( "file" in device.keys() ):
537   - device["device_config"] = self.read_device_config_file(self.CONFIG_PATH+device["file"])["DEVICE"]
538   - devices[device["name"]] = device
539   - return devices
540   -
541   - def get_devices_names(self)->list:
542   - """Return the list of devices names of an observatory
543   -
544   - Returns:
545   - list: list of names of devices
546   - """
547   - devices_names = []
548   - for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
549   - devices_names.append(device["DEVICE"]["name"])
550   - return devices_names
551   -
552   - def get_agents(self,unit_name)->dict:
553   - """
554   - return dictionary of agents
555   -
556   - Args:
557   - unit_name (str): name of the unit
558   -
559   - Returns:
560   - dict: dictionary of agents. For each agents tell the name, computer, device, protocole, etc...
561   - """
562   - unit = self.get_unit_by_name(unit_name)
563   - if self.agents != None:
564   - return self.agents
565   - else:
566   - agents = {}
567   -
568   -
569   - for agent_id in range(len(unit["AGENTS"])):
570   - # Agents is a list containing dictionary that have only one key
571   - key = list(unit["AGENTS"][agent_id].keys())[0]
572   - agent = unit["AGENTS"][agent_id][key]
573   - agents[agent["name"]] = agent
574   - return agents
575   -
576   - def get_layouts(self, unit_name: str) -> dict:
577   - """
578   - Return dictionary of layouts
579   -
580   - Args:
581   - unit_name (str): name of the unit
582   - Returns:
583   - dict: dictionary of layouts
584   - """
585   - unit = self.get_unit_by_name(unit_name)
586   - info = {}
587   - info["layouts"] = {}
588   - for layout_id in range(len(unit["TOPOLOGY"]["LAYOUTS"])):
589   - layout = unit["TOPOLOGY"]["LAYOUTS"][layout_id]["LAYOUT"]
590   - info["layouts"][layout["name"]] = layout
591   - return info
592   -
593   - def get_albums(self,unit_name : str) -> dict:
594   - """
595   - Return dictionary of layouts
596   -
597   - Args:
598   - unit_name (str): name of the unit
599   - Returns:
600   - dict: dictionary of layouts
601   - """
602   - unit = self.get_unit_by_name(unit_name)
603   - info = {}
604   - info["albums"] = {}
605   - for album_id in range(len(unit["TOPOLOGY"]["ALBUMS"])):
606   - album = unit["TOPOLOGY"]["ALBUMS"][album_id]["ALBUM"]
607   - info["albums"][album["name"]] = album
608   - return info
609   -
610   - def get_channel_information(self, unit_name: str, channel_name: str) -> dict:
611   - """
612   - Return information of the given channel name of a unit
613   -
614   - Args:
615   - unit_name (str): Name of the unit
616   - channel_name (str): name of the channel
617   -
618   - Returns:
619   - dict: dictionary containing all values that define this channel
620   - """
621   - channels = self.get_channels(unit_name)
622   - return channels[channel_name]
623   -
624   - def get_topology(self, unit_name:str)->dict:
625   - """
626   - Return dictionary of the topology of the observatory
627   -
628   - Args:
629   - unit_name (str): Name of the unit
630   - Returns:
631   - dict: dictionary representing the topology of an unit (security, mount, channels, layouts, albums)
632   - """
633   - unit = self.get_unit_by_name(unit_name)
634   - topology = {}
635   - for key in unit["TOPOLOGY"].keys():
636   - branch = unit["TOPOLOGY"][key]
637   - if key == "CHANNELS":
638   - topology[key] = self.get_channels(unit_name)
639   - elif key == "LAYOUTS":
640   - topology[key] = self.get_layouts(unit_name)
641   - elif key == "ALBUMS":
642   - topology[key] = self.get_albums(unit_name)
643   - else:
644   - topology[key] = branch
645   - return topology
646   -
647   - def get_active_agents(self, unit_name: str) -> list:
648   - """
649   - Return the list of active agents (i.e. agents that have an association with a device)
650   -
651   - Args:
652   - unit_name (str): Name of the unit
653   -
654   - Returns:
655   - list: kist of the name of active agents
656   - """
657   - return list(self.get_agents(unit_name).keys())
658   -
659   - def get_units(self)->dict:
660   - """
661   - Return all units sort by name defined in the config file
662   -
663   - Returns:
664   - dict: dictionary giving for a unit_name, his content (name,database,topology,agents,...)
665   - """
666   - result = {}
667   - units = self.obs_config["OBSERVATORY"]["UNITS"]
668   - for unit in units:
669   - unit = unit["UNIT"]
670   - result[unit["name"]] = unit
671   - return result
672   -
673   - def get_components_agents(self, unit_name: str) -> dict:
674   - """
675   - Return dictionary of component_agents of the given unit
676   -
677   - Args:
678   - unit_name (str): Name of the unit
679   -
680   - Returns:
681   - dict: dictionary sort by component name giving the associated agent (agent name)
682   - """
683   - components_agents = {}
684   - topology = self.get_topology(unit_name)
685   - for element in topology:
686   - if element in ("SECURITY","MOUNT","CHANNELS"):
687   - if(element != "CHANNELS"):
688   - for component_agent in topology[element]["COMPONENT_AGENTS"]:
689   - component_name = list(component_agent.keys())[0]
690   - components_agents[component_name] = component_agent[component_name]
691   - else:
692   - for channel in topology[element]:
693   - for component_agent in topology[element][channel]["COMPONENT_AGENTS"]:
694   - component_name = list(component_agent.keys())[0]
695   - components_agents[component_name] = component_agent[component_name]
696   - return components_agents
697   -
698   - def get_units_name(self)->list:
699   - """
700   - Return list of units names
701   -
702   - Returns:
703   - [list]: names of units
704   - """
705   - return list(self.get_units().keys())
706   -
707   - def get_unit_by_name(self, name: str) -> dict:
708   - """
709   - Return dictionary containing definition of the unit that matches the given name
710   -
711   - Args:
712   - name (str): name of the unit
713   -
714   - Returns:
715   - dict: dictonary representing the unit
716   - """
717   - return self.get_units()[name]
718   -
719   - def get_agents_per_computer(self, unit_name: str) -> dict:
720   - """
721   - Return dictionary that give for each computer, what are the associated agents to it as a list
722   -
723   - Args:
724   - unit_name (str): Name of the unit
725   -
726   - Returns:
727   - dict: dictionary that give for each computer, what are the associated agents to it as a list
728   -
729   - """
730   - agents_per_computer = {}
731   - agents = self.get_agents(unit_name)
732   - for agent in agents:
733   - computer_name = agents[agent]["computer"]
734   - if(agents[agent]["computer"] not in agents_per_computer.keys()):
735   - agents_per_computer[computer_name] = [agent]
736   - else:
737   - agents_per_computer[computer_name].append(agent)
738   - return agents_per_computer
739   -
740   -
741   - def get_agents_per_device(self,unit_name:str)->dict:
742   - """
743   - Return dictionary that give for each device, what are the associated agents to it as a list
744   -
745   - Args:
746   - unit_name (str): Name of the unit
747   -
748   - Returns:
749   - dict: dictionary that give for each device, what are the associated agents to it as a list
750   -
751   - """
752   - agents_per_device = {}
753   - agents = self.get_agents(unit_name)
754   - for agent in agents:
755   - if("device" in agents[agent].keys()):
756   - device_name = agents[agent]["device"]
757   - if device_name in self.get_devices_names():
758   - if(agents[agent]["device"] not in agents_per_device.keys()):
759   - agents_per_device[device_name] = [agent]
760   - else:
761   - agents_per_device[device_name].append(agent)
762   - else:
763   - print(f"Error: device name '{device_name}' for agent '{agent}' is not known in the configuration file. The device name must match one of the names defined in the DEVICES section")
764   - exit(1)
765   - return agents_per_device
766   -
767   - def get_active_devices(self)->list:
768   - """
769   - Return a list of active device names
770   -
771   - Returns:
772   - list: list of active device names
773   - """
774   - active_devices = []
775   - for unit_name in self.get_units():
776   - for device in self.get_agents_per_device(unit_name):
777   - active_devices.append(device)
778   - return active_devices
779   -
780   - def get_active_computers(self)->list:
781   - """
782   - Return a list of active computer names
783   -
784   - Returns:
785   - list: list of active computer names
786   - """
787   - active_computers = []
788   - for unit_name in self.get_units():
789   - unit = self.get_unit_by_name(unit_name)
790   - for computer in self.get_agents_per_computer(unit_name):
791   - active_computers.append(computer)
792   - return active_computers
793   -
794   - def get_agent_information(self,unit_name:str,agent_name:str)->dict:
795   - """
796   - Give the dictionary of attributes of the agent for an unit.
797   -
798   - Args:
799   - unit (dict): dictonary representing the unit
800   - agent_name (str): agent name
801   -
802   - Returns:
803   - dict: dictionary containing attributes of the agent
804   - """
805   - return self.get_agents(unit_name)[agent_name]
806   -
807   - def get_device_information(self,device_name:str)->dict:
808   - """
809   - Give the dictionary of the attributes of the device
810   -
811   - Args:
812   - device_name (str): device name
813   -
814   - Returns:
815   - dict: dictionary containing attributes of the device
816   - """
817   - return self.get_devices()[device_name]
818   -
819   - def get_database_for_unit(self, unit_name: str) -> dict:
820   - """
821   - Return dictionary of attributes of the database for an unit
822   -
823   - Args:
824   - unit_name (str): unit name
825   -
826   - Returns:
827   - dict: dictionary of attributes of the database for an unit
828   - """
829   - return self.get_unit_by_name(unit_name)["DATABASE"]
830   -
831   - def get_device_for_agent(self, unit_name: str, agent_name: str) -> str:
832   - """
833   - Return device name associated to the agent
834   -
835   - Args:
836   - unit (dict): dictonary representing the unit
837   - agent_name (str): agent name
838   -
839   - Returns:
840   - str: device name associated to this agent
841   - """
842   - agents_per_device = self.get_agents_per_device(unit_name)
843   - for device in agents_per_device:
844   - if agent_name in agents_per_device[device]:
845   - return self.get_device_information(device)
846   -
847   - def get_unit_of_computer(self, computer_name: str) -> str:
848   - """
849   - Return the name of the unit where the computer is used
850   -
851   - Args:
852   - computer_name (str): computer name
853   -
854   - Returns:
855   - str: unit name
856   - """
857   - for unit_name in self.get_units():
858   - if(computer_name in self.get_agents_per_computer(unit_name)):
859   - return unit_name
860   -
861   - def get_unit_of_device(self, device_name:str)->str:
862   - """
863   - Return the name of the unit where the device is used
864   -
865   - Args:
866   - device_name (str): device name
867   -
868   - Returns:
869   - str: unit name
870   - """
871   - for unit_name in self.get_units():
872   - if(device_name in self.get_agents_per_device(unit_name)):
873   - return unit_name
874   -
875   - def get_device_power(self,device_name:str)->dict:
876   - """
877   - Return dictionary that contains informations about power if this information is present in the device config file
878   -
879   - Return None if this information isn't stored in device's config file
880   - Args:
881   - device_name (str): name of the device
882   -
883   - Returns:
884   - dict: informations about power of device
885   - """
886   - return self.get_devices()[device_name]["device_config"].get("power")
887   -
888   -
889   - def get_device_capabilities(self, device_name:str)->list:
890   - """
891   - Return dictionary that contains informations about capabilities if this information is present in the device config file
892   -
893   - Return empty list if this information isn't stored in device's config file
894   - Args:
895   - device_name (str): name of the device
896   -
897   - Returns:
898   - list: list of capabilities of device
899   - """
900   - list_of_capabilities = []
901   - capabilities = self.get_devices()[device_name]["device_config"].get("CAPABILITIES")
902   - return capabilities
903   -
904   - def get_device_connector(self,device_name:str)->dict:
905   - """
906   - Return dictionary that contains informations about connector if this information is present in the device config file
907   -
908   - Return None if this information isn't stored in device's config file
909   - Args:
910   - device_name (str): name of the device
911   -
912   - Returns:
913   - dict: informations about connector of device
914   - """
915   - return self.get_devices()[device_name]["device_config"].get("connector")
916   -
917   - def get_computer_power(self, computer_name: str) -> dict:
918   - """
919   - Return dictionary that contains informations about power if this information is present in the device config file
920   -
921   - Return None if this information isn't stored in device's config file
922   - Args:
923   - device_name (str): name of the device
924   -
925   - Returns:
926   - dict: informations about connector of device
927   - """
928   - return self.get_computers()[computer_name]["computer_config"].get("power")
929   -
930   - def getDeviceControllerNameForAgent(self,unit_name:str,agent_name:str)->tuple:
931   - agent = self.get_agent_information(unit_name,agent_name)
932   - return (agent["device"],"DeviceController"+agent["device"])
933   -
934   - def getDeviceConfigForDeviceController(self,device_name:str)->dict:
935   - return self.get_devices()[device_name]["device_config"]
936   -
937   - def getCommParamsForAgentDevice(self,unit_name:str,agent_name:str)->tuple:
938   - agent = self.get_agent_information(unit_name,agent_name)
939   - device_config = self.getDeviceConfigForDeviceController(agent["device"])
940   - comm_access = agent.get("comm_access",None)
941   - comm = device_config["comm"]
942   - return (comm_access,comm)
943   -
944   - def getChannelCapabilities(self,unit_name:str,channel_name:str)->list:
945   - channel = self.get_channel_information(unit_name,channel_name)
946   - result = []
947   - for component_agent in channel["COMPONENT_AGENTS"]:
948   - component = list(component_agent.keys())[0]
949   - agent = component_agent[component]
950   - device = self.getDeviceControllerNameForAgent(unit_name,agent)[0]
951   - device_capabilities = self.get_device_capabilities(device)
952   - for capability in device_capabilities:
953   - if capability["component"] == component:
954   - result.append(capability)
955   - return result
956   -
957   - def getEditableAttributesOfCapability(self,capability:dict)->dict:
958   - editable_fields = {}
959   - attributes = capability.get("attributes")
960   - for attribute in attributes:
961   - if attributes[attribute]["is_editable"]:
962   - editable_fields[attribute] = attributes[attribute]
963   - return editable_fields
964   -
965   - def getUneditableAttributesOfCapability(self,capability:dict)->dict:
966   - uneditable_fields = {}
967   - attributes = capability.get("attributes")
968   - for attribute in attributes:
969   - if attributes[attribute]["is_editable"] == False:
970   - uneditable_fields[attribute] = attributes[attribute]
971   - return uneditable_fields
972   -
973   - def getEditableAttributesOfChannel(self,unit_name:str,channel_name:str)->list:
974   - capabilities = self.getChannelCapabilities(unit_name,channel_name)
975   - # merged_result = {}
976   - # for capability in capabilities:
977   - # merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
978   - # return merged_result
979   - merged_result = []
980   - for capability in capabilities:
981   - attributes = self.getEditableAttributesOfCapability(capability)
982   - if len(attributes.keys()) > 0:
983   - merged_result.append(attributes)
984   - return merged_result
985   -
986   - def getUneditableAttributesOfChannel(self,unit_name:str,channel_name:str)->list:
987   - capabilities = self.getChannelCapabilities(unit_name,channel_name)
988   - # merged_result = {}
989   - # for capability in capabilities:
990   - # merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
991   - # return merged_result
992   - merged_result = []
993   - for capability in capabilities:
994   - attributes = self.getUneditableAttributesOfCapability(capability)
995   - if len(attributes.keys()) > 0:
996   - merged_result.append(attributes)
997   - return merged_result
998   -
999   -
1000   - # def getLogicOfChannelGroups(self,unit_name):
1001   - # return self.get_layouts(unit_name)["global_group_logic"]
1002   -
1003   -
1004   - def getLayoutByName(self, unit_name:str, name_of_layout):
1005   - return self.get_layouts(unit_name)["layouts"][name_of_layout]
1006   -
1007   - def getAlbumByName(self,unit_name : str, name_of_album):
1008   - return self.get_albums(unit_name)["albums"][name_of_album]
1009   -
1010   - def getEditableAttributesOfMount(self,unit_name):
1011   - capabilities = self.get_device_capabilities(self.get_device_for_agent(unit_name,"mount")["name"])
1012   - merged_result = []
1013   - for capability in capabilities:
1014   - attributes = self.getEditableAttributesOfCapability(capability)
1015   - if len(attributes.keys()) > 0:
1016   - merged_result.append(attributes)
1017   - return merged_result
1018   -
1019   -
1020   - def getHorizonLine(self, unit_name):
1021   - horizon = self.get_unit_by_name(unit_name).get("horizon")
1022   - return horizon.get("line")
1023   -
1024   -def main():
1025   - # config = ConfigPyros("../../../../privatedev/config/guitalens/observatory_guitalens.yml")
1026   - # unit_name = config.get_units_name()[0]
1027   - # dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
1028   - #print(config.getDeviceConfigForDeviceController(dc))
1029   - #print(config.getCommParamsForAgentDevice(unit_name,"mount"))
1030   - # print(config.getChannelCapabilities(unit_name,"OpticalChannel_up"))
1031   - # print(config.get_channel_groups(unit_name))
1032   - # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_up")[0]))
1033   - # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_up"))
1034   - config = ConfigPyros("../../../../privatedev/config/tnc/observatory_tnc.yml")
1035   - unit_name = config.get_units_name()[0]
1036   - #dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
1037   - #print(config.getDeviceConfigForDeviceController(dc))
1038   - #print(config.getCommParamsForAgentDevice(unit_name,"mount"))
1039   - # print(config.getChannelCapabilities(unit_name,"OpticalChannel_down2"))
1040   - # print(config.get_channel_groups(unit_name))
1041   - # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_down2")[0]))
1042   - # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_down2"))
1043   - print(config.getEditableAttributesOfMount(unit_name))
1044   - #print(config.get_devices()["FLI-Kepler4040"]["device_config"])
1045   - #print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"][1]["attributes"]["manufacturer"])
1046   - #print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"])
1047   - #print(config.get_devices()["AstroMecCA-TM350"]["device_config"]["CAPABILITIES"])
1048   -if __name__ == "__main__":
1049   -
  1 +#!/usr/bin/env python3
  2 +import pykwalify.core
  3 +import sys
  4 +import yaml, logging, os, pickle, time
  5 +from datetime import datetime
  6 +from pykwalify.errors import PyKwalifyException,SchemaError
  7 +from pathlib import Path
  8 +
  9 +class OBSConfig:
  10 + # (AKo) : Config file path is checked on the settings file, if the file isn't valid (i.e not found) the error will be launched by the settings file when starting the application
  11 + devices_links = {}
  12 + current_file = None
  13 + #COMPONENT_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/components/")
  14 + #GENERIC_DEVICES_PATH = os.path.join(os.environ["DJANGO_PATH"],"../../../config/devices/")
  15 + pickle_file = "obsconfig.p"
  16 + obs_config = None
  17 + devices = None
  18 + computers = None
  19 + agents = None
  20 + obs_config_file_content = None
  21 + #obs_config_path = os.environ.get("PATH_TO_OBSCONF_FOLDER",os.path.join(os.environ["DJANGO_PATH"],"../../../privatedev/config/default/"))
  22 + errors = None
  23 +
  24 + def verify_if_pickle_needs_to_be_updated(self, observatory_config_file)->bool:
  25 + """
  26 +
  27 + Args:
  28 + observatory_config_file ([type]): [description]
  29 +
  30 + Returns:
  31 + bool: [description]
  32 + """
  33 + self.CONFIG_PATH = os.path.dirname(observatory_config_file)+"/"
  34 + self.obs_config_path = self.CONFIG_PATH
  35 + #self.CONFIG_PATH = self.obs_config_path
  36 + if os.path.isfile(self.CONFIG_PATH+self.pickle_file) == False:
  37 + return True
  38 + else:
  39 + pickle_file_mtime = os.path.getmtime(self.CONFIG_PATH+self.pickle_file)
  40 + obs_config_mtime = os.path.getmtime(observatory_config_file)
  41 +
  42 + obs_config = self.read_and_check_config_file(observatory_config_file)
  43 + if obs_config_mtime > pickle_file_mtime:
  44 + # create obs file (yaml) from pickle["obsconfig"] with date of pickle within history folder-> nom ficher + année + mois + jour + datetime (avec secondes) -> YYYY/MM/DD H:m:s
  45 + pickle_datetime = datetime.utcfromtimestamp(pickle_file_mtime).strftime("%Y%m%d_%H%M%S")
  46 + # Create history folder if doesn't exist
  47 + Path(self.obs_config_path+"/history/").mkdir(parents=True, exist_ok=True)
  48 + file_name = f"{self.obs_config_path}/history/observatory_{pickle_datetime}.yml"
  49 + config_file = open(observatory_config_file,"r")
  50 +
  51 + with open(file_name, 'w') as f:
  52 + f.write(config_file.read())
  53 + return True
  54 + if obs_config == None:
  55 + print(f"Error when trying to read config file (path of config file : {observatory_config_file}")
  56 + return -1
  57 + self.obs_config = obs_config
  58 + # check last date of modification for devices files
  59 + for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
  60 + device_file = self.CONFIG_PATH+device["DEVICE"]["file"]
  61 + device_file_mtime = os.path.getmtime(device_file)
  62 + if device_file_mtime > pickle_file_mtime:
  63 + return True
  64 +
  65 + for computer in self.obs_config["OBSERVATORY"]["COMPUTERS"]:
  66 + computer_file = self.CONFIG_PATH+computer["COMPUTER"]["file"]
  67 + computer_file_mtime = os.path.getmtime(computer_file)
  68 + if computer_file_mtime > pickle_file_mtime:
  69 + return True
  70 + return False
  71 +
  72 + def load(self, observatory_config_file):
  73 + pickle_needs_to_be_updated = self.verify_if_pickle_needs_to_be_updated(observatory_config_file)
  74 + # check if we already read and load devices configuration and if pickle needs to be updated
  75 + if pickle_needs_to_be_updated == False and self.devices != None:
  76 + return None
  77 + else:
  78 + if os.path.isfile(self.CONFIG_PATH+self.pickle_file) and pickle_needs_to_be_updated == False:
  79 + print("Reading pickle file")
  80 + try:
  81 + can_pickle_file_be_read = False
  82 + while can_pickle_file_be_read != True:
  83 + if os.access(self.CONFIG_PATH+self.pickle_file, os.R_OK):
  84 + pickle_dict = pickle.load(open(self.CONFIG_PATH+self.pickle_file,"rb"))
  85 + can_pickle_file_be_read = True
  86 + else:
  87 + time.sleep(0.5)
  88 + except IOError:
  89 + print("Error when reading the pickle file")
  90 + try:
  91 + self.obs_config = pickle_dict["obs_config"]
  92 + self.computers = pickle_dict["computers"]
  93 + self.devices = pickle_dict["devices"]
  94 + self.devices_links = pickle_dict["devices_links"]
  95 + self.obs_config_file_content = pickle_dict["obs_config_file_content"]
  96 + self.raw_config = pickle_dict["raw_config"]
  97 + except:
  98 + # we rewrite the pickle file, the content will be the same otherwise we would be in the else case
  99 + print("Rewritting the pickle file (an error occured while reading it, the content will be the same as it was")
  100 + pickle_dict = {}
  101 +
  102 + self.obs_config = self.read_and_check_config_file(observatory_config_file)
  103 + obs_file = open(observatory_config_file,"r")
  104 + pickle_dict["raw_config"] = obs_file.read()
  105 + obs_file.close()
  106 + self.raw_config = pickle_dict["raw_config"]
  107 + pickle_dict["obs_config"] = self.obs_config
  108 + pickle_dict["devices"] = self.get_devices()
  109 + pickle_dict["computers"] = self.get_computers()
  110 + pickle_dict["devices_links"] = self.devices_links
  111 + pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(observatory_config_file)
  112 + print("Writing pickle file")
  113 + pickle.dump(pickle_dict,open(self.CONFIG_PATH+self.pickle_file,"wb"))
  114 + else:
  115 + print("Pickle file needs to be created or updated")
  116 + pickle_dict = {}
  117 +
  118 + self.obs_config = self.read_and_check_config_file(observatory_config_file)
  119 + pickle_dict["obs_config"] = self.obs_config
  120 + pickle_dict["devices"] = self.get_devices()
  121 + pickle_dict["computers"] = self.get_computers()
  122 + pickle_dict["devices_links"] = self.devices_links
  123 + pickle_dict["obs_config_file_content"] = self.read_and_check_config_file(observatory_config_file)
  124 + print("Writing pickle file")
  125 + pickle.dump(pickle_dict,open(self.CONFIG_PATH+self.pickle_file,"wb"))
  126 +
  127 + def check_and_return_config(self,yaml_file:str,schema_file:str)->dict:
  128 + """
  129 + Check if yaml_file is valid for the schema_file and return an dictionary of the config file
  130 +
  131 + Args:
  132 + yaml_file (str): Path to the config_file to be validated
  133 + schema_file (str): Path to the schema file
  134 +
  135 + Returns:
  136 + dict: dictionary of the config file (with values)
  137 + """
  138 + # disable pykwalify error to clean the output
  139 + #####logging.disable(logging.ERROR)
  140 + try:
  141 + can_yaml_file_be_read = False
  142 + while can_yaml_file_be_read != True:
  143 + if os.access(yaml_file, os.R_OK):
  144 + can_yaml_file_be_read = True
  145 + else:
  146 + print(f"{yaml_file} can't be accessed, waiting for availability")
  147 + time.sleep(0.5)
  148 +
  149 + c = pykwalify.core.Core(source_file=yaml_file, schema_files=[self.SCHEMA_PATH+schema_file])
  150 + return c.validate(raise_exception=True)
  151 + except SchemaError:
  152 + for error in c.errors:
  153 + print("Error :",str(error).split(". Path")[0])
  154 + print("Path to error :",error.path)
  155 + self.errors = c.errors
  156 + return None
  157 + except IOError:
  158 + print("Error when reading the observatory config file")
  159 +
  160 + @staticmethod
  161 + def check_config(yaml_file:str,schema_file:str)->any:
  162 + """
  163 + Check if yaml_file is valid for the schema_file and return a boolean or list of errors according the schema
  164 +
  165 + Args:
  166 + yaml_file (str): Path to the config_file to be validated
  167 + schema_file (str): Path to the schema file
  168 +
  169 + Returns:
  170 + any: boolean (True) if the configuration is valid according the schema or a list of error otherwise
  171 + """
  172 + # disable pykwalify error to clean the output
  173 + ####logging.disable(logging.ERROR)
  174 + try:
  175 + can_yaml_file_be_read = False
  176 + while can_yaml_file_be_read != True:
  177 + if os.access(yaml_file, os.R_OK):
  178 + can_yaml_file_be_read = True
  179 + else:
  180 + print(f"{yaml_file} can't be accessed, waiting for availability")
  181 + time.sleep(0.5)
  182 +
  183 + c = pykwalify.core.Core(source_file=yaml_file, schema_files=[schema_file])
  184 + c.validate(raise_exception=True)
  185 + return True
  186 + except SchemaError:
  187 + for error in c.errors:
  188 + print("Error :",str(error).split(". Path")[0])
  189 + print("Path to error :",error.path)
  190 +
  191 + return c.errors
  192 + except IOError:
  193 + print("Error when reading the observatory config file")
  194 +
  195 + def read_and_check_config_file(self,yaml_file:str)->dict:
  196 + """
  197 + Read the schema key of the config file to retrieve schema name and proceed to the checking of that config file
  198 + Call check_and_return_config function and print its return.
  199 +
  200 + Args:
  201 + yaml_file (str): path to the config file
  202 + Returns:
  203 + dict: Dictionary of the config file (with values)
  204 + """
  205 + self.current_file = yaml_file
  206 + try:
  207 + can_config_file_be_read = False
  208 + while can_config_file_be_read != True:
  209 +
  210 + if os.access(yaml_file, os.R_OK):
  211 + can_config_file_be_read = True
  212 + else:
  213 + print(f"{yaml_file} can't be accessed, waiting for availability")
  214 + time.sleep(0.5)
  215 + with open(yaml_file, 'r') as stream:
  216 + print(f"Reading {yaml_file}")
  217 + config_file = yaml.safe_load(stream)
  218 +
  219 + self.DJANGO_PATH = os.environ.get("DJANGO_PATH",os.path.abspath(os.path.dirname(yaml_file)))
  220 + self.SCHEMA_PATH = os.path.join(self.DJANGO_PATH,"../../../config/schemas/")
  221 + self.CONFIG_PATH = self.obs_config_path
  222 + self.COMPONENT_PATH = os.path.join(self.DJANGO_PATH,"../../../config/components/")
  223 + self.GENERIC_DEVICES_PATH = os.path.join(self.DJANGO_PATH,"../../../config/devices/")
  224 + result = self.check_and_return_config(yaml_file,config_file["schema"])
  225 + if result == None:
  226 + print("Error when reading and validating config file, please check the errors right above")
  227 + exit(1)
  228 + return result
  229 +
  230 + except yaml.YAMLError as exc:
  231 + print(exc)
  232 + except Exception as e:
  233 + print(e)
  234 + return None
  235 +
  236 + def read_generic_component_and_return_attributes(self,component_name:str)->dict:
  237 + file_path = self.COMPONENT_PATH + component_name + ".yml"
  238 + try:
  239 + with open(file_path, 'r') as stream:
  240 + config_file = yaml.safe_load(stream)
  241 +
  242 + attributes = {}
  243 + for attribute in config_file:
  244 +
  245 + attribute = attribute["attribute"]
  246 + attributes[attribute.pop("key")] = attribute
  247 + return attributes
  248 + except yaml.YAMLError as exc:
  249 + print(exc)
  250 + except Exception as e:
  251 + print(e)
  252 + return None
  253 +
  254 + def read_capability_of_device(self,capability:dict)->dict:
  255 + """
  256 + Read capability of device and inherit attributes from generic component then overwrite attributes defined in device config
  257 +
  258 + Args:
  259 + capability (dict): dictionary containing a capabilitiy (keys : component and attributes)
  260 +
  261 + Returns:
  262 + dict: dictionary of capability inherited by generic component and overwritten his attributes by current attributes of capability
  263 + """
  264 +
  265 + component_attributes = self.read_generic_component_and_return_attributes(capability["component"])
  266 +
  267 + attributes = {}
  268 + # get all attributes of device's capability
  269 + for attribute in capability["attributes"]:
  270 + attribute = attribute["attribute"]
  271 +
  272 + attributes[attribute.pop("key")] = attribute
  273 +
  274 + # for each attributes of generic component attributes
  275 + for attribute_name in attributes.keys():
  276 + # merge attributes of general component with specified component in device config file
  277 + new_attributes = {**component_attributes[attribute_name],**attributes[attribute_name]}
  278 + if "is_enum" in component_attributes[attribute_name].keys():
  279 + # make an intersection of both list of values
  280 + new_attributes["value"] = list(set(attributes[attribute_name]["value"]) & set(component_attributes[attribute_name]["value"]))
  281 + if len(new_attributes["value"]) == 0:
  282 + print(f"Value of lastly read device's attribute '{attribute_name}' isn't one of the values of component configuration for this device (component configuration value(s): {component_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
  283 + exit(1)
  284 + component_attributes[attribute_name] = new_attributes
  285 +
  286 + # return inherited and overwritten attributes of capability
  287 + capability["attributes"] = component_attributes
  288 + return capability
  289 +
  290 + def get_devices_names_and_file(self) -> dict:
  291 + """
  292 + Return a dictionary giving the device file name by the device name
  293 + Returns:
  294 + dict: key is device name, value is file name
  295 + """
  296 + devices_names_and_files = {}
  297 + for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
  298 + device = device["DEVICE"]
  299 +
  300 + devices_names_and_files[device["name"]] = device["file"]
  301 + return devices_names_and_files
  302 +
  303 + def read_device_config_file(self,config_file_name:str,is_generic=False)->dict:
  304 + """
  305 + Read the device config file, inherit generic device config if "generic" key is present in "DEVICE".
  306 + Associate capabilities of attached_devices if this device has attached_devices.
  307 + Inherit capabilities from generic component and overwritte attributes defined in the device config
  308 + Args:
  309 + config_file_name (str): file name to be read
  310 + is_generic (bool, optional): tells if we're reading a generic configuration (is_generic =True) or not (is_generic = False). Defaults to False.
  311 +
  312 + Returns:
  313 + dict: formatted device configuration (attributes, capabilities...)
  314 + """
  315 + self.current_file = config_file_name
  316 + devices_name_and_file = self.get_devices_names_and_file()
  317 + if not is_generic:
  318 + # check if device config file is listed in observatory configuration
  319 + current_device_name = [device for device,file_name in devices_name_and_file.items() if file_name in config_file_name[len(self.CONFIG_PATH):] ]
  320 + if len(current_device_name) <= 0:
  321 + print(f"Current file '{config_file_name[len(self.CONFIG_PATH):]}' isn't listed in observatory configuration")
  322 + print("The devices names and files are: ")
  323 + for device_name,file_name in devices_name_and_file.items():
  324 + print(f"device name: '{device_name}', device filename: '{file_name}'")
  325 + exit(1)
  326 + print(f"Reading {config_file_name}")
  327 + try:
  328 + with open(config_file_name, 'r') as stream:
  329 + config_file = yaml.safe_load(stream)
  330 + # if we're reading a generic device configuration, the path to get the schema is different than usual
  331 + self.SCHEMA_PATH =os.path.join(self.DJANGO_PATH,"../../../config/schemas/")
  332 +
  333 + # read and verify that the device configuration match the schema
  334 + result = self.check_and_return_config(config_file_name,config_file["schema"])
  335 + # if the configuration didn't match the schema or had an error when reading the file
  336 + if result == None:
  337 + print("Error when reading and validating config file, please check the errors right above")
  338 + exit(1)
  339 + else:
  340 + # the configuration is valid
  341 + # storing DEVICE key in device (DEVICE can contains : attributes of device, capabilities, attached devices)
  342 + device = result["DEVICE"]
  343 + generic_device_config = None
  344 + # if the device is associated to an generic configuration, we'll read that generic configuration to inherit his attributes
  345 + if "generic" in device:
  346 + # storing the whole current config
  347 + current_config = result
  348 + # read and get the generic device config
  349 + generic_device_config = self.read_device_config_file(self.GENERIC_DEVICES_PATH+device["generic"],is_generic=True)
  350 + # merge whole device config but we need to merge capabilities differently after
  351 + new_config = {**generic_device_config["DEVICE"],**current_config["DEVICE"]}
  352 + result["DEVICE"] = new_config
  353 +
  354 + # device has capabilities
  355 + if "CAPABILITIES" in device:
  356 + capabilities = []
  357 + # if the device is associated to a generic device we need to associate his capabilities with the generic capabilities and overwrite them
  358 + if generic_device_config != None:
  359 + # we're making a copy of generic device config so we can remove items during loop
  360 + copy_generic_device_config = generic_device_config.copy()
  361 + # We have to extend capabilities of generic device configuration
  362 + for capability in current_config["DEVICE"]["CAPABILITIES"]:
  363 + is_capability_in_generic_config = False
  364 + current_config_capability = capability["CAPABILITY"]
  365 + current_config_component = current_config_capability["component"]
  366 + # find if this component was defined in generic_device_config
  367 + for index,generic_config_capability in enumerate(generic_device_config["DEVICE"]["CAPABILITIES"]):
  368 + # if the current capability is the capability of the component we're looking for
  369 + if current_config_component == generic_config_capability["component"]:
  370 + is_capability_in_generic_config = True
  371 + # we're merging their attributes
  372 + new_attributes = generic_config_capability["attributes"].copy()
  373 + attributes = {}
  374 + current_config_attributes = current_config_capability["attributes"]
  375 + generic_config_attributes = generic_config_capability["attributes"]
  376 + for attribute in current_config_attributes:
  377 + attribute = attribute["attribute"]
  378 + attributes[attribute.pop("key")] = attribute
  379 + # for each attributes of device component attributes
  380 + for attribute_name in attributes.keys():
  381 + # merge attributes of general component with specified component in device config file
  382 + new_attributes[attribute_name] = {**generic_config_attributes[attribute_name],**attributes[attribute_name]}
  383 + if "is_enum" in generic_config_attributes[attribute_name].keys():
  384 + # make an intersection of both list of values
  385 + new_attributes[attribute_name]["value"] = list(set(attributes[attribute_name]["value"]) & set(generic_config_attributes[attribute_name]["value"]))
  386 + if len(new_attributes[attribute_name]["value"]) == 0:
  387 + print(f"Value of device '{config_file_name}' for attribute '{attribute_name}' isn't one of the values of generic configuration for this device (generic value(s): {generic_config_attributes[attribute_name]['value']}) (actual value : {attributes[attribute_name]['value']})")
  388 + exit(1)
  389 + # removing this capability from generic device configuration
  390 + generic_device_config["DEVICE"]["CAPABILITIES"].pop(index)
  391 + capabilities.append({"component": current_config_component,"attributes":new_attributes})
  392 + break
  393 + if is_capability_in_generic_config == False:
  394 + current_config_capability = self.read_capability_of_device(current_config_capability)
  395 + # the component defined in the current_config isn't defined in generic config (should not happen but we'll deal with that case anyway) : we're simply adding this capability
  396 + capabilities.append(current_config_capability)
  397 + # looping through generic device config's capabilities in order to add them to current device configuration
  398 + for generic_config_capability in generic_device_config["DEVICE"]["CAPABILITIES"]:
  399 + capabilities.append(generic_config_capability)
  400 + else:
  401 + # device not associated to a generic device configuration
  402 + for capability in device["CAPABILITIES"]:
  403 + capability = capability["CAPABILITY"]
  404 + capabilities.append(self.read_capability_of_device(capability))
  405 + device["CAPABILITIES"] = capabilities
  406 + # associate capabilities to final device configuration (stored in result variable)
  407 + result["DEVICE"]["CAPABILITIES"] = device["CAPABILITIES"]
  408 + if "ATTACHED_DEVICES" in device.keys():
  409 + # device has attached devices, we need to read their configuration in order to get their capabilities and add them to the current device
  410 + devices_name_and_file = self.get_devices_names_and_file()
  411 + active_devices = self.get_active_devices()
  412 + for attached_device in device["ATTACHED_DEVICES"]:
  413 + is_attached_device_link_to_agent = False
  414 + # we're looking for if the attached device is associated to an agent (i.e. the device is considered as 'active'). However an attached_device shoudn't be active
  415 + for active_device in active_devices:
  416 + if devices_name_and_file[active_device] == attached_device["file"]:
  417 + # the attached device is an active device (so it's linked to an agent)
  418 + is_attached_device_link_to_agent = True
  419 + break
  420 + if self.CONFIG_PATH+attached_device["file"] != config_file_name and not is_attached_device_link_to_agent:
  421 + # if the attached device isn't the device itself and not active
  422 +
  423 + # get configuration of attached device
  424 + config_of_attached_device = self.read_device_config_file(self.CONFIG_PATH+attached_device["file"])
  425 + capabilities_of_attached_device = None
  426 + if "CAPABILITIES" in config_of_attached_device["DEVICE"].keys():
  427 + capabilities_of_attached_device = config_of_attached_device["DEVICE"]["CAPABILITIES"]
  428 + if capabilities_of_attached_device != None:
  429 + # get name of device corresponding to the config file name
  430 + parent_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == config_file_name[len(self.CONFIG_PATH):]]
  431 + attached_device_name = [device for device,file_name in devices_name_and_file.items() if file_name == attached_device["file"]]
  432 + if len(parent_device_name) > 0 :
  433 + parent_device_name = parent_device_name[0]
  434 + else:
  435 + print(f"Attached device filename '{config_file_name[len(self.CONFIG_PATH):]}' is not listed in observatory devices files names")
  436 + print("The devices names and files are: ")
  437 + for device_name,file_name in devices_name_and_file.items():
  438 + print(f"device name: '{device_name}', device filename: '{file_name}'")
  439 + exit(1)
  440 + if len(attached_device_name) > 0 :
  441 + attached_device_name = attached_device_name[0]
  442 + else:
  443 +
  444 + print(f"Attached device filename '{attached_device['file']}' is not listed in observatory devices files names")
  445 + print("The devices names and files are: ")
  446 + for device_name,file_name in devices_name_and_file.items():
  447 + print(f"device name: '{device_name}', device filename: '{file_name}'")
  448 + exit(1)
  449 + # associate attached device to his 'parent' device (parent device is the currently read device)
  450 + self.devices_links[attached_device_name] = parent_device_name
  451 + for capability in capabilities_of_attached_device:
  452 + # add capabilities of attached device to current device
  453 + result["DEVICE"]["CAPABILITIES"].append(capability)
  454 + return result
  455 + except yaml.YAMLError as exc:
  456 + print(exc)
  457 + except Exception as e:
  458 + print(e)
  459 + exit(1)
  460 + #return None
  461 +
  462 + def __init__(self, observatory_config_file:str, unit_name:str="") -> None:
  463 + """
  464 + Initiate class with the config file
  465 + set content attribute to a dictionary containing all values from the config file
  466 +
  467 + Args:
  468 + config_file_name (str): path to the config file
  469 + """
  470 + self.load(observatory_config_file)
  471 + if unit_name == "":
  472 + # By default we will use the first unit
  473 + self.unit_name = self.get_units_name()[0]
  474 + else:
  475 + self.unit_name = unit_name
  476 +
  477 + def get_obs_name(self) -> str:
  478 + """
  479 + Return name of the observatory
  480 +
  481 + Returns:
  482 + str: Name of the observatory
  483 + """
  484 + return self.obs_config["OBSERVATORY"]["name"]
  485 +
  486 + def get_channels(self, unit_name: str) -> dict:
  487 +
  488 + """
  489 + return dictionary of channels
  490 +
  491 + Args:
  492 + unit_name (str): Name of the unit
  493 + Returns:
  494 + dict: [description]
  495 + """
  496 + unit = self.get_unit_by_name(unit_name)
  497 + channels = {}
  498 +
  499 + for channel_id in range(len(unit["TOPOLOGY"]["CHANNELS"])):
  500 + channel = unit["TOPOLOGY"]["CHANNELS"][channel_id]["CHANNEL"]
  501 + channels[channel["name"]] = channel
  502 + return channels
  503 +
  504 + def get_computers(self) -> dict:
  505 + """
  506 + return dictionary of computers
  507 +
  508 + Returns:
  509 + dict: [description]
  510 + """
  511 + if self.computers != None:
  512 + return self.computers
  513 + else:
  514 + computers = {}
  515 + for computer_id in range(len(self.obs_config["OBSERVATORY"]["COMPUTERS"])):
  516 + computer = self.obs_config["OBSERVATORY"]["COMPUTERS"][computer_id]["COMPUTER"]
  517 + if( "file" in computer.keys() ):
  518 + computer["computer_config"]= self.read_and_check_config_file(self.CONFIG_PATH+computer["file"])["COMPUTER"]
  519 + computers[computer["name"]] = computer
  520 + return computers
  521 +
  522 + def get_devices(self) -> dict:
  523 + """
  524 + return dictionary of devices
  525 +
  526 + Returns:
  527 + dict: [description]
  528 + """
  529 + if self.devices != None:
  530 + return self.devices
  531 +
  532 + else:
  533 + devices = {}
  534 + for device_id in range(len(self.obs_config["OBSERVATORY"]["DEVICES"])):
  535 + device = self.obs_config["OBSERVATORY"]["DEVICES"][device_id]["DEVICE"]
  536 + if( "file" in device.keys() ):
  537 + device["device_config"] = self.read_device_config_file(self.CONFIG_PATH+device["file"])["DEVICE"]
  538 + devices[device["name"]] = device
  539 + return devices
  540 +
  541 + def get_devices_names(self)->list:
  542 + """Return the list of devices names of an observatory
  543 +
  544 + Returns:
  545 + list: list of names of devices
  546 + """
  547 + devices_names = []
  548 + for device in self.obs_config["OBSERVATORY"]["DEVICES"]:
  549 + devices_names.append(device["DEVICE"]["name"])
  550 + return devices_names
  551 +
  552 + def get_agents(self,unit_name)->dict:
  553 + """
  554 + return dictionary of agents
  555 +
  556 + Args:
  557 + unit_name (str): name of the unit
  558 +
  559 + Returns:
  560 + dict: dictionary of agents. For each agents tell the name, computer, device, protocole, etc...
  561 + """
  562 + unit = self.get_unit_by_name(unit_name)
  563 + if self.agents != None:
  564 + return self.agents
  565 + else:
  566 + agents = {}
  567 +
  568 +
  569 + for agent_id in range(len(unit["AGENTS"])):
  570 + # Agents is a list containing dictionary that have only one key
  571 + key = list(unit["AGENTS"][agent_id].keys())[0]
  572 + agent = unit["AGENTS"][agent_id][key]
  573 + agents[agent["name"]] = agent
  574 + return agents
  575 +
  576 + def get_layouts(self, unit_name: str) -> dict:
  577 + """
  578 + Return dictionary of layouts
  579 +
  580 + Args:
  581 + unit_name (str): name of the unit
  582 + Returns:
  583 + dict: dictionary of layouts
  584 + """
  585 + unit = self.get_unit_by_name(unit_name)
  586 + info = {}
  587 + info["layouts"] = {}
  588 + for layout_id in range(len(unit["TOPOLOGY"]["LAYOUTS"])):
  589 + layout = unit["TOPOLOGY"]["LAYOUTS"][layout_id]["LAYOUT"]
  590 + info["layouts"][layout["name"]] = layout
  591 + return info
  592 +
  593 + def get_albums(self,unit_name : str) -> dict:
  594 + """
  595 + Return dictionary of layouts
  596 +
  597 + Args:
  598 + unit_name (str): name of the unit
  599 + Returns:
  600 + dict: dictionary of layouts
  601 + """
  602 + unit = self.get_unit_by_name(unit_name)
  603 + info = {}
  604 + info["albums"] = {}
  605 + for album_id in range(len(unit["TOPOLOGY"]["ALBUMS"])):
  606 + album = unit["TOPOLOGY"]["ALBUMS"][album_id]["ALBUM"]
  607 + info["albums"][album["name"]] = album
  608 + return info
  609 +
  610 + def get_channel_information(self, unit_name: str, channel_name: str) -> dict:
  611 + """
  612 + Return information of the given channel name of a unit
  613 +
  614 + Args:
  615 + unit_name (str): Name of the unit
  616 + channel_name (str): name of the channel
  617 +
  618 + Returns:
  619 + dict: dictionary containing all values that define this channel
  620 + """
  621 + channels = self.get_channels(unit_name)
  622 + return channels[channel_name]
  623 +
  624 + def get_topology(self, unit_name:str)->dict:
  625 + """
  626 + Return dictionary of the topology of the observatory
  627 +
  628 + Args:
  629 + unit_name (str): Name of the unit
  630 + Returns:
  631 + dict: dictionary representing the topology of an unit (security, mount, channels, layouts, albums)
  632 + """
  633 + unit = self.get_unit_by_name(unit_name)
  634 + topology = {}
  635 + for key in unit["TOPOLOGY"].keys():
  636 + branch = unit["TOPOLOGY"][key]
  637 + if key == "CHANNELS":
  638 + topology[key] = self.get_channels(unit_name)
  639 + elif key == "LAYOUTS":
  640 + topology[key] = self.get_layouts(unit_name)
  641 + elif key == "ALBUMS":
  642 + topology[key] = self.get_albums(unit_name)
  643 + else:
  644 + topology[key] = branch
  645 + return topology
  646 +
  647 + def get_active_agents(self, unit_name: str) -> list:
  648 + """
  649 + Return the list of active agents (i.e. agents that have an association with a device)
  650 +
  651 + Args:
  652 + unit_name (str): Name of the unit
  653 +
  654 + Returns:
  655 + list: kist of the name of active agents
  656 + """
  657 + return list(self.get_agents(unit_name).keys())
  658 +
  659 + def get_units(self)->dict:
  660 + """
  661 + Return all units sort by name defined in the config file
  662 +
  663 + Returns:
  664 + dict: dictionary giving for a unit_name, his content (name,database,topology,agents,...)
  665 + """
  666 + result = {}
  667 + units = self.obs_config["OBSERVATORY"]["UNITS"]
  668 + for unit in units:
  669 + unit = unit["UNIT"]
  670 + result[unit["name"]] = unit
  671 + return result
  672 +
  673 + def get_components_agents(self, unit_name: str) -> dict:
  674 + """
  675 + Return dictionary of component_agents of the given unit
  676 +
  677 + Args:
  678 + unit_name (str): Name of the unit
  679 +
  680 + Returns:
  681 + dict: dictionary sort by component name giving the associated agent (agent name)
  682 + """
  683 + components_agents = {}
  684 + topology = self.get_topology(unit_name)
  685 + for element in topology:
  686 + if element in ("SECURITY","MOUNT","CHANNELS"):
  687 + if(element != "CHANNELS"):
  688 + for component_agent in topology[element]["COMPONENT_AGENTS"]:
  689 + component_name = list(component_agent.keys())[0]
  690 + components_agents[component_name] = component_agent[component_name]
  691 + else:
  692 + for channel in topology[element]:
  693 + for component_agent in topology[element][channel]["COMPONENT_AGENTS"]:
  694 + component_name = list(component_agent.keys())[0]
  695 + components_agents[component_name] = component_agent[component_name]
  696 + return components_agents
  697 +
  698 + def get_units_name(self)->list:
  699 + """
  700 + Return list of units names
  701 +
  702 + Returns:
  703 + [list]: names of units
  704 + """
  705 + return list(self.get_units().keys())
  706 +
  707 + def get_unit_by_name(self, name: str) -> dict:
  708 + """
  709 + Return dictionary containing definition of the unit that matches the given name
  710 +
  711 + Args:
  712 + name (str): name of the unit
  713 +
  714 + Returns:
  715 + dict: dictonary representing the unit
  716 + """
  717 + return self.get_units()[name]
  718 +
  719 + def get_agents_per_computer(self, unit_name: str) -> dict:
  720 + """
  721 + Return dictionary that give for each computer, what are the associated agents to it as a list
  722 +
  723 + Args:
  724 + unit_name (str): Name of the unit
  725 +
  726 + Returns:
  727 + dict: dictionary that give for each computer, what are the associated agents to it as a list
  728 +
  729 + """
  730 + agents_per_computer = {}
  731 + agents = self.get_agents(unit_name)
  732 + for agent in agents:
  733 + computer_name = agents[agent]["computer"]
  734 + if(agents[agent]["computer"] not in agents_per_computer.keys()):
  735 + agents_per_computer[computer_name] = [agent]
  736 + else:
  737 + agents_per_computer[computer_name].append(agent)
  738 + return agents_per_computer
  739 +
  740 +
  741 + def get_agents_per_device(self,unit_name:str)->dict:
  742 + """
  743 + Return dictionary that give for each device, what are the associated agents to it as a list
  744 +
  745 + Args:
  746 + unit_name (str): Name of the unit
  747 +
  748 + Returns:
  749 + dict: dictionary that give for each device, what are the associated agents to it as a list
  750 +
  751 + """
  752 + agents_per_device = {}
  753 + agents = self.get_agents(unit_name)
  754 + for agent in agents:
  755 + if("device" in agents[agent].keys()):
  756 + device_name = agents[agent]["device"]
  757 + if device_name in self.get_devices_names():
  758 + if(agents[agent]["device"] not in agents_per_device.keys()):
  759 + agents_per_device[device_name] = [agent]
  760 + else:
  761 + agents_per_device[device_name].append(agent)
  762 + else:
  763 + print(f"Error: device name '{device_name}' for agent '{agent}' is not known in the configuration file. The device name must match one of the names defined in the DEVICES section")
  764 + exit(1)
  765 + return agents_per_device
  766 +
  767 + def get_active_devices(self)->list:
  768 + """
  769 + Return a list of active device names
  770 +
  771 + Returns:
  772 + list: list of active device names
  773 + """
  774 + active_devices = []
  775 + for unit_name in self.get_units():
  776 + for device in self.get_agents_per_device(unit_name):
  777 + active_devices.append(device)
  778 + return active_devices
  779 +
  780 + def get_active_computers(self)->list:
  781 + """
  782 + Return a list of active computer names
  783 +
  784 + Returns:
  785 + list: list of active computer names
  786 + """
  787 + active_computers = []
  788 + for unit_name in self.get_units():
  789 + unit = self.get_unit_by_name(unit_name)
  790 + for computer in self.get_agents_per_computer(unit_name):
  791 + active_computers.append(computer)
  792 + return active_computers
  793 +
  794 + def get_agent_information(self,unit_name:str,agent_name:str)->dict:
  795 + """
  796 + Give the dictionary of attributes of the agent for an unit.
  797 +
  798 + Args:
  799 + unit (dict): dictonary representing the unit
  800 + agent_name (str): agent name
  801 +
  802 + Returns:
  803 + dict: dictionary containing attributes of the agent
  804 + """
  805 + return self.get_agents(unit_name)[agent_name]
  806 +
  807 + def get_device_information(self,device_name:str)->dict:
  808 + """
  809 + Give the dictionary of the attributes of the device
  810 +
  811 + Args:
  812 + device_name (str): device name
  813 +
  814 + Returns:
  815 + dict: dictionary containing attributes of the device
  816 + """
  817 + return self.get_devices()[device_name]
  818 +
  819 + def get_database_for_unit(self, unit_name: str) -> dict:
  820 + """
  821 + Return dictionary of attributes of the database for an unit
  822 +
  823 + Args:
  824 + unit_name (str): unit name
  825 +
  826 + Returns:
  827 + dict: dictionary of attributes of the database for an unit
  828 + """
  829 + return self.get_unit_by_name(unit_name)["DATABASE"]
  830 +
  831 + def get_device_for_agent(self, unit_name: str, agent_name: str) -> str:
  832 + """
  833 + Return device name associated to the agent
  834 +
  835 + Args:
  836 + unit (dict): dictonary representing the unit
  837 + agent_name (str): agent name
  838 +
  839 + Returns:
  840 + str: device name associated to this agent
  841 + """
  842 + agents_per_device = self.get_agents_per_device(unit_name)
  843 + for device in agents_per_device:
  844 + if agent_name in agents_per_device[device]:
  845 + return self.get_device_information(device)
  846 +
  847 + def get_unit_of_computer(self, computer_name: str) -> str:
  848 + """
  849 + Return the name of the unit where the computer is used
  850 +
  851 + Args:
  852 + computer_name (str): computer name
  853 +
  854 + Returns:
  855 + str: unit name
  856 + """
  857 + for unit_name in self.get_units():
  858 + if(computer_name in self.get_agents_per_computer(unit_name)):
  859 + return unit_name
  860 +
  861 + def get_unit_of_device(self, device_name:str)->str:
  862 + """
  863 + Return the name of the unit where the device is used
  864 +
  865 + Args:
  866 + device_name (str): device name
  867 +
  868 + Returns:
  869 + str: unit name
  870 + """
  871 + for unit_name in self.get_units():
  872 + if(device_name in self.get_agents_per_device(unit_name)):
  873 + return unit_name
  874 +
  875 + def get_device_power(self,device_name:str)->dict:
  876 + """
  877 + Return dictionary that contains informations about power if this information is present in the device config file
  878 +
  879 + Return None if this information isn't stored in device's config file
  880 + Args:
  881 + device_name (str): name of the device
  882 +
  883 + Returns:
  884 + dict: informations about power of device
  885 + """
  886 + return self.get_devices()[device_name]["device_config"].get("power")
  887 +
  888 +
  889 + def get_device_capabilities(self, device_name:str)->list:
  890 + """
  891 + Return dictionary that contains informations about capabilities if this information is present in the device config file
  892 +
  893 + Return empty list if this information isn't stored in device's config file
  894 + Args:
  895 + device_name (str): name of the device
  896 +
  897 + Returns:
  898 + list: list of capabilities of device
  899 + """
  900 + list_of_capabilities = []
  901 + capabilities = self.get_devices()[device_name]["device_config"].get("CAPABILITIES")
  902 + return capabilities
  903 +
  904 + def get_device_connector(self,device_name:str)->dict:
  905 + """
  906 + Return dictionary that contains informations about connector if this information is present in the device config file
  907 +
  908 + Return None if this information isn't stored in device's config file
  909 + Args:
  910 + device_name (str): name of the device
  911 +
  912 + Returns:
  913 + dict: informations about connector of device
  914 + """
  915 + return self.get_devices()[device_name]["device_config"].get("connector")
  916 +
  917 + def get_computer_power(self, computer_name: str) -> dict:
  918 + """
  919 + Return dictionary that contains informations about power if this information is present in the device config file
  920 +
  921 + Return None if this information isn't stored in device's config file
  922 + Args:
  923 + device_name (str): name of the device
  924 +
  925 + Returns:
  926 + dict: informations about connector of device
  927 + """
  928 + return self.get_computers()[computer_name]["computer_config"].get("power")
  929 +
  930 + def getDeviceControllerNameForAgent(self,unit_name:str,agent_name:str)->tuple:
  931 + agent = self.get_agent_information(unit_name,agent_name)
  932 + return (agent["device"],"DeviceController"+agent["device"])
  933 +
  934 + def getDeviceConfigForDeviceController(self,device_name:str)->dict:
  935 + return self.get_devices()[device_name]["device_config"]
  936 +
  937 + def getCommParamsForAgentDevice(self,unit_name:str,agent_name:str)->tuple:
  938 + agent = self.get_agent_information(unit_name,agent_name)
  939 + device_config = self.getDeviceConfigForDeviceController(agent["device"])
  940 + comm_access = agent.get("comm_access",None)
  941 + comm = device_config["comm"]
  942 + return (comm_access,comm)
  943 +
  944 + def getChannelCapabilities(self,unit_name:str,channel_name:str)->list:
  945 + channel = self.get_channel_information(unit_name,channel_name)
  946 + result = []
  947 + for component_agent in channel["COMPONENT_AGENTS"]:
  948 + component = list(component_agent.keys())[0]
  949 + agent = component_agent[component]
  950 + device = self.getDeviceControllerNameForAgent(unit_name,agent)[0]
  951 + device_capabilities = self.get_device_capabilities(device)
  952 + for capability in device_capabilities:
  953 + if capability["component"] == component:
  954 + result.append(capability)
  955 + return result
  956 +
  957 + def getEditableAttributesOfCapability(self,capability:dict)->dict:
  958 + editable_fields = {}
  959 + attributes = capability.get("attributes")
  960 + for attribute in attributes:
  961 + if attributes[attribute]["is_editable"]:
  962 + editable_fields[attribute] = attributes[attribute]
  963 + return editable_fields
  964 +
  965 + def getUneditableAttributesOfCapability(self,capability:dict)->dict:
  966 + uneditable_fields = {}
  967 + attributes = capability.get("attributes")
  968 + for attribute in attributes:
  969 + if attributes[attribute]["is_editable"] == False:
  970 + uneditable_fields[attribute] = attributes[attribute]
  971 + return uneditable_fields
  972 +
  973 + def getEditableAttributesOfChannel(self,unit_name:str,channel_name:str)->list:
  974 + capabilities = self.getChannelCapabilities(unit_name,channel_name)
  975 + # merged_result = {}
  976 + # for capability in capabilities:
  977 + # merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
  978 + # return merged_result
  979 + merged_result = []
  980 + for capability in capabilities:
  981 + attributes = self.getEditableAttributesOfCapability(capability)
  982 + if len(attributes.keys()) > 0:
  983 + merged_result.append(attributes)
  984 + return merged_result
  985 +
  986 + def getUneditableAttributesOfChannel(self,unit_name:str,channel_name:str)->list:
  987 + capabilities = self.getChannelCapabilities(unit_name,channel_name)
  988 + # merged_result = {}
  989 + # for capability in capabilities:
  990 + # merged_result[capability["component"]] = self.getEditableAttributesOfCapability(capability)
  991 + # return merged_result
  992 + merged_result = []
  993 + for capability in capabilities:
  994 + attributes = self.getUneditableAttributesOfCapability(capability)
  995 + if len(attributes.keys()) > 0:
  996 + merged_result.append(attributes)
  997 + return merged_result
  998 +
  999 +
  1000 + # def getLogicOfChannelGroups(self,unit_name):
  1001 + # return self.get_layouts(unit_name)["global_group_logic"]
  1002 +
  1003 +
  1004 + def getLayoutByName(self, unit_name:str, name_of_layout):
  1005 + return self.get_layouts(unit_name)["layouts"][name_of_layout]
  1006 +
  1007 + def getAlbumByName(self,unit_name : str, name_of_album):
  1008 + return self.get_albums(unit_name)["albums"][name_of_album]
  1009 +
  1010 + def getEditableAttributesOfMount(self,unit_name):
  1011 + capabilities = self.get_device_capabilities(self.get_device_for_agent(unit_name,"mount")["name"])
  1012 + merged_result = []
  1013 + for capability in capabilities:
  1014 + attributes = self.getEditableAttributesOfCapability(capability)
  1015 + if len(attributes.keys()) > 0:
  1016 + merged_result.append(attributes)
  1017 + return merged_result
  1018 +
  1019 +
  1020 + def getHorizonLine(self, unit_name):
  1021 + horizon = self.get_unit_by_name(unit_name).get("horizon")
  1022 + return horizon.get("line")
  1023 +
  1024 +def main():
  1025 + # config = ConfigPyros("../../../../privatedev/config/guitalens/observatory_guitalens.yml")
  1026 + # unit_name = config.get_units_name()[0]
  1027 + # dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
  1028 + #print(config.getDeviceConfigForDeviceController(dc))
  1029 + #print(config.getCommParamsForAgentDevice(unit_name,"mount"))
  1030 + # print(config.getChannelCapabilities(unit_name,"OpticalChannel_up"))
  1031 + # print(config.get_channel_groups(unit_name))
  1032 + # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_up")[0]))
  1033 + # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_up"))
  1034 + config = OBSConfig("../../../../privatedev/config/tnc/observatory_tnc.yml")
  1035 + unit_name = config.get_units_name()[0]
  1036 + #dc = config.getDeviceControllerNameForAgent(unit_name,"mount")[0]
  1037 + #print(config.getDeviceConfigForDeviceController(dc))
  1038 + #print(config.getCommParamsForAgentDevice(unit_name,"mount"))
  1039 + # print(config.getChannelCapabilities(unit_name,"OpticalChannel_down2"))
  1040 + # print(config.get_channel_groups(unit_name))
  1041 + # print(config.getEditableAttributesOfCapability(config.getChannelCapabilities(unit_name,"OpticalChannel_down2")[0]))
  1042 + # print(config.getEditableAttributesOfChannel(unit_name,"OpticalChannel_down2"))
  1043 + print(config.getEditableAttributesOfMount(unit_name))
  1044 + #print(config.get_devices()["FLI-Kepler4040"]["device_config"])
  1045 + #print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"][1]["attributes"]["manufacturer"])
  1046 + #print(config.get_devices()["FLI-Kepler4040"]["device_config"]["CAPABILITIES"])
  1047 + #print(config.get_devices()["AstroMecCA-TM350"]["device_config"]["CAPABILITIES"])
  1048 +if __name__ == "__main__":
  1049 +
1050 1050 main()
1051 1051 \ No newline at end of file
... ...
src/core/pyros_django/obsconfig/templates/obsconfig/obs_astronomer_config.html
... ... @@ -49,7 +49,53 @@
49 49 </div>
50 50 {% endfor %}
51 51 </div>
52   -
  52 + {% elif category == "ALBUMS" %}
  53 + <li class="collapsible collapsible_category"> ALBUMS </li>
  54 +
  55 + <div class="content">
  56 + {% for album in category_content.values %}
  57 + <h2 class="collapsible collapsible_name">Name : {{ album|get_item:"name" }}</h2>
  58 + <div class="content">
  59 + <table class="table table-bordered table-hover table-striped">
  60 + <thead>
  61 + <tr>
  62 + <th> Channels </th>
  63 + </tr>
  64 + </thead>
  65 + <tbody>
  66 + {% for channel in album|get_item:"CHANNELS" %}
  67 +
  68 + <tr>
  69 + <td> {{ channel }} </td>
  70 + </tr>
  71 + {% endfor %}
  72 + </table>
  73 + </div>
  74 + {% endfor %}
  75 + </div>
  76 + {% elif category == "LAYOUTS" %}
  77 + <li class="collapsible collapsible_category"> LAYOUTS </li>
  78 + <div class="content">
  79 + {% for layout in category_content.values %}
  80 + <h2 class="collapsible collapsible_name">Name : {{ layout|get_item:"name" }}</h2>
  81 + <div class="content">
  82 + <table class="table table-bordered table-hover table-striped">
  83 + <thead>
  84 + <tr>
  85 + <th> Albums </th>
  86 + </tr>
  87 + </thead>
  88 + <tbody>
  89 + {% for album in layout|get_item:"ALBUMS" %}
  90 +
  91 + <tr>
  92 + <td> {{ album }} </td>
  93 + </tr>
  94 + {% endfor %}
  95 + </table>
  96 + </div>
  97 + {% endfor %}
  98 + </div>
53 99 {% else %}
54 100  
55 101 <li class="collapsible collapsible_category">{{ category }} </li>
... ... @@ -87,7 +133,9 @@
87 133 </div>
88 134 {% endfor %}
89 135 </ul>
90   -</div>
  136 +
  137 + {{ albums }}
  138 +
91 139 <script>
92 140 var coll = document.getElementsByClassName("collapsible");
93 141 var i;
... ...
src/core/pyros_django/obsconfig/tests.py
1 1 from django.test import TestCase
2 2 from common.models import PyrosUser
3   -from .configpyros import ConfigPyros
  3 +from .obsconfig_class import OBSConfig
4 4 from django.urls import reverse
5 5 import unittest,os
6 6 class ObservatoryConfigurationTests(TestCase):
... ... @@ -11,13 +11,13 @@ class ObservatoryConfigurationTests(TestCase):
11 11 os.remove("obsconfig/fixtures/obsconfig.p")
12 12  
13 13 u1 = PyrosUser.objects.get(username="haribo")
14   - config_ko = ConfigPyros("obsconfig/fixtures/observatory_configuration_ko.yml")
  14 + config_ko = OBSConfig("obsconfig/fixtures/observatory_configuration_ko.yml")
15 15 if os.path.exists("obsconfig/fixtures/obsconfig.p"):
16 16 os.remove("obsconfig/fixtures/obsconfig.p")
17 17  
18 18 def test_OCF_read_config_get_content(self):
19 19 os.environ["PATH_TO_OBSCONF_FILE"] = "obsconfig/fixtures/observatory_configuration_ok_simple.yml"
20   - config = ConfigPyros("obsconfig/fixtures/observatory_configuration_ok_simple.yml")
  20 + config = OBSConfig("obsconfig/fixtures/observatory_configuration_ok_simple.yml")
21 21 # get_devices, and get_computers are already tested in load
22 22 self.assertEqual(config.get_obs_name(),"observatory_test")
23 23 self.assertEqual(len(config.get_units()),1)
... ... @@ -35,7 +35,7 @@ class ObservatoryConfigurationTests(TestCase):
35 35  
36 36 def test_OCF_view_config_simple(self):
37 37 os.environ["PATH_TO_OBSCONF_FILE"] = "obsconfig/fixtures/observatory_configuration_ok_simple.yml"
38   - config = ConfigPyros("obsconfig/fixtures/observatory_configuration_ok_simple.yml")
  38 + config = OBSConfig("obsconfig/fixtures/observatory_configuration_ok_simple.yml")
39 39 u1 = PyrosUser.objects.get(username="haribo")
40 40 u1.set_password("password123")
41 41 u1.save()
... ... @@ -53,7 +53,7 @@ class ObservatoryConfigurationTests(TestCase):
53 53  
54 54 def test_OCF_view_config_complex(self):
55 55 os.environ["PATH_TO_OBSCONF_FILE"] = "obsconfig/fixtures/observatory_configuration_ok_complex.yml"
56   - config = ConfigPyros("obsconfig/fixtures/observatory_configuration_ok_complex.yml")
  56 + config = OBSConfig("obsconfig/fixtures/observatory_configuration_ok_complex.yml")
57 57 u1 = PyrosUser.objects.get(username="haribo")
58 58 u1.set_password("password123")
59 59 u1.save()
... ...
src/core/pyros_django/obsconfig/views.py
1 1 from django import conf
2 2 from django.shortcuts import render
3   -from .configpyros import ConfigPyros
  3 +from .obsconfig_class import OBSConfig
4 4 from django.conf import settings
5 5 from src.core.pyros_django.dashboard.decorator import level_required
6 6 from django.contrib.auth.decorators import login_required
... ... @@ -30,7 +30,7 @@ def get_nested_dictionaries_as_list(dic,result=[]):
30 30 @login_required
31 31 @level_required("Admin","Observer","Management","Operator","Unit-PI")
32 32 def obs_global_config(request):
33   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  33 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
34 34 units_names = list(config.get_units().keys())
35 35 pickle_file_mtime = os.path.getmtime(config.CONFIG_PATH+config.pickle_file)
36 36 pickle_datetime = datetime.utcfromtimestamp(pickle_file_mtime).strftime("%Y/%m/%d %H:%M:%S")
... ... @@ -52,7 +52,7 @@ def obs_global_config(request):
52 52 @login_required
53 53 @level_required("Admin","Unit-PI","Operator")
54 54 def obs_hardware_config(request):
55   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  55 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
56 56 devices = config.get_devices()
57 57 active_devices = config.get_active_devices()
58 58 computers = config.get_computers()
... ... @@ -71,7 +71,7 @@ def obs_hardware_config(request):
71 71 @login_required
72 72 @level_required("Admin","Unit-PI","Operator")
73 73 def unit_hardware_configuration(request,unit_name):
74   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  74 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
75 75 devices = config.get_devices()
76 76  
77 77 return render(request, 'obsconfig/unit_hardware_configuration.html', {'config':config})
... ... @@ -79,7 +79,7 @@ def unit_hardware_configuration(request,unit_name):
79 79 @login_required
80 80 @level_required("Admin","Unit-PI","Operator")
81 81 def computer_details(request,computer_name):
82   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  82 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
83 83 computer_detail = yaml.dump(config.get_computers()[computer_name])
84 84 """
85 85 computer_detail = { re.sub("^_","",key):value for key,value in config.get_computers()[computer_name].items() }
... ... @@ -101,7 +101,7 @@ def computer_details(request,computer_name):
101 101 @login_required
102 102 @level_required("Admin","Unit-PI","Unit-board","Operator","Observer")
103 103 def device_details(request,device_name):
104   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  104 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
105 105 # We're removing "_" at the beginning of each key :
106 106 device = config.get_devices()[device_name]
107 107 #device_detail = yaml.dump(device)
... ... @@ -156,16 +156,15 @@ def device_details(request,device_name):
156 156 @login_required
157 157 @level_required("Admin","Observer","Management","Operator","Unit-PI","TAC")
158 158 def obs_astronomer_config(request):
159   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  159 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
160 160 units = config.get_units()
161 161 units_topologies = {}
162 162 # for each unit
163 163 for unit_name in units:
164 164  
165 165 units_topologies[unit_name] = config.get_topology(unit_name)
166   - # removing channel_groups, not useful in this view
167   - units_topologies[unit_name].pop("LAYOUTS")
168   - units_topologies[unit_name].pop("ALBUMS")
  166 + layouts = units_topologies[unit_name].pop("LAYOUTS")["layouts"]
  167 + albums = units_topologies[unit_name].pop("ALBUMS")["albums"]
169 168 # for each category (security, mount, channels)
170 169 for category in units_topologies[unit_name]:
171 170 if category != "CHANNELS":
... ... @@ -187,14 +186,17 @@ def obs_astronomer_config(request):
187 186 device_of_agent = config.get_device_for_agent(unit_name,agent)
188 187 index = units_topologies[unit_name]["CHANNELS"][channel_name]["COMPONENT_AGENTS"].index(component_agent)
189 188 units_topologies[unit_name]["CHANNELS"][channel_name]["COMPONENT_AGENTS"][index][component_name] = device_of_agent
190   -
191   - return render(request,"obsconfig/obs_astronomer_config.html", { "units_topologies" : units_topologies })
  189 + units_topologies[unit_name]["ALBUMS"] = albums
  190 + units_topologies[unit_name]["LAYOUTS"] = layouts
  191 + return render(request,"obsconfig/obs_astronomer_config.html", {
  192 + "units_topologies" : units_topologies,
  193 + })
192 194  
193 195  
194 196 @login_required
195 197 @level_required("Admin")
196 198 def obs_agents_config(request):
197   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  199 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
198 200 units = config.get_units()
199 201 units_topologies = {}
200 202 active_agents_by_unit = {}
... ... @@ -205,8 +207,9 @@ def obs_agents_config(request):
205 207 active_agents_by_unit[unit_name] = config.get_active_agents(unit_name)
206 208 # topology of the current unit
207 209 units_topologies[unit_name] = config.get_topology(unit_name)
208   - # removing channel_groups, not useful in this view
209   - units_topologies[unit_name].pop("CHANNEL_GROUPS")
  210 + # removing albums and layouts, not useful in this view
  211 + units_topologies[unit_name].pop("LAYOUTS")
  212 + units_topologies[unit_name].pop("ALBUMS")
210 213 for category in units_topologies[unit_name]:
211 214 if category != "CHANNELS":
212 215 # Security and Mount are directly a dictionary containing the attributes of those categories
... ... @@ -233,7 +236,7 @@ def obs_agents_config(request):
233 236 @login_required
234 237 @level_required("Admin","Operator","Unit-PI","Unit-board")
235 238 def edit_config(request):
236   - config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
  239 + config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"])
237 240 return render(request,"obsconfig/edit_config.html",{"config_file":config.raw_config})
238 241  
239 242 @login_required
... ... @@ -263,7 +266,7 @@ def verify_config(request):
263 266 response_data["is_valid"] = False
264 267 response_data["message"] = "Missing schema"
265 268 schema_path = os.path.join(os.environ["DJANGO_PATH"],"../../../config/schemas/")
266   - config = ConfigPyros.check_config(temp_config_file.name,schema_path+schema)
  269 + config = OBSConfig.check_config(temp_config_file.name,schema_path+schema)
267 270 if type(config) == bool and config:
268 271 response_data["is_valid"] = True
269 272 else:
... ...
src/core/pyros_django/pyros/settings.py
... ... @@ -44,10 +44,23 @@ MODULES_VERSIONS = {
44 44  
45 45  
46 46  
47   -import os,re,platform, subprocess
  47 +import os,re,platform, subprocess, sys
48 48 from datetime import date, datetime
49 49  
50 50 import django
  51 +# append previous parent folders to import configpyros
  52 +sys.path.append("../../..")
  53 +from config.pyros.config_pyros import configpyros
  54 +
  55 +PYROS_CONFIG_FILE = os.environ.get("pyros_config_file")
  56 +if PYROS_CONFIG_FILE:
  57 + CONFIG_PYROS = configpyros(os.environ["pyros_config_file"]).pyros_config
  58 + NB_ELEMENT_PER_PAGE = CONFIG_PYROS.get("general").get("nb_element_per_page")
  59 +
  60 +def read_version_number_from_file(version_file:str) -> str:
  61 + version = open(version_file,"r").readline()
  62 + return version
  63 +
51 64 # duplicate from the same function in pyros.py ...
52 65 def set_environment_variables_if_not_configured(env_path: str,env_sample_path: str)->None:
53 66 """
... ... @@ -404,6 +417,8 @@ python_version = subprocess.run( &quot;python --version | cut -d &#39; &#39; -f 2 | cut -d &#39;.
404 417 python_version = python_version.stdout
405 418 day = "2022-01-06"
406 419 django_version_major,django_version_minor = django.VERSION[:2][0],django.VERSION[:2][1]
407   -pyros_version = "0.3.3.0"
  420 +pyros_version = read_version_number_from_file("../../../VERSION")
  421 +#TODO: create function to read VERSION file
  422 +
408 423 #pyros_version = "0.2.12.0"
409 424 VERSION_NUMBER = f"{pyros_version}_{django_version_major}.{django_version_minor}_{python_version}_{day}"
... ...