Commit 980acc36efbb94a44ee8576c3101e4f876e3a8a9

Authored by Alain Klotz
2 parents f1d7a80d 627f618c
Exists in dev

Merge branch 'dev' of https://gitlab.irap.omp.eu/epallier/pyros into dev

CHANGELOG
  1 +25-11-2022 (AKo): v0.6.13.1
  2 + - Add debug mode to agentSST
  3 + - UI changes on agent_detail
  4 + - Enable log file of agent with the right name
  5 +
1 6 25-11-2022 (AKo): v0.6.13.0
2 7 - Add is_active field for agents in tnc obsconfig
3 8 - Add new colors in agents_cmds and agent_detail for cmd status
... ...
VERSION
1   -0.6.13.0
2 1 \ No newline at end of file
  2 +0.6.13.1
3 3 \ No newline at end of file
... ...
privatedev/plugin/agent/AgentBasic.py
... ... @@ -394,6 +394,8 @@ class AgentBasic(Agent):
394 394 print(res)
395 395 self.sleep(nbsec)
396 396  
  397 + #raise Exception("exception bidon")
  398 +
397 399 res = f"3 - now sleeping {nbsec} sec"
398 400 self.CC.set_result(res, True)
399 401 #self.CC.get_updated_result()
... ...
src/core/pyros_django/agent/Agent.py
... ... @@ -42,7 +42,7 @@ and execute them on reception at each iteration :
42 42 # For cmd parsing
43 43 from array import array
44 44 from datetime import datetime
45   -from typing import Dict, List, Tuple, Union, Any, Optional, Literal
  45 +from typing import Final, Sequence, Iterable, Mapping, MutableMapping, Dict, List, Tuple, Union, Any, Optional, Literal
46 46 import ast
47 47 from inspect import signature
48 48  
... ... @@ -384,7 +384,9 @@ class Agent:
384 384 #
385 385 #_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, int, Union[str,None], Union[int,None] ] ] = [
386 386 ##_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, int, Optional[str], AgentCmd.CMD_STATUS_CODES ] ] = [
387   - _TEST_COMMANDS_LIST: List[ Tuple[ bool, str, Optional[int], Optional[str], Optional[int]] ] = [
  387 + TestCommand = Tuple[ bool, str, Optional[int], Optional[str], Optional[int]]
  388 + #_TEST_COMMANDS_LIST: List[ Tuple[ bool, str, Optional[int], Optional[str], Optional[int]] ] = [
  389 + _TEST_COMMANDS_LIST: List[ TestCommand ] = [
388 390 # Format : (DO_IT, "self cmd_name cmd_args", validity, "expected_result", expected_status),
389 391  
390 392 #("self do_stop now", 200, '15.5', None),
... ... @@ -602,24 +604,25 @@ class Agent:
602 604 ##def __init__(self, RUN_IN_THREAD=True):
603 605 def __init__(self,simulated_computer=None):
604 606  
605   - # Declaration of Instance attributes, default values
  607 + # Instance attributes declaration (with default values, or None)
  608 + self.__UP_SINCE : Final = datetime.now(tz=timezone.utc)
606 609 #self.UP_SINCE = datetime.utcnow()
607   - self.__UP_SINCE = datetime.now(tz=timezone.utc)
608 610 self.__ROUTINE_ITER_START_IS_RUNNING:bool = False
609 611 self.__ROUTINE_ITER_END_IS_RUNNING:bool = False
610 612 self.__test_cmd_received_num:int = 0 # only for tests
611 613 # Current Command running
612   - self.__CC :Optional[AgentCmd] = None
613   - self.__CC_thread :Union[StoppableThreadEvenWhenSleeping, multiprocessing.Process] = None
  614 + self.__CC :Optional[AgentCmd] #= None
  615 + self.__CC_thread :Union[StoppableThreadEvenWhenSleeping, multiprocessing.Process] #= None
614 616 # Previous Command running
615 617 ##self.__CC_prev :Optional[AgentCmd] = None
616 618 # Current Command exception (if occurs)
617   - self.__CCE :Optional[Exception] = None
  619 + self.__CCE :Optional[Exception] #= None
618 620 self.name = "Generic Agent"
619   - self.__status :str = None
620   - self.__mode :str = None
621   - self.unit = None
622   - self.TEST_COMMANDS = None
  621 + self.__status :str #= None
  622 + self.__mode :str #= None
  623 + self.unit :str #= None
  624 + #self.TEST_COMMANDS :List #= None
  625 + self.TEST_COMMANDS :Iterable[Agent.TestCommand] #= None
623 626 self.__iter_num :int = 0
624 627 #print(AgentSurvey.MODE_CHOICES.IDLE)
625 628 #sys.exit()
... ... @@ -628,23 +631,24 @@ class Agent:
628 631 #self.__mode = self.MODE_ATTENTIVE
629 632 self.set_mode_attentive()
630 633 #self._set_mode(MODES.)
631   -
632   - log.addHandler(handler_filebyagent(logging.INFO, self.__class__.__name__))
633   - #log.addHandler(handler_filebyagent(logging.INFO, self.name))
634   - log.debug("start Agent init")
635 634 obs_config_file_path = os.environ["PATH_TO_OBSCONF_FILE"]
636 635 path_to_obs_config_folder = os.environ["PATH_TO_OBSCONF_FOLDER"]
637 636 unit = os.environ["unit_name"]
638 637 oc = OBSConfig(obs_config_file_path,unit)
639 638 self.set_config(oc, obs_config_file_path, path_to_obs_config_folder, unit)
  639 + agent_name_from_config = self.get_config().get_agent_name_from_config(self.__class__.__name__,simulated_computer)
  640 + if agent_name_from_config:
  641 + self.name = agent_name_from_config
  642 + else:
  643 + self.name = self.__class__.__name__
  644 +
  645 + log.addHandler(handler_filebyagent(logging.INFO, self.name))
  646 + #log.addHandler(handler_filebyagent(logging.INFO, self.name))
  647 + log.debug("start Agent init")
640 648  
641   - self.name = self.__class__.__name__
642 649 # set real unit name (the current unit used)
643 650 if unit == "":
644 651 unit = oc.unit_name
645   - agent_name_from_config = self.get_config().get_agent_name_from_config(self.__class__.__name__,simulated_computer)
646   - if agent_name_from_config:
647   - self.name = agent_name_from_config
648 652 self.unit = unit
649 653 print(f"Agent name : {self.name}")
650 654 print(f"Unit name : {self.unit}")
... ... @@ -3044,7 +3048,7 @@ class Agent:
3044 3048 #cmd_full_name, validity, res_expected, after_status = next(self.TEST_COMMANDS, (None,None,None,None))
3045 3049 DO_IT = False
3046 3050 while not DO_IT:
3047   - cmd_params = next(self.TEST_COMMANDS, (False,None,None,None,None))
  3051 + cmd_params:Agent.TestCommand = next(self.TEST_COMMANDS, (False,None,None,None,None))
3048 3052 #print(cmd_params)
3049 3053 DO_IT, cmd_full_name, validity, expected_res, expected_status = cmd_params
3050 3054 ###DO_IT, cmd_full_name, validity, expected_res, expected_status = next(self.TEST_COMMANDS, (False,None,None,None,None))
... ...
src/core/pyros_django/agent/AgentBasic.py
... ... @@ -394,6 +394,8 @@ class AgentBasic(Agent):
394 394 print(res)
395 395 self.sleep(nbsec)
396 396  
  397 + #raise Exception("exception bidon")
  398 +
397 399 res = f"3 - now sleeping {nbsec} sec"
398 400 self.CC.set_result(res, True)
399 401 #self.CC.get_updated_result()
... ...
src/core/pyros_django/agent/AgentSST.py
... ... @@ -147,6 +147,8 @@ class AgentSST(Agent):
147 147 cmd += test_mode
148 148 if self.simulated_computer:
149 149 cmd += f" --computer {self.simulated_computer}"
  150 + if self.DEBUG_MODE:
  151 + cmd += " -d"
150 152 process = subprocess.Popen(f"{cmd}",shell=True)
151 153 process.poll()
152 154 if agent not in self.subprocess_dict:
... ... @@ -187,6 +189,8 @@ class AgentSST(Agent):
187 189 cmd += test_mode
188 190 if self.simulated_computer:
189 191 cmd += f" --computer {self.simulated_computer}"
  192 + if self.DEBUG_MODE:
  193 + cmd += " -d"
190 194 # process = subprocess.Popen(f"{cmd}", shell=True, stdout=subprocess.DEVNULL,stderr=subprocess.STDOUT)
191 195 process = subprocess.Popen(f"{cmd}", shell=True)
192 196 self.subprocess_dict[agent] = {}
... ... @@ -256,7 +260,6 @@ class AgentSST(Agent):
256 260 def _do_things_before_exit(self,abort_cmd_sender):
257 261 kill_agent_commands = {}
258 262 for agent in self.subprocess_dict.keys():
259   - print(agent)
260 263 if AgentSurvey.objects.get(name=agent).status != "EXITING":
261 264 self.do_stop_agent(agent)
262 265 cmd = AgentCmd.objects.filter(full_name="do_stop asap",recipient=agent).latest("s_deposit_time")
... ... @@ -358,6 +361,7 @@ if __name__ == "__main__":
358 361 parser.add_argument("--computer",dest="computer",help='Launch agent with simulated computer hostname',action="store")
359 362 parser.add_argument("--agent",dest="agent",help='Launch an specific agent ',action="store")
360 363 parser.add_argument("-t", action="store_true" )
  364 + parser.add_argument("-d", action="store_true" )
361 365 args = vars(parser.parse_args())
362 366 agent = build_agent(AgentSST,param_constr=args)
363 367 # agent = build_agent(AgentSST)
... ...
src/core/pyros_django/common/admin.py
... ... @@ -7,6 +7,9 @@ from django.conf import settings
7 7  
8 8 from common.models import *
9 9  
  10 +# Necessary since new device/models.py file
  11 +from devices.models import Detector, Filter, AgentDeviceStatus, FilterWheel, Telescope, PlcDevice, PlcDeviceStatus
  12 +
10 13  
11 14 # EP added
12 15 class ReadOnlyModelAdmin(admin.ModelAdmin):
... ...
src/core/pyros_django/common/models.py
... ... @@ -4,9 +4,7 @@
4 4 from __future__ import annotations
5 5  
6 6 # Stdlib imports
7   -from numpy import False_
8 7 from src.device_controller.abstract_component.device_controller import DeviceCmd
9   -from enum import Enum
10 8 from datetime import datetime, timedelta, date
11 9 from dateutil.relativedelta import relativedelta
12 10 import os
... ... @@ -31,17 +29,7 @@ from django.utils import timezone
31 29 # DeviceCommand is used by class Command
32 30 sys.path.append("../../..")
33 31  
34   -'''
35   -NOT USED - to be removed
36   -class PyrosState(Enum):
37   - START = 'Starting'
38   - PA = 'Passive'
39   - INI = "INIT"
40   - STAND = "Standby"
41   - SCHED_START = 'Scheduler startup'
42   - SCHED = 'Scheduler'
43   - SCHED_CLOSE = 'Scheduler closing'
44   -'''
  32 +
45 33  
46 34  
47 35 """
... ... @@ -176,43 +164,28 @@ class Company(models.Model):
176 164 """
177 165  
178 166  
179   -def printd(*args, **kwargs):
180   - if os.environ.get('PYROS_DEBUG', '0') == '1':
181   - print('(MODEL)', *args, **kwargs)
182   -
183 167 # ---
184 168 # --- Utility functions
185 169 # ---
186 170  
  171 +def printd(*args, **kwargs):
  172 + if os.environ.get('PYROS_DEBUG', '0') == '1':
  173 + print('(MODEL)', *args, **kwargs)
187 174  
188 175 def get_or_create_unique_row_from_model(model: models.Model):
189 176 # return model.objects.get(id=1) if model.objects.exists() else model.objects.create(id=1)
190 177 return model.objects.first() if model.objects.exists() else model.objects.create(id=1)
191 178  
192 179  
  180 +
  181 +
  182 +
193 183 """
194 184 ------------------------
195 185 BASE MODEL CLASSES
196 186 ------------------------
197 187 """
198 188  
199   -
200   -class Device(models.Model):
201   - name = models.CharField(max_length=45, blank=True, null=True)
202   - desc = models.TextField(blank=True, null=True)
203   - created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
204   - updated = models.DateTimeField(blank=True, null=True, auto_now=True)
205   - is_online = models.BooleanField(default=False)
206   - status = models.CharField(max_length=11, blank=True, null=True)
207   - maintenance_date = models.DateTimeField(blank=True, null=True)
208   -
209   - class Meta:
210   - abstract = True
211   -
212   - def __str__(self):
213   - return (str(self.name))
214   -
215   -
216 189 class Request(models.Model):
217 190 pyros_user = models.ForeignKey(
218 191 'PyrosUser', on_delete=models.DO_NOTHING, related_name="requests")
... ... @@ -245,64 +218,51 @@ class Request(models.Model):
245 218 ------------------------
246 219 """
247 220  
248   -# TODO: A VIRER car remplacรฉ par AgentDeviceStatus
249 221  
250 222  
251   -class AgentDeviceTelescopeStatus(models.Model):
252   - #created = models.DateTimeField('status date', blank=True, null=True, auto_now_add=True)
253   - updated = models.DateTimeField(
254   - 'status date', blank=True, null=True, auto_now=True)
255   - radec = models.CharField('agent mode', max_length=30, blank=True)
  223 +# TODO: A mettre dans device/models.py ?
  224 +class Image(models.Model):
  225 + plan = models.ForeignKey(
  226 + 'Plan', on_delete=models.CASCADE, related_name="images")
  227 + nrtanalysis = models.ForeignKey(
  228 + 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images")
  229 + name = models.CharField(max_length=45, blank=True, null=True)
  230 + desc = models.TextField(blank=True, null=True)
  231 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  232 + updated = models.DateTimeField(blank=True, null=True, auto_now=True)
  233 + date_from_gps = models.CharField(max_length=45, blank=True, null=True)
  234 + level = models.IntegerField(blank=True, null=True)
  235 + type = models.CharField(max_length=5, blank=True, null=True)
  236 + quality = models.CharField(max_length=45, blank=True, null=True)
  237 + flaggps = models.CharField(max_length=45, blank=True, null=True)
  238 + exposure = models.CharField(max_length=45, blank=True, null=True)
  239 + tempext = models.CharField(max_length=45, blank=True, null=True)
  240 + pressure = models.CharField(max_length=45, blank=True, null=True)
  241 + humidext = models.CharField(max_length=45, blank=True, null=True)
  242 + wind = models.CharField(max_length=45, blank=True, null=True)
  243 + wind_dir = models.CharField(max_length=45, blank=True, null=True)
  244 + dwnimg = models.CharField(max_length=45, blank=True, null=True)
  245 + dwncata = models.CharField(max_length=45, blank=True, null=True)
  246 + dwn = models.CharField(max_length=45, blank=True, null=True)
  247 + level0_fits_name = models.CharField(max_length=45, blank=True, null=True)
  248 + level1a_fits_name = models.CharField(max_length=45, blank=True, null=True)
  249 + level1b_fits_name = models.CharField(max_length=45, blank=True, null=True)
256 250  
257 251 class Meta:
258 252 managed = True
259   - db_table = 'agent_device_telescope_status'
260   - #verbose_name = "agent survey"
261   - #verbose_name_plural = "agents survey"
  253 + db_table = 'image'
262 254  
263   - """
264 255 def __str__(self):
265   - return (f"Agent {self.name} at {self.updated} in mode {self.mode} and status {self.status}")
266   - """
  256 + return (str(self.name))
  257 +
  258 +
267 259  
268 260  
269   -class AgentDeviceStatus(models.Model):
270   - """Table storing various status parameters for EACH Device.
271 261  
272   - Attributes:
273   - attr1 (str): Description of `attr1`.
274   - attr2 (:obj:`int`, optional): Description of `attr2`.
275 262  
276   - """
277   - #created = models.DateTimeField('status date', blank=True, null=True, auto_now_add=True)
278   - agent = models.CharField(
279   - 'Name of the agent that saved this parameter', max_length=45, blank=True, null=True)
280   - #radec = models.CharField('agent mode', max_length=30, blank=True)
281   - status = models.CharField(
282   - 'status parameters json dictionnary (ex: {radec:..., speed:...})', max_length=300, blank=True, null=True)
283   - date_updated = models.DateTimeField(
284   - 'status parameter date', blank=True, null=True, auto_now=True)
285 263  
286   - class Meta:
287   - managed = True
288   - db_table = 'agent_device_status'
289   - verbose_name = "agent device status"
290   - verbose_name_plural = "agent devices status"
291 264  
292   - def __str__(self):
293   - return (f"Agent {self.agent} last status is ({self.status}) (saved at {self.date_updated})")
294 265  
295   - @classmethod
296   - def getStatusForAgent(cls, agent: str) -> str:
297   - return cls.objects.filter(agent=agent)[0] if cls.objects.filter(agent=agent).exists() else cls.objects.create(agent=agent)
298   - '''
299   - return cls.objects.filter(agent=agent)[0].status if cls.objects.filter(agent=agent).exists() else cls.objects.create(agent=agent).status
300   - agent_status = cls.objects.filter(agent=agent)
301   - if agent_status.exists():
302   - return agent_status[0].status
303   - else:
304   - return cls.objects.create(agent=agent)
305   - '''
306 266  
307 267  
308 268 class AgentLogs(models.Model):
... ... @@ -1379,130 +1339,6 @@ class Country(models.Model):
1379 1339 return (str(self.name))
1380 1340  
1381 1341  
1382   -class Detector(Device):
1383   - VIS = "Visible camera"
1384   - NIR = "Cagire"
1385   -
1386   - telescope = models.ForeignKey(
1387   - 'Telescope', models.DO_NOTHING, related_name="detectors")
1388   - nb_photo_x = models.IntegerField(blank=True, null=True)
1389   - nb_photo_y = models.IntegerField(blank=True, null=True)
1390   - photo_size_x = models.IntegerField(blank=True, null=True)
1391   - photo_size_y = models.IntegerField(blank=True, null=True)
1392   - has_shutter = models.BooleanField(default=False)
1393   - equivalent_foc_len = models.CharField(max_length=45, blank=True, null=True)
1394   - acq_start = models.DateTimeField(blank=True, null=True)
1395   - acq_stop = models.DateTimeField(blank=True, null=True)
1396   - check_temp = models.FloatField(blank=True, null=True)
1397   - gain = models.FloatField(blank=True, null=True)
1398   - readout_noise = models.FloatField(blank=True, null=True)
1399   - readout_time = models.FloatField(blank=True, null=True)
1400   - idcam_readout_mode = models.IntegerField(blank=True, null=True)
1401   -
1402   - class Meta:
1403   - managed = True
1404   - db_table = 'detector'
1405   -
1406   - def __str__(self):
1407   - return str(self.name)
1408   -
1409   - def device_name(self):
1410   - return self.__str__()
1411   - device_name.short_description = "Name"
1412   -
1413   -
1414   -class Dome(Device):
1415   - DOME = "Dome"
1416   -
1417   - open = models.BooleanField(default=False, blank=True)
1418   -
1419   - class Meta:
1420   - managed = True
1421   - db_table = 'dome'
1422   -
1423   - def __str__(self):
1424   - return str(self.name)
1425   -
1426   - def device_name(self):
1427   - return self.__str__()
1428   - device_name.short_description = "Name"
1429   -
1430   -
1431   -class Filter(Device):
1432   - VIS_FILTER_1 = "First visible filter"
1433   - VIS_FILTER_2 = "Second visible filter"
1434   - NIR_FILTER_1 = "First infrared filter"
1435   - NIR_FILTER_2 = "Second infrared filter"
1436   -
1437   - filter_wheel = models.ForeignKey(
1438   - "FilterWheel", models.DO_NOTHING, related_name="filters", blank=True, null=True)
1439   - category = models.CharField(max_length=1, blank=True, null=True)
1440   - transmission_curve_doc = models.CharField(
1441   - max_length=45, blank=True, null=True)
1442   -
1443   - class Meta:
1444   - managed = True
1445   - db_table = 'filter'
1446   -
1447   - def __str__(self):
1448   - return (str(self.name))
1449   -
1450   - def device_name(self):
1451   - return self.__str__()
1452   - device_name.short_description = "Name"
1453   -
1454   -
1455   -class FilterWheel(Device):
1456   - detector = models.OneToOneField(Detector, on_delete=models.CASCADE,
1457   - related_name="filter_wheel", blank=True, null=True)
1458   -
1459   - class Meta:
1460   - managed = True
1461   - db_table = 'filter_wheel'
1462   -
1463   - def __str__(self):
1464   - return (str(self.name))
1465   -
1466   - def device_name(self):
1467   - return self.__str__()
1468   - device_name.short_description = "Name"
1469   -
1470   -
1471   -class Image(models.Model):
1472   - plan = models.ForeignKey(
1473   - 'Plan', on_delete=models.CASCADE, related_name="images")
1474   - nrtanalysis = models.ForeignKey(
1475   - 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images")
1476   - name = models.CharField(max_length=45, blank=True, null=True)
1477   - desc = models.TextField(blank=True, null=True)
1478   - created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
1479   - updated = models.DateTimeField(blank=True, null=True, auto_now=True)
1480   - date_from_gps = models.CharField(max_length=45, blank=True, null=True)
1481   - level = models.IntegerField(blank=True, null=True)
1482   - type = models.CharField(max_length=5, blank=True, null=True)
1483   - quality = models.CharField(max_length=45, blank=True, null=True)
1484   - flaggps = models.CharField(max_length=45, blank=True, null=True)
1485   - exposure = models.CharField(max_length=45, blank=True, null=True)
1486   - tempext = models.CharField(max_length=45, blank=True, null=True)
1487   - pressure = models.CharField(max_length=45, blank=True, null=True)
1488   - humidext = models.CharField(max_length=45, blank=True, null=True)
1489   - wind = models.CharField(max_length=45, blank=True, null=True)
1490   - wind_dir = models.CharField(max_length=45, blank=True, null=True)
1491   - dwnimg = models.CharField(max_length=45, blank=True, null=True)
1492   - dwncata = models.CharField(max_length=45, blank=True, null=True)
1493   - dwn = models.CharField(max_length=45, blank=True, null=True)
1494   - level0_fits_name = models.CharField(max_length=45, blank=True, null=True)
1495   - level1a_fits_name = models.CharField(max_length=45, blank=True, null=True)
1496   - level1b_fits_name = models.CharField(max_length=45, blank=True, null=True)
1497   -
1498   - class Meta:
1499   - managed = True
1500   - db_table = 'image'
1501   -
1502   - def __str__(self):
1503   - return (str(self.name))
1504   -
1505   -
1506 1342 class Log(models.Model):
1507 1343 agent = models.CharField(max_length=45, blank=True, null=True)
1508 1344 created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
... ... @@ -1573,156 +1409,6 @@ class Plan(models.Model):
1573 1409 def __str__(self) -> str:
1574 1410 return f"Plan of Album {self.album.name} has {self.nb_images} image(s)"
1575 1411  
1576   -class PlcDeviceStatus(models.Model):
1577   - device = models.ForeignKey(
1578   - 'PlcDevice', on_delete=models.CASCADE, related_name='current_status')
1579   - created = models.DateTimeField(
1580   - auto_now_add=True, editable=False, blank=True)
1581   - outside_temp = models.DecimalField(
1582   - max_digits=15, decimal_places=8, blank=True, null=True)
1583   - outside_temp_unit = models.CharField(max_length=45, blank=True, null=True)
1584   - outside_humidity = models.DecimalField(
1585   - max_digits=15, decimal_places=8, blank=True, null=True)
1586   - outside_humidity_unit = models.CharField(
1587   - max_length=45, blank=True, null=True)
1588   - pressure = models.DecimalField(
1589   - max_digits=15, decimal_places=8, blank=True, null=True)
1590   - pressure_unit = models.CharField(max_length=45, blank=True, null=True)
1591   - rain_rate = models.DecimalField(
1592   - max_digits=15, decimal_places=8, blank=True, null=True)
1593   - rain_rate_unit = models.CharField(max_length=45, blank=True, null=True)
1594   - wind_speed = models.DecimalField(
1595   - max_digits=15, decimal_places=8, blank=True, null=True)
1596   - wind_speed_unit = models.CharField(max_length=45, blank=True, null=True)
1597   - wind_dir = models.DecimalField(
1598   - max_digits=15, decimal_places=8, blank=True, null=True)
1599   - wind_dir_unit = models.CharField(max_length=45, blank=True, null=True)
1600   - dew_point = models.DecimalField(
1601   - max_digits=15, decimal_places=8, blank=True, null=True)
1602   - dew_point_unit = models.CharField(max_length=45, blank=True, null=True)
1603   - analog = models.DecimalField(
1604   - max_digits=15, decimal_places=8, blank=True, null=True)
1605   - analog_unit = models.CharField(max_length=45, blank=True, null=True)
1606   - digital = models.DecimalField(
1607   - max_digits=15, decimal_places=8, blank=True, null=True)
1608   - digital_unit = models.CharField(max_length=45, blank=True, null=True)
1609   - inside_temp = models.DecimalField(
1610   - max_digits=15, decimal_places=8, blank=True, null=True)
1611   - inside_temp_unit = models.CharField(max_length=45, blank=True, null=True)
1612   - inside_humidity = models.DecimalField(
1613   - max_digits=15, decimal_places=8, blank=True, null=True)
1614   - inside_humidity_unit = models.CharField(
1615   - max_length=45, blank=True, null=True)
1616   - wind_dir_cardinal = models.CharField(max_length=45, blank=True, null=True)
1617   - wind_dir_cardinal_unit = models.CharField(
1618   - max_length=45, blank=True, null=True)
1619   - sensor_temperature = models.DecimalField(
1620   - max_digits=15, decimal_places=8, blank=True, null=True)
1621   - sensor_temperature_unit = models.CharField(
1622   - max_length=45, blank=True, null=True)
1623   - sky_temperature = models.DecimalField(
1624   - max_digits=15, decimal_places=8, blank=True, null=True)
1625   - sky_temperature_unit = models.CharField(
1626   - max_length=45, blank=True, null=True)
1627   - status = models.CharField(max_length=45, blank=True, null=True)
1628   - current = models.DecimalField(
1629   - max_digits=15, decimal_places=8, blank=True, null=True)
1630   - current_unit = models.CharField(max_length=45, blank=True, null=True)
1631   - is_safe = models.BooleanField(default=True)
1632   - plc_mode = models.CharField(max_length=4, null=True)
1633   - lights = models.CharField(max_length=3, null=True)
1634   - shutters = models.CharField(max_length=5, null=True)
1635   -
1636   - class Meta:
1637   - managed = True
1638   - db_table = 'plc_devices_status'
1639   -
1640   - def __str__(self):
1641   - return (str(self.__dict__))
1642   -
1643   - '''
1644   - TODO : This function is Ugly,
1645   - we should change this with a function pointer array
1646   - and setters getters for each attribute
1647   - '''
1648   -
1649   - def setValue(self, key, value, unit=""):
1650   - if key == "Temperature_outside":
1651   - self.outside_temp = value
1652   - self.outside_temp_unit = unit
1653   - elif key == "Humidity_outside":
1654   - self.outside_humidity = value
1655   - self.outside_humidity_unit = unit
1656   - elif key == "_Pressure":
1657   - self.pressure = value
1658   - self.pressure_unit = unit
1659   - elif key == "Rain_boolean": # RainRate
1660   - self.rain_rate = value
1661   - self.rain_rate_unit = 'boulean'
1662   - elif key == "Wind_speed":
1663   - self.wind_speed = value
1664   - self.wind_speed_unit = unit
1665   - elif key == "Wind_dir":
1666   - self.wind_dir = value
1667   - self.wind_dir_unit = unit
1668   - elif key == "_DewPoint":
1669   - self.dew_point = value
1670   - self.dew_point_unit = unit
1671   - elif key == "_analog":
1672   - self.analog = value
1673   - self.analog_unit = unit
1674   - elif key == "_digital":
1675   - self.digital = value
1676   - self.digital_unit = unit
1677   - elif key == "_InsideTemp":
1678   - self.inside_temp = value
1679   - self.inside_temp_unit = unit
1680   - elif key == "_InsideHumidity":
1681   - self.inside_humidity = value
1682   - self.inside_humidity_unit = unit
1683   - elif key == "_WindDirCardinal":
1684   - self.wind_dir_cardinal = value
1685   - self.wind_dir_cardinal_unit = unit
1686   - elif key == "_SensorTemperature":
1687   - self.sensor_temperature = value
1688   - self.sensor_temperature_unit = unit
1689   - elif key == "_SkyTemperature":
1690   - self.sky_temperature = value
1691   - self.sky_temperature_unit = unit
1692   - # PM 20190222 try patch
1693   - elif key == "Error_code":
1694   - self.status = value
1695   - elif key == "current":
1696   - self.current = value
1697   - self.current_unit = unit
1698   - elif key == "mode":
1699   - self.plc_mode = value
1700   - elif key == "is_safe":
1701   - self.is_safe = value
1702   - elif key == "LIGHTS":
1703   - self.lights = value
1704   - elif key == "SHUTTERS":
1705   - self.shutters = value
1706   - else:
1707   - # PM 20190222 ignore unrecognized
1708   - #raise KeyError("Key " + str(key) + " unrecognized")
1709   - pass
1710   -
1711   -
1712   -class PlcDevice(Device):
1713   - #device = models.ForeignKey('Plc', on_delete=models.CASCADE, related_name='plc_devices')
1714   - name = models.CharField(max_length=45, blank=True, null=True)
1715   - desc = models.TextField(blank=True, null=True)
1716   - created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
1717   - updated = models.DateTimeField(blank=True, null=True, auto_now=True)
1718   -
1719   - class Meta:
1720   - managed = True
1721   - db_table = 'plc_devices'
1722   -
1723   - def __str__(self):
1724   - return str(self.name)
1725   -
1726 1412  
1727 1413 # class Plc(Device):
1728 1414 # last_update_status = models.DateTimeField(blank=True, null=True)
... ... @@ -2474,50 +2160,6 @@ class TaskId(models.Model):
2474 2160 return (str(self.task) + " - " + str(self.task_id))
2475 2161  
2476 2162  
2477   -class Telescope(Device):
2478   - TELESCOPE = "Telescope"
2479   -
2480   - mount_type = models.CharField(max_length=9, blank=True, null=True)
2481   - diameter = models.FloatField(blank=True, null=True)
2482   - latitude = models.FloatField(blank=True, null=True)
2483   - longitude = models.FloatField(blank=True, null=True)
2484   - sens = models.CharField(max_length=1, blank=True, null=True)
2485   - altitude = models.FloatField(blank=True, null=True)
2486   - readout_time = models.IntegerField(blank=True, null=True)
2487   - slew_time = models.IntegerField(blank=True, null=True)
2488   - slew_dead = models.IntegerField(blank=True, null=True)
2489   - slew_rate_max = models.FloatField(blank=True, null=True)
2490   - horizon_type = models.CharField(max_length=45, blank=True, null=True)
2491   - horizon_def = models.FloatField(blank=True, null=True)
2492   - lim_dec_max = models.FloatField(blank=True, null=True)
2493   - lim_dec_min = models.FloatField(blank=True, null=True)
2494   - lim_ha_rise = models.FloatField(blank=True, null=True)
2495   - lim_ha_set = models.FloatField(blank=True, null=True)
2496   - address = models.CharField(max_length=45, blank=True, null=True)
2497   - night_elev_sun = models.FloatField(blank=True, null=True)
2498   - mpc_code = models.CharField(max_length=45, blank=True, null=True)
2499   -
2500   - class Meta:
2501   - managed = True
2502   - db_table = 'telescope'
2503   -
2504   - def __str__(self):
2505   - return (self.name)
2506   -
2507   -
2508   -class TelescopeCommand(models.Model):
2509   - created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
2510   - answered = models.DateTimeField(blank=True, null=True)
2511   - request = models.CharField(blank=False, null=False, max_length=255)
2512   - answer = models.TextField(null=True, blank=True)
2513   -
2514   - class Meta:
2515   - managed = True
2516   - db_table = "telescopecommand"
2517   -
2518   - def __str__(self):
2519   - return str(self.request) + str(self.created)
2520   -
2521 2163  
2522 2164 class UserLevel(models.Model):
2523 2165 name = models.CharField(max_length=45, blank=True, null=True)
... ... @@ -2678,6 +2320,9 @@ class Majordome(models.Model):
2678 2320 default = DAY_MODE,
2679 2321 max_length=15
2680 2322 )
  2323 + class Meta:
  2324 + managed = True
  2325 + db_table = 'majordome'
2681 2326  
2682 2327 @classmethod
2683 2328 def object(cls):
... ... @@ -2710,4 +2355,9 @@ class Tickets:
2710 2355 (LEVEL_FOUR,"Known issue without immediate solution"),
2711 2356 (LEVEL_FIVE,"Issue not categorized until it happened")
2712 2357 )
2713   - security_level = models.TextField(choices=SECURITY_LEVEL_CHOICES, default=LEVEL_ONE)
2714 2358 \ No newline at end of file
  2359 + security_level = models.TextField(choices=SECURITY_LEVEL_CHOICES, default=LEVEL_ONE)
  2360 +
  2361 + class Meta:
  2362 + managed = True
  2363 + db_table = 'tickets'
  2364 + verbose_name_plural = "tickets"
2715 2365 \ No newline at end of file
... ...
src/core/pyros_django/dashboard/templates/dashboard/agent_detail.html
... ... @@ -33,7 +33,10 @@
33 33 .violet{
34 34 color : violet;
35 35 }
36   - .table{
  36 + #agentsst_table{
  37 + max-width: fit-content;
  38 + }
  39 + #cmd_table{
37 40 /*max-width: 90vw;*/
38 41 table-layout: fixed;
39 42 }
... ... @@ -46,12 +49,17 @@
46 49 overflow:hidden;
47 50 word-wrap: break-word;
48 51 }
  52 + .date_td{
  53 + max-width: fit-content;
  54 + word-wrap: none;
  55 + overflow: none;
  56 + }
49 57 #td_result{
50 58 max-width:20vw;
51 59 }
52 60 .theader{
53 61 text-align: center;
54   - min-width: 7vw;
  62 + //min-width:7vw;
55 63 max-width: 12vw;
56 64 vertical-align:top;
57 65 }
... ... @@ -119,13 +127,13 @@
119 127 {% endcomment %}
120 128  
121 129 <h2 id="cmd_unimplemented_message" style="color:red;"></h2>
122   - <h2 id="cmdform_exiting" style="color:red;display:none" > {{ agent_name }} is not available.</h2>
  130 + <h2 id="cmdform_exiting" style="color:red;display:none" > {{ agent_name }} is OFF.</h2>
123 131  
124 132 {% endif %}
125 133 {% if managed_agents != None %}
126 134 <div>
127 135 <h2> Managed agents </h2>
128   - <table
  136 + <table id="agentsst_table"
129 137 class="table table-sm table-bordered tablesorter">
130 138 <thead>
131 139 <tr>
... ... @@ -202,10 +210,10 @@
202 210 <th class="theader align-top">Status </th>
203 211 <th class="theader align-top small-cln">Validity<br> (s)</th>
204 212 <th class="theader align-top small-cln">Timeout<br> (s)</th>
205   - <th class="theader align-top">Deposit<br> (UTC)</th>
206   - <th class="theader align-top">Read<br> (UTC)</th>
207   - <th class="theader align-top">Exec start <br>(UTC)</th>
208   - <th class="theader align-top">Exec end <br>(UTC)</th>
  213 + <th class="theader align-top date_td">Deposit<br> (UTC)</th>
  214 + <th class="theader align-top date_td">Read<br> (UTC)</th>
  215 + <th class="theader align-top date_td">Exec start <br>(UTC)</th>
  216 + <th class="theader align-top date_td">Exec end <br>(UTC)</th>
209 217 <th class="theader align-top small-cln">Exec time <br>(s)</th>
210 218 <th class="theader align-top">Result</th>
211 219 </tr>
... ...
src/core/pyros_django/dashboard/views.py
... ... @@ -9,7 +9,11 @@ from django.contrib.auth.decorators import login_required
9 9 import datetime
10 10 from datetime import timezone
11 11 from django.core.paginator import Paginator
12   -from common.models import Log, SP_Period_Guest, WeatherWatch, SiteWatch, ScientificProgram, Config, PyrosUser, PlcDeviceStatus, Telescope, TelescopeCommand, UserLevel, WeatherWatchHistory, Majordome, AgentSurvey, AgentCmd
  12 +
  13 +#from common.models import Log, SP_Period_Guest, WeatherWatch, SiteWatch, ScientificProgram, Config, PyrosUser, PlcDeviceStatus, Telescope, TelescopeCommand, UserLevel, WeatherWatchHistory, Majordome, AgentSurvey, AgentCmd
  14 +from common.models import Log, SP_Period_Guest, WeatherWatch, SiteWatch, ScientificProgram, Config, PyrosUser, UserLevel, WeatherWatchHistory, Majordome, AgentSurvey, AgentCmd
  15 +from devices.models import PlcDeviceStatus, Telescope, TelescopeCommand
  16 +
13 17 from django.core import serializers
14 18 import utils.Logger as l
15 19 from django.forms import modelformset_factory
... ...
src/core/pyros_django/devices/models.py
1   -from django.db import models
  1 +#from django.db import models
2 2  
3 3 # Create your models here.
  4 +
  5 +##from __future__ import unicode_literals
  6 +
  7 +# (EP 21/9/22) To allow autoreferencing (ex: AgentCmd.create() returns a AgentCmd)
  8 +from __future__ import annotations
  9 +
  10 +# Stdlib imports
  11 +from numpy import False_
  12 +from src.device_controller.abstract_component.device_controller import DeviceCmd
  13 +from enum import Enum
  14 +from datetime import datetime, timedelta, date
  15 +from dateutil.relativedelta import relativedelta
  16 +import os
  17 +import sys
  18 +from typing import Any, List, Tuple, Optional
  19 +import re
  20 +
  21 +# Django imports
  22 +from django.core.validators import MaxValueValidator, MinValueValidator
  23 +
  24 +# DJANGO imports
  25 +from django.contrib.auth.models import AbstractUser, UserManager
  26 +from django.db import models
  27 +from django.db.models import Q, Max
  28 +from django.core.validators import MaxValueValidator, MinValueValidator
  29 +from django.db.models.deletion import DO_NOTHING
  30 +from django.db.models.expressions import F
  31 +from django.db.models.query import QuerySet
  32 +from model_utils import Choices
  33 +from django.utils import timezone
  34 +# Project imports
  35 +# DeviceCommand is used by class Command
  36 +sys.path.append("../../..")
  37 +
  38 +'''
  39 +NOT USED - to be removed
  40 +class PyrosState(Enum):
  41 + START = 'Starting'
  42 + PA = 'Passive'
  43 + INI = "INIT"
  44 + STAND = "Standby"
  45 + SCHED_START = 'Scheduler startup'
  46 + SCHED = 'Scheduler'
  47 + SCHED_CLOSE = 'Scheduler closing'
  48 +'''
  49 +
  50 +
  51 +"""
  52 +STYLE RULES
  53 +===========
  54 +https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html
  55 +https://steelkiwi.com/blog/best-practices-working-django-models-python/
  56 +
  57 +- Model name => singular
  58 + Call it Company instead of Companies.
  59 + A model definition is the representation of a single object (the object in this example is a company),
  60 + and not a collection of companies
  61 + The model definition is a class, so always use CapWords convention (no underscores)
  62 + E.g. User, Permission, ContentType, etc.
  63 +
  64 +- For the modelโ€™s attributes use snake_case.
  65 + E.g. first_name, last_name, etc
  66 +
  67 +- Blank and Null Fields (https://simpleisbetterthancomplex.com/tips/2016/07/25/django-tip-8-blank-or-null.html)
  68 + - Null: It is database-related. Defines if a given database column will accept null values or not.
  69 + - Blank: It is validation-related. It will be used during forms validation, when calling form.is_valid().
  70 + Do not use null=True for text-based fields that are optional.
  71 + Otherwise, you will end up having two possible values for โ€œno dataโ€, that is: None and an empty string.
  72 + Having two possible values for โ€œno dataโ€ is redundant.
  73 + The Django convention is to use the empty string, not NULL.
  74 + Example:
  75 + # The default values of `null` and `blank` are `False`.
  76 + class Person(models.Model):
  77 + name = models.CharField(max_length=255) # Mandatory
  78 + bio = models.TextField(max_length=500, blank=True) # Optional (don't put null=True)
  79 + birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True)
  80 + The default values of null and blank are False.
  81 + Special case, when you need to accept NULL values for a BooleanField, use NullBooleanField instead.
  82 +
  83 +- Choices : you can use Choices from the model_utils library. Take model Article, for instance:
  84 + from model_utils import Choices
  85 + class Article(models.Model):
  86 + STATUSES = Choices(
  87 + (0, 'draft', _('draft')),
  88 + (1, 'published', _('published')) )
  89 + status = models.IntegerField(choices=STATUSES, default=STATUSES.draft)
  90 +
  91 +- Reverse Relationships
  92 +
  93 + - related_name :
  94 + Rule of thumb: if you are not sure what would be the related_name,
  95 + use the plural of the model holding the ForeignKey.
  96 + ex:
  97 + class Company:
  98 + name = models.CharField(max_length=30)
  99 + class Employee:
  100 + first_name = models.CharField(max_length=30)
  101 + last_name = models.CharField(max_length=30)
  102 + company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees')
  103 + usage:
  104 + google = Company.objects.get(name='Google')
  105 + google.employees.all()
  106 + You can also use the reverse relationship to modify the company field on the Employee instances:
  107 + vitor = Employee.objects.get(first_name='Vitor')
  108 + google = Company.objects.get(name='Google')
  109 + google.employees.add(vitor)
  110 +
  111 + - related_query_name :
  112 + This kind of relationship also applies to query filters.
  113 + For example, if I wanted to list all companies that employs people named โ€˜Vitorโ€™, I could do the following:
  114 + companies = Company.objects.filter(employee__first_name='Vitor')
  115 + If you want to customize the name of this relationship, here is how we do it:
  116 + class Employee:
  117 + first_name = models.CharField(max_length=30)
  118 + last_name = models.CharField(max_length=30)
  119 + company = models.ForeignKey(
  120 + Company,
  121 + on_delete=models.CASCADE,
  122 + related_name='employees',
  123 + related_query_name='person'
  124 + )
  125 + Then the usage would be:
  126 + companies = Company.objects.filter(person__first_name='Vitor')
  127 +
  128 + To use it consistently, related_name goes as plural and related_query_name goes as singular.
  129 +
  130 +
  131 +GENERAL EXAMPLE
  132 +=======
  133 +
  134 +from django.db import models
  135 +from django.urls import reverse
  136 +
  137 +class Company(models.Model):
  138 + # CHOICES
  139 + PUBLIC_LIMITED_COMPANY = 'PLC'
  140 + PRIVATE_COMPANY_LIMITED = 'LTD'
  141 + LIMITED_LIABILITY_PARTNERSHIP = 'LLP'
  142 + COMPANY_TYPE_CHOICES = (
  143 + (PUBLIC_LIMITED_COMPANY, 'Public limited company'),
  144 + (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'),
  145 + (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'),
  146 + )
  147 +
  148 + # DATABASE FIELDS
  149 + name = models.CharField('name', max_length=30)
  150 + vat_identification_number = models.CharField('VAT', max_length=20)
  151 + company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES)
  152 +
  153 + # MANAGERS
  154 + objects = models.Manager()
  155 + limited_companies = LimitedCompanyManager()
  156 +
  157 + # META CLASS
  158 + class Meta:
  159 + verbose_name = 'company'
  160 + verbose_name_plural = 'companies'
  161 +
  162 + # TO STRING METHOD
  163 + def __str__(self):
  164 + return self.name
  165 +
  166 + # SAVE METHOD
  167 + def save(self, *args, **kwargs):
  168 + do_something()
  169 + super().save(*args, **kwargs) # Call the "real" save() method.
  170 + do_something_else()
  171 +
  172 + # ABSOLUTE URL METHOD
  173 + def get_absolute_url(self):
  174 + return reverse('company_details', kwargs={'pk': self.id})
  175 +
  176 + # OTHER METHODS
  177 + def process_invoices(self):
  178 + do_something()
  179 +
  180 +"""
  181 +
  182 +
  183 +'''
  184 +# ---
  185 +# --- Utility functions
  186 +# ---
  187 +
  188 +def printd(*args, **kwargs):
  189 + if os.environ.get('PYROS_DEBUG', '0') == '1':
  190 + print('(MODEL)', *args, **kwargs)
  191 +
  192 +def get_or_create_unique_row_from_model(model: models.Model):
  193 + # return model.objects.get(id=1) if model.objects.exists() else model.objects.create(id=1)
  194 + return model.objects.first() if model.objects.exists() else model.objects.create(id=1)
  195 +'''
  196 +
  197 +
  198 +"""
  199 +------------------------
  200 + BASE MODEL CLASSES
  201 +------------------------
  202 +"""
  203 +
  204 +
  205 +class Device(models.Model):
  206 + name = models.CharField(max_length=45, blank=True, null=True)
  207 + desc = models.TextField(blank=True, null=True)
  208 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  209 + updated = models.DateTimeField(blank=True, null=True, auto_now=True)
  210 + is_online = models.BooleanField(default=False)
  211 + status = models.CharField(max_length=11, blank=True, null=True)
  212 + maintenance_date = models.DateTimeField(blank=True, null=True)
  213 +
  214 + class Meta:
  215 + abstract = True
  216 +
  217 + def __str__(self):
  218 + return (str(self.name))
  219 +
  220 +
  221 +'''
  222 +class Image(models.Model):
  223 + plan = models.ForeignKey(
  224 + 'Plan', on_delete=models.CASCADE, related_name="images")
  225 + nrtanalysis = models.ForeignKey(
  226 + 'NrtAnalysis', models.DO_NOTHING, blank=True, null=True, related_name="images")
  227 + name = models.CharField(max_length=45, blank=True, null=True)
  228 + desc = models.TextField(blank=True, null=True)
  229 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  230 + updated = models.DateTimeField(blank=True, null=True, auto_now=True)
  231 + date_from_gps = models.CharField(max_length=45, blank=True, null=True)
  232 + level = models.IntegerField(blank=True, null=True)
  233 + type = models.CharField(max_length=5, blank=True, null=True)
  234 + quality = models.CharField(max_length=45, blank=True, null=True)
  235 + flaggps = models.CharField(max_length=45, blank=True, null=True)
  236 + exposure = models.CharField(max_length=45, blank=True, null=True)
  237 + tempext = models.CharField(max_length=45, blank=True, null=True)
  238 + pressure = models.CharField(max_length=45, blank=True, null=True)
  239 + humidext = models.CharField(max_length=45, blank=True, null=True)
  240 + wind = models.CharField(max_length=45, blank=True, null=True)
  241 + wind_dir = models.CharField(max_length=45, blank=True, null=True)
  242 + dwnimg = models.CharField(max_length=45, blank=True, null=True)
  243 + dwncata = models.CharField(max_length=45, blank=True, null=True)
  244 + dwn = models.CharField(max_length=45, blank=True, null=True)
  245 + level0_fits_name = models.CharField(max_length=45, blank=True, null=True)
  246 + level1a_fits_name = models.CharField(max_length=45, blank=True, null=True)
  247 + level1b_fits_name = models.CharField(max_length=45, blank=True, null=True)
  248 +
  249 + class Meta:
  250 + managed = True
  251 + db_table = 'image'
  252 +
  253 + def __str__(self):
  254 + return (str(self.name))
  255 +'''
  256 +
  257 +
  258 +"""
  259 +------------------------
  260 + OTHER MODEL CLASSES
  261 +------------------------
  262 +"""
  263 +
  264 +# TODO: A VIRER car remplacรฉ par AgentDeviceStatus
  265 +
  266 +
  267 +class AgentDeviceTelescopeStatus(models.Model):
  268 + #created = models.DateTimeField('status date', blank=True, null=True, auto_now_add=True)
  269 + updated = models.DateTimeField(
  270 + 'status date', blank=True, null=True, auto_now=True)
  271 + radec = models.CharField('agent mode', max_length=30, blank=True)
  272 +
  273 + class Meta:
  274 + managed = True
  275 + db_table = 'agent_device_telescope_status'
  276 + #verbose_name = "agent survey"
  277 + #verbose_name_plural = "agents survey"
  278 +
  279 + """
  280 + def __str__(self):
  281 + return (f"Agent {self.name} at {self.updated} in mode {self.mode} and status {self.status}")
  282 + """
  283 +
  284 +
  285 +class AgentDeviceStatus(models.Model):
  286 + """Table storing various status parameters for EACH Device.
  287 +
  288 + Attributes:
  289 + attr1 (str): Description of `attr1`.
  290 + attr2 (:obj:`int`, optional): Description of `attr2`.
  291 +
  292 + """
  293 + #created = models.DateTimeField('status date', blank=True, null=True, auto_now_add=True)
  294 + agent = models.CharField(
  295 + 'Name of the agent that saved this parameter', max_length=45, blank=True, null=True)
  296 + #radec = models.CharField('agent mode', max_length=30, blank=True)
  297 + status = models.CharField(
  298 + 'status parameters json dictionnary (ex: {radec:..., speed:...})', max_length=300, blank=True, null=True)
  299 + date_updated = models.DateTimeField(
  300 + 'status parameter date', blank=True, null=True, auto_now=True)
  301 +
  302 + class Meta:
  303 + managed = True
  304 + db_table = 'agent_device_status'
  305 + verbose_name = "agent device status"
  306 + verbose_name_plural = "agent devices status"
  307 +
  308 + def __str__(self):
  309 + return (f"Agent {self.agent} last status is ({self.status}) (saved at {self.date_updated})")
  310 +
  311 + @classmethod
  312 + def getStatusForAgent(cls, agent: str) -> str:
  313 + return cls.objects.filter(agent=agent)[0] if cls.objects.filter(agent=agent).exists() else cls.objects.create(agent=agent)
  314 + '''
  315 + return cls.objects.filter(agent=agent)[0].status if cls.objects.filter(agent=agent).exists() else cls.objects.create(agent=agent).status
  316 + agent_status = cls.objects.filter(agent=agent)
  317 + if agent_status.exists():
  318 + return agent_status[0].status
  319 + else:
  320 + return cls.objects.create(agent=agent)
  321 + '''
  322 +
  323 +
  324 +
  325 +class PlcDeviceStatus(models.Model):
  326 + device = models.ForeignKey(
  327 + 'PlcDevice', on_delete=models.CASCADE, related_name='current_status')
  328 + created = models.DateTimeField(
  329 + auto_now_add=True, editable=False, blank=True)
  330 + outside_temp = models.DecimalField(
  331 + max_digits=15, decimal_places=8, blank=True, null=True)
  332 + outside_temp_unit = models.CharField(max_length=45, blank=True, null=True)
  333 + outside_humidity = models.DecimalField(
  334 + max_digits=15, decimal_places=8, blank=True, null=True)
  335 + outside_humidity_unit = models.CharField(
  336 + max_length=45, blank=True, null=True)
  337 + pressure = models.DecimalField(
  338 + max_digits=15, decimal_places=8, blank=True, null=True)
  339 + pressure_unit = models.CharField(max_length=45, blank=True, null=True)
  340 + rain_rate = models.DecimalField(
  341 + max_digits=15, decimal_places=8, blank=True, null=True)
  342 + rain_rate_unit = models.CharField(max_length=45, blank=True, null=True)
  343 + wind_speed = models.DecimalField(
  344 + max_digits=15, decimal_places=8, blank=True, null=True)
  345 + wind_speed_unit = models.CharField(max_length=45, blank=True, null=True)
  346 + wind_dir = models.DecimalField(
  347 + max_digits=15, decimal_places=8, blank=True, null=True)
  348 + wind_dir_unit = models.CharField(max_length=45, blank=True, null=True)
  349 + dew_point = models.DecimalField(
  350 + max_digits=15, decimal_places=8, blank=True, null=True)
  351 + dew_point_unit = models.CharField(max_length=45, blank=True, null=True)
  352 + analog = models.DecimalField(
  353 + max_digits=15, decimal_places=8, blank=True, null=True)
  354 + analog_unit = models.CharField(max_length=45, blank=True, null=True)
  355 + digital = models.DecimalField(
  356 + max_digits=15, decimal_places=8, blank=True, null=True)
  357 + digital_unit = models.CharField(max_length=45, blank=True, null=True)
  358 + inside_temp = models.DecimalField(
  359 + max_digits=15, decimal_places=8, blank=True, null=True)
  360 + inside_temp_unit = models.CharField(max_length=45, blank=True, null=True)
  361 + inside_humidity = models.DecimalField(
  362 + max_digits=15, decimal_places=8, blank=True, null=True)
  363 + inside_humidity_unit = models.CharField(
  364 + max_length=45, blank=True, null=True)
  365 + wind_dir_cardinal = models.CharField(max_length=45, blank=True, null=True)
  366 + wind_dir_cardinal_unit = models.CharField(
  367 + max_length=45, blank=True, null=True)
  368 + sensor_temperature = models.DecimalField(
  369 + max_digits=15, decimal_places=8, blank=True, null=True)
  370 + sensor_temperature_unit = models.CharField(
  371 + max_length=45, blank=True, null=True)
  372 + sky_temperature = models.DecimalField(
  373 + max_digits=15, decimal_places=8, blank=True, null=True)
  374 + sky_temperature_unit = models.CharField(
  375 + max_length=45, blank=True, null=True)
  376 + status = models.CharField(max_length=45, blank=True, null=True)
  377 + current = models.DecimalField(
  378 + max_digits=15, decimal_places=8, blank=True, null=True)
  379 + current_unit = models.CharField(max_length=45, blank=True, null=True)
  380 + is_safe = models.BooleanField(default=True)
  381 + plc_mode = models.CharField(max_length=4, null=True)
  382 + lights = models.CharField(max_length=3, null=True)
  383 + shutters = models.CharField(max_length=5, null=True)
  384 +
  385 + class Meta:
  386 + managed = True
  387 + db_table = 'plc_devices_status'
  388 +
  389 + def __str__(self):
  390 + return (str(self.__dict__))
  391 +
  392 + """
  393 + TODO : This function is Ugly,
  394 + we should change this with a function pointer array
  395 + and setters getters for each attribute
  396 + """
  397 + def setValue(self, key, value, unit=""):
  398 + if key == "Temperature_outside":
  399 + self.outside_temp = value
  400 + self.outside_temp_unit = unit
  401 + elif key == "Humidity_outside":
  402 + self.outside_humidity = value
  403 + self.outside_humidity_unit = unit
  404 + elif key == "_Pressure":
  405 + self.pressure = value
  406 + self.pressure_unit = unit
  407 + elif key == "Rain_boolean": # RainRate
  408 + self.rain_rate = value
  409 + self.rain_rate_unit = 'boulean'
  410 + elif key == "Wind_speed":
  411 + self.wind_speed = value
  412 + self.wind_speed_unit = unit
  413 + elif key == "Wind_dir":
  414 + self.wind_dir = value
  415 + self.wind_dir_unit = unit
  416 + elif key == "_DewPoint":
  417 + self.dew_point = value
  418 + self.dew_point_unit = unit
  419 + elif key == "_analog":
  420 + self.analog = value
  421 + self.analog_unit = unit
  422 + elif key == "_digital":
  423 + self.digital = value
  424 + self.digital_unit = unit
  425 + elif key == "_InsideTemp":
  426 + self.inside_temp = value
  427 + self.inside_temp_unit = unit
  428 + elif key == "_InsideHumidity":
  429 + self.inside_humidity = value
  430 + self.inside_humidity_unit = unit
  431 + elif key == "_WindDirCardinal":
  432 + self.wind_dir_cardinal = value
  433 + self.wind_dir_cardinal_unit = unit
  434 + elif key == "_SensorTemperature":
  435 + self.sensor_temperature = value
  436 + self.sensor_temperature_unit = unit
  437 + elif key == "_SkyTemperature":
  438 + self.sky_temperature = value
  439 + self.sky_temperature_unit = unit
  440 + # PM 20190222 try patch
  441 + elif key == "Error_code":
  442 + self.status = value
  443 + elif key == "current":
  444 + self.current = value
  445 + self.current_unit = unit
  446 + elif key == "mode":
  447 + self.plc_mode = value
  448 + elif key == "is_safe":
  449 + self.is_safe = value
  450 + elif key == "LIGHTS":
  451 + self.lights = value
  452 + elif key == "SHUTTERS":
  453 + self.shutters = value
  454 + else:
  455 + # PM 20190222 ignore unrecognized
  456 + #raise KeyError("Key " + str(key) + " unrecognized")
  457 + pass
  458 +
  459 +
  460 +class PlcDevice(Device):
  461 + #device = models.ForeignKey('Plc', on_delete=models.CASCADE, related_name='plc_devices')
  462 + name = models.CharField(max_length=45, blank=True, null=True)
  463 + desc = models.TextField(blank=True, null=True)
  464 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  465 + updated = models.DateTimeField(blank=True, null=True, auto_now=True)
  466 +
  467 + class Meta:
  468 + managed = True
  469 + db_table = 'plc_devices'
  470 +
  471 + def __str__(self):
  472 + return str(self.name)
  473 +
  474 +
  475 +
  476 +
  477 +
  478 +class Detector(Device):
  479 + VIS = "Visible camera"
  480 + NIR = "Cagire"
  481 +
  482 + telescope = models.ForeignKey(
  483 + 'Telescope', models.DO_NOTHING, related_name="detectors")
  484 + nb_photo_x = models.IntegerField(blank=True, null=True)
  485 + nb_photo_y = models.IntegerField(blank=True, null=True)
  486 + photo_size_x = models.IntegerField(blank=True, null=True)
  487 + photo_size_y = models.IntegerField(blank=True, null=True)
  488 + has_shutter = models.BooleanField(default=False)
  489 + equivalent_foc_len = models.CharField(max_length=45, blank=True, null=True)
  490 + acq_start = models.DateTimeField(blank=True, null=True)
  491 + acq_stop = models.DateTimeField(blank=True, null=True)
  492 + check_temp = models.FloatField(blank=True, null=True)
  493 + gain = models.FloatField(blank=True, null=True)
  494 + readout_noise = models.FloatField(blank=True, null=True)
  495 + readout_time = models.FloatField(blank=True, null=True)
  496 + idcam_readout_mode = models.IntegerField(blank=True, null=True)
  497 +
  498 + class Meta:
  499 + managed = True
  500 + db_table = 'detector'
  501 +
  502 + def __str__(self):
  503 + return str(self.name)
  504 +
  505 + def device_name(self):
  506 + return self.__str__()
  507 + device_name.short_description = "Name"
  508 +
  509 +
  510 +class Dome(Device):
  511 + DOME = "Dome"
  512 +
  513 + open = models.BooleanField(default=False, blank=True)
  514 +
  515 + class Meta:
  516 + managed = True
  517 + db_table = 'dome'
  518 +
  519 + def __str__(self):
  520 + return str(self.name)
  521 +
  522 + def device_name(self):
  523 + return self.__str__()
  524 + device_name.short_description = "Name"
  525 +
  526 +
  527 +class Filter(Device):
  528 + VIS_FILTER_1 = "First visible filter"
  529 + VIS_FILTER_2 = "Second visible filter"
  530 + NIR_FILTER_1 = "First infrared filter"
  531 + NIR_FILTER_2 = "Second infrared filter"
  532 +
  533 + filter_wheel = models.ForeignKey(
  534 + "FilterWheel", models.DO_NOTHING, related_name="filters", blank=True, null=True)
  535 + category = models.CharField(max_length=1, blank=True, null=True)
  536 + transmission_curve_doc = models.CharField(
  537 + max_length=45, blank=True, null=True)
  538 +
  539 + class Meta:
  540 + managed = True
  541 + db_table = 'filter'
  542 +
  543 + def __str__(self):
  544 + return (str(self.name))
  545 +
  546 + def device_name(self):
  547 + return self.__str__()
  548 + device_name.short_description = "Name"
  549 +
  550 +
  551 +class FilterWheel(Device):
  552 + detector = models.OneToOneField(Detector, on_delete=models.CASCADE,
  553 + related_name="filter_wheel", blank=True, null=True)
  554 +
  555 + class Meta:
  556 + managed = True
  557 + db_table = 'filter_wheel'
  558 +
  559 + def __str__(self):
  560 + return (str(self.name))
  561 +
  562 + def device_name(self):
  563 + return self.__str__()
  564 + device_name.short_description = "Name"
  565 +
  566 +
  567 +class Telescope(Device):
  568 + TELESCOPE = "Telescope"
  569 +
  570 + mount_type = models.CharField(max_length=9, blank=True, null=True)
  571 + diameter = models.FloatField(blank=True, null=True)
  572 + latitude = models.FloatField(blank=True, null=True)
  573 + longitude = models.FloatField(blank=True, null=True)
  574 + sens = models.CharField(max_length=1, blank=True, null=True)
  575 + altitude = models.FloatField(blank=True, null=True)
  576 + readout_time = models.IntegerField(blank=True, null=True)
  577 + slew_time = models.IntegerField(blank=True, null=True)
  578 + slew_dead = models.IntegerField(blank=True, null=True)
  579 + slew_rate_max = models.FloatField(blank=True, null=True)
  580 + horizon_type = models.CharField(max_length=45, blank=True, null=True)
  581 + horizon_def = models.FloatField(blank=True, null=True)
  582 + lim_dec_max = models.FloatField(blank=True, null=True)
  583 + lim_dec_min = models.FloatField(blank=True, null=True)
  584 + lim_ha_rise = models.FloatField(blank=True, null=True)
  585 + lim_ha_set = models.FloatField(blank=True, null=True)
  586 + address = models.CharField(max_length=45, blank=True, null=True)
  587 + night_elev_sun = models.FloatField(blank=True, null=True)
  588 + mpc_code = models.CharField(max_length=45, blank=True, null=True)
  589 +
  590 + class Meta:
  591 + managed = True
  592 + db_table = 'telescope'
  593 +
  594 + def __str__(self):
  595 + return (self.name)
  596 +
  597 +
  598 +class TelescopeCommand(models.Model):
  599 + created = models.DateTimeField(blank=True, null=True, auto_now_add=True)
  600 + answered = models.DateTimeField(blank=True, null=True)
  601 + request = models.CharField(blank=False, null=False, max_length=255)
  602 + answer = models.TextField(null=True, blank=True)
  603 +
  604 + class Meta:
  605 + managed = True
  606 + db_table = "telescopecommand"
  607 +
  608 + def __str__(self):
  609 + return str(self.request) + str(self.created)
  610 +
4 611 \ No newline at end of file
... ...